mirror of
https://github.com/opencv/opencv.git
synced 2025-06-11 03:33:28 +08:00
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
This commit is contained in:
parent
95f0c9b19b
commit
8b5efc6f4c
@ -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
|
You may also find the source code and these video file in the
|
||||||
`samples/data` folder of the OpenCV source library.
|
`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
|
@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)?
|
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
|
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
|
pixel values have a very wide dynamic range. All this translated to OpenCV and a C++ function looks
|
||||||
like:
|
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
|
@add_toggle_cpp
|
||||||
|
@include cpp/tutorial_code/videoio/video-input-psnr-ssim/video-input-psnr-ssim.cpp get-psnr
|
||||||
|
@end_toggle
|
||||||
|
|
||||||
double sse = s.val[0] + s.val[1] + s.val[2]; // sum channels
|
@add_toggle_python
|
||||||
|
@include samples/python/tutorial_code/videoio/video-input-psnr-ssim.py get-psnr
|
||||||
|
@end_toggle
|
||||||
|
|
||||||
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
|
|
||||||
Typically result values are anywhere between 30 and 50 for video compression, where higher is
|
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
|
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
|
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
|
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.
|
Transactions on Image Processing, vol. 13, no. 4, pp. 600-612, Apr. 2004." article.
|
||||||
|
|
||||||
@code{.cpp}
|
@add_toggle_cpp
|
||||||
Scalar getMSSIM( const Mat& i1, const Mat& i2)
|
@include cpp/tutorial_code/videoio/video-input-psnr-ssim/video-input-psnr-ssim.cpp get-mssim
|
||||||
{
|
@end_toggle
|
||||||
const double C1 = 6.5025, C2 = 58.5225;
|
|
||||||
/***************************** INITS **********************************/
|
|
||||||
int d = CV_32F;
|
|
||||||
|
|
||||||
Mat I1, I2;
|
@add_toggle_python
|
||||||
i1.convertTo(I1, d); // cannot calculate on one byte large values
|
@include samples/python/tutorial_code/videoio/video-input-psnr-ssim.py get-mssim
|
||||||
i2.convertTo(I2, d);
|
@end_toggle
|
||||||
|
|
||||||
Mat I2_2 = I2.mul(I2); // I2^2
|
|
||||||
Mat I1_2 = I1.mul(I1); // I1^2
|
|
||||||
Mat I1_I2 = I1.mul(I2); // I1 * I2
|
|
||||||
|
|
||||||
/***********************PRELIMINARY COMPUTING ******************************/
|
|
||||||
|
|
||||||
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
|
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
|
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
|
costly, so while the PSNR may work in a real time like environment (24 frame per second) this will
|
||||||
|
@ -132,6 +132,7 @@ int main(int argc, char *argv[])
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ![get-psnr]
|
||||||
double getPSNR(const Mat& I1, const Mat& I2)
|
double getPSNR(const Mat& I1, const Mat& I2)
|
||||||
{
|
{
|
||||||
Mat s1;
|
Mat s1;
|
||||||
@ -152,6 +153,9 @@ double getPSNR(const Mat& I1, const Mat& I2)
|
|||||||
return psnr;
|
return psnr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// ![get-psnr]
|
||||||
|
|
||||||
|
// ![get-mssim]
|
||||||
|
|
||||||
Scalar getMSSIM( const Mat& i1, const Mat& i2)
|
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
|
Scalar mssim = mean(ssim_map); // mssim = average of ssim map
|
||||||
return mssim;
|
return mssim;
|
||||||
}
|
}
|
||||||
|
// ![get-mssim]
|
||||||
|
148
samples/python/tutorial_code/videoio/video-input-psnr-ssim.py
Normal file
148
samples/python/tutorial_code/videoio/video-input-psnr-ssim.py
Normal file
@ -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()
|
Loading…
Reference in New Issue
Block a user