From 8b5efc6f4ccaf3f8e12abd1dd32cc49825925ad3 Mon Sep 17 00:00:00 2001 From: Gourav Roy <34737471+themechanicalcoder@users.noreply.github.com> Date: Sat, 22 Feb 2020 17:12:26 +0530 Subject: [PATCH] Merge pull request #16586 from themechanicalcoder:video-psnr * add python version of video-input-psnr-ssim * remove ret * documentation changes * added link for python file * command line argument --- .../video_input_psnr_ssim.markdown | 95 +++-------- .../video-input-psnr-ssim.cpp | 5 + .../videoio/video-input-psnr-ssim.py | 148 ++++++++++++++++++ 3 files changed, 174 insertions(+), 74 deletions(-) create mode 100644 samples/python/tutorial_code/videoio/video-input-psnr-ssim.py diff --git a/doc/tutorials/videoio/video-input-psnr-ssim/video_input_psnr_ssim.markdown b/doc/tutorials/videoio/video-input-psnr-ssim/video_input_psnr_ssim.markdown index 80205213a2..96c6637c5f 100644 --- a/doc/tutorials/videoio/video-input-psnr-ssim/video_input_psnr_ssim.markdown +++ b/doc/tutorials/videoio/video-input-psnr-ssim/video_input_psnr_ssim.markdown @@ -25,7 +25,13 @@ version of it ](https://github.com/opencv/opencv/tree/3.4/samples/data/Megamind_ You may also find the source code and these video file in the `samples/data` folder of the OpenCV source library. +@add_toggle_cpp @include cpp/tutorial_code/videoio/video-input-psnr-ssim/video-input-psnr-ssim.cpp +@end_toggle + +@add_toggle_python +@include samples/python/tutorial_code/videoio/video-input-psnr-ssim.py +@end_toggle How to read a video stream (online-camera or offline-file)? ----------------------------------------------------------- @@ -139,28 +145,15 @@ an invalid divide by zero operation in the PSNR formula. In this case the PSNR i we'll need to handle this case separately. The transition to a logarithmic scale is made because the pixel values have a very wide dynamic range. All this translated to OpenCV and a C++ function looks like: -@code{.cpp} -double getPSNR(const Mat& I1, const Mat& I2) -{ - Mat s1; - absdiff(I1, I2, s1); // |I1 - I2| - s1.convertTo(s1, CV_32F); // cannot make a square on 8 bits - s1 = s1.mul(s1); // |I1 - I2|^2 - - Scalar s = sum(s1); // sum elements per channel - - double sse = s.val[0] + s.val[1] + s.val[2]; // sum channels - - if( sse <= 1e-10) // for small values return zero - return 0; - else - { - double mse =sse /(double)(I1.channels() * I1.total()); - double psnr = 10.0*log10((255*255)/mse); - return psnr; - } -} -@endcode + +@add_toggle_cpp +@include cpp/tutorial_code/videoio/video-input-psnr-ssim/video-input-psnr-ssim.cpp get-psnr +@end_toggle + +@add_toggle_python +@include samples/python/tutorial_code/videoio/video-input-psnr-ssim.py get-psnr +@end_toggle + Typically result values are anywhere between 30 and 50 for video compression, where higher is better. If the images significantly differ you'll get much lower ones like 15 and so. This similarity check is easy and fast to calculate, however in practice it may turn out somewhat @@ -176,60 +169,14 @@ implementation below. Simoncelli, "Image quality assessment: From error visibility to structural similarity," IEEE Transactions on Image Processing, vol. 13, no. 4, pp. 600-612, Apr. 2004." article. -@code{.cpp} -Scalar getMSSIM( const Mat& i1, const Mat& i2) -{ - const double C1 = 6.5025, C2 = 58.5225; - /***************************** INITS **********************************/ - int d = CV_32F; - - Mat I1, I2; - i1.convertTo(I1, d); // cannot calculate on one byte large values - i2.convertTo(I2, d); - - Mat I2_2 = I2.mul(I2); // I2^2 - Mat I1_2 = I1.mul(I1); // I1^2 - Mat I1_I2 = I1.mul(I2); // I1 * I2 +@add_toggle_cpp +@include cpp/tutorial_code/videoio/video-input-psnr-ssim/video-input-psnr-ssim.cpp get-mssim +@end_toggle - /***********************PRELIMINARY COMPUTING ******************************/ +@add_toggle_python +@include samples/python/tutorial_code/videoio/video-input-psnr-ssim.py get-mssim +@end_toggle - Mat mu1, mu2; // - GaussianBlur(I1, mu1, Size(11, 11), 1.5); - GaussianBlur(I2, mu2, Size(11, 11), 1.5); - - Mat mu1_2 = mu1.mul(mu1); - Mat mu2_2 = mu2.mul(mu2); - Mat mu1_mu2 = mu1.mul(mu2); - - Mat sigma1_2, sigma2_2, sigma12; - - GaussianBlur(I1_2, sigma1_2, Size(11, 11), 1.5); - sigma1_2 -= mu1_2; - - GaussianBlur(I2_2, sigma2_2, Size(11, 11), 1.5); - sigma2_2 -= mu2_2; - - GaussianBlur(I1_I2, sigma12, Size(11, 11), 1.5); - sigma12 -= mu1_mu2; - - ///////////////////////////////// FORMULA //////////////////////////////// - Mat t1, t2, t3; - - t1 = 2 * mu1_mu2 + C1; - t2 = 2 * sigma12 + C2; - t3 = t1.mul(t2); // t3 = ((2*mu1_mu2 + C1).*(2*sigma12 + C2)) - - t1 = mu1_2 + mu2_2 + C1; - t2 = sigma1_2 + sigma2_2 + C2; - t1 = t1.mul(t2); // t1 =((mu1_2 + mu2_2 + C1).*(sigma1_2 + sigma2_2 + C2)) - - Mat ssim_map; - divide(t3, t1, ssim_map); // ssim_map = t3./t1; - - Scalar mssim = mean( ssim_map ); // mssim = average of ssim map - return mssim; -} -@endcode This will return a similarity index for each channel of the image. This value is between zero and one, where one corresponds to perfect fit. Unfortunately, the many Gaussian blurring is quite costly, so while the PSNR may work in a real time like environment (24 frame per second) this will diff --git a/samples/cpp/tutorial_code/videoio/video-input-psnr-ssim/video-input-psnr-ssim.cpp b/samples/cpp/tutorial_code/videoio/video-input-psnr-ssim/video-input-psnr-ssim.cpp index be0b1a8a21..8d567b2f5e 100644 --- a/samples/cpp/tutorial_code/videoio/video-input-psnr-ssim/video-input-psnr-ssim.cpp +++ b/samples/cpp/tutorial_code/videoio/video-input-psnr-ssim/video-input-psnr-ssim.cpp @@ -132,6 +132,7 @@ int main(int argc, char *argv[]) return 0; } +// ![get-psnr] double getPSNR(const Mat& I1, const Mat& I2) { Mat s1; @@ -152,6 +153,9 @@ double getPSNR(const Mat& I1, const Mat& I2) return psnr; } } +// ![get-psnr] + +// ![get-mssim] Scalar getMSSIM( const Mat& i1, const Mat& i2) { @@ -205,3 +209,4 @@ Scalar getMSSIM( const Mat& i1, const Mat& i2) Scalar mssim = mean(ssim_map); // mssim = average of ssim map return mssim; } +// ![get-mssim] diff --git a/samples/python/tutorial_code/videoio/video-input-psnr-ssim.py b/samples/python/tutorial_code/videoio/video-input-psnr-ssim.py new file mode 100644 index 0000000000..84610d4768 --- /dev/null +++ b/samples/python/tutorial_code/videoio/video-input-psnr-ssim.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Python 2/3 compatibility +from __future__ import print_function + +import numpy as np +import cv2 as cv +import argparse +import sys + +# [get-psnr] +def getPSNR(I1, I2): + s1 = cv.absdiff(I1, I2) #|I1 - I2| + s1 = np.float32(s1) # cannot make a square on 8 bits + s1 = s1 * s1 # |I1 - I2|^2 + sse = s1.sum() # sum elements per channel + if sse <= 1e-10: # sum channels + return 0 # for small values return zero + else: + shape = I1.shape + mse = 1.0 * sse / (shape[0] * shape[1] * shape[2]) + psnr = 10.0 * np.log10((255 * 255) / mse) + return psnr +# [get-psnr] + +# [get-mssim] +def getMSSISM(i1, i2): + C1 = 6.5025 + C2 = 58.5225 + # INITS + + I1 = np.float32(i1) # cannot calculate on one byte large values + I2 = np.float32(i2) + + I2_2 = I2 * I2 # I2^2 + I1_2 = I1 * I1 # I1^2 + I1_I2 = I1 * I2 # I1 * I2 + # END INITS + + # PRELIMINARY COMPUTING + mu1 = cv.GaussianBlur(I1, (11, 11), 1.5) + mu2 = cv.GaussianBlur(I2, (11, 11), 1.5) + + mu1_2 = mu1 * mu1 + mu2_2 = mu2 * mu2 + mu1_mu2 = mu1 * mu2 + + sigma1_2 = cv.GaussianBlur(I1_2, (11, 11), 1.5) + sigma1_2 -= mu1_2 + + sigma2_2 = cv.GaussianBlur(I2_2, (11, 11), 1.5) + sigma2_2 -= mu2_2 + + sigma12 = cv.GaussianBlur(I1_I2, (11, 11), 1.5) + sigma12 -= mu1_mu2 + + t1 = 2 * mu1_mu2 + C1 + t2 = 2 * sigma12 + C2 + t3 = t1 * t2 # t3 = ((2*mu1_mu2 + C1).*(2*sigma12 + C2)) + + t1 = mu1_2 + mu2_2 + C1 + t2 = sigma1_2 + sigma2_2 + C2 + t1 = t1 * t2 # t1 =((mu1_2 + mu2_2 + C1).*(sigma1_2 + sigma2_2 + C2)) + + ssim_map = cv.divide(t3, t1) # ssim_map = t3./t1; + + mssim = cv.mean(ssim_map) # mssim = average of ssim map + return mssim +# [get-mssim] + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("-d", "--delay", type=int, default=30, help=" Time delay") + parser.add_argument("-v", "--psnrtriggervalue", type=int, default=30, help="PSNR Trigger Value") + parser.add_argument("-r", "--ref", type=str, default="Megamind.avi", help="Path to reference video") + parser.add_argument("-t", "--undertest", type=str, default="Megamind_bugy.avi", + help="Path to the video to be tested") + args = parser.parse_args() + + sourceReference = args.ref + sourceCompareWith = args.undertest + delay = args.delay + psnrTriggerValue = args.psnrtriggervalue + + framenum = -1 # Frame counter + + captRefrnc = cv.VideoCapture(sourceReference) + captUndTst = cv.VideoCapture(sourceCompareWith) + + if not captRefrnc.isOpened(): + print("Could not open the reference " + sourceReference) + sys.exit(-1) + if not captUndTst.isOpened(): + print("Could not open case test " + sourceCompareWith) + sys.exit(-1) + + refS = (int(captRefrnc.get(cv.CAP_PROP_FRAME_WIDTH)), int(captRefrnc.get(cv.CAP_PROP_FRAME_HEIGHT))) + uTSi = (int(captUndTst.get(cv.CAP_PROP_FRAME_WIDTH)), int(captUndTst.get(cv.CAP_PROP_FRAME_HEIGHT))) + + if refS != uTSi: + print("Inputs have different size!!! Closing.") + sys.exit(-1) + + WIN_UT = "Under Test" + WIN_RF = "Reference" + + cv.namedWindow(WIN_RF, cv.WINDOW_AUTOSIZE) + cv.namedWindow(WIN_UT, cv.WINDOW_AUTOSIZE) + cv.moveWindow(WIN_RF, 400, 0) #750, 2 (bernat =0) + cv.moveWindow(WIN_UT, refS[0], 0) #1500, 2 + + print("Reference frame resolution: Width={} Height={} of nr#: {}".format(refS[0], refS[1], + captRefrnc.get(cv.CAP_PROP_FRAME_COUNT))) + print("PSNR trigger value {}".format(psnrTriggerValue)) + + while True: # Show the image captured in the window and repeat + _, frameReference = captRefrnc.read() + _, frameUnderTest = captUndTst.read() + + if frameReference is None or frameUnderTest is None: + print(" < < < Game over! > > > ") + break + + framenum += 1 + psnrv = getPSNR(frameReference, frameUnderTest) + print("Frame: {}# {}dB".format(framenum, round(psnrv, 3)), end=" ") + + if (psnrv < psnrTriggerValue and psnrv): + mssimv = getMSSISM(frameReference, frameUnderTest) + print("MSSISM: R {}% G {}% B {}%".format(round(mssimv[2] * 100, 2), round(mssimv[1] * 100, 2), + round(mssimv[0] * 100, 2)), end=" ") + + print() + + cv.imshow(WIN_RF, frameReference) + cv.imshow(WIN_UT, frameUnderTest) + + k = cv.waitKey(delay) + if k == 27: + break + + sys.exit(0) + + +if __name__ == "__main__": + main()