【OpenCV】图像/视频相似度测量PSNR( Peak signal-to-noise ratio) and SSIM,视频/图片转换
目錄
1 目標
2 原理
2.1 圖像比較 - PSNR and SSIM?
3 代碼
3.1如何讀取一個視頻流(攝像頭或者視頻文件)??
3 運行效果
視頻/圖片轉換:
- 如何用OpenCV創建一個視頻文件
- 用OpenCV能創建什么樣的視頻文件
- 如何釋放視頻文件當中的某個顏色通道
為了使例子簡單,我就僅僅釋放原始視頻RGB通道中的一個,并把它放入新視頻文件中。你可以使用命令行參數來控制程序的一些行為:
- 第一個參數指向你需要操作的視頻文件。
- 第二個參數可以是如下的幾個字母之一:R G B。用來指定你需要釋放哪一個通道。
- 最后一個參數是Y(Yes)或者N(No). 如果你選擇N, 就直接使用視頻輸入格式來創建輸出文件,否則就會彈出一個對話框來讓你選擇編碼器。
1 目標
-
如何打開和讀取視頻流
-
兩種檢查相似度的方法:PSNR和SSIM
-
數據鏈接:https://pan.baidu.com/s/1zqcFKmWViSF1O8QK2pVhMg 提取碼:g5p9
2 原理
2.1 圖像比較 - PSNR and SSIM?
我們想檢查壓縮視頻帶來的細微差異的時候,就需要構建一個能夠逐幀比較差視頻差異的系統。最常用的比較算法是PSNR( Peak signal-to-noise ratio)。這是個使用“局部均值誤差”來判斷差異的最簡單的方法,假設有這兩幅圖像:I1和I2,它們的行列數分別是i,j,有c個通道。
PSNR公式如下:
每個像素的每個通道的值占用一個字節,值域[0,255]。這里每個像素會有 個有效的最大值 注意當兩幅圖像的相同的話,MSE的值會變成0。這樣會導致PSNR的公式會除以0而變得沒有意義。所以我們需要單獨的處理這樣的特殊情況。此外由于像素的動態范圍很廣,在處理時會使用對數變換來縮小范圍。這些變換的C++代碼如下:
double getPSNR(const Mat& I1, const Mat& I2)
{Mat s1;absdiff(I1, I2, s1); ? ? ? // |I1 - I2|s1.convertTo(s1, CV_32F); // 不能在8位矩陣上做平方運算s1 = s1.mul(s1); ? ? ? ? ? // |I1 - I2|^2
?Scalar s = sum(s1); ? ? ? ? // 疊加每個通道的元素
?double sse = s.val[0] + s.val[1] + s.val[2]; // 疊加所有通道
?if( sse <= 1e-10) // 如果值太小就直接等于0return 0;else{double mse =sse /(double)(I1.channels() * I1.total());double psnr = 10.0*log10((255*255)/mse);return psnr;}
}
在考察壓縮后的視頻時,這個值大約在30到50之間,數字越大則表明壓縮質量越好。如果圖像差異很明顯,就可能會得到15甚至更低的值。**PSNR算法簡單,檢查的速度也很快。但是其呈現的差異值有時候和人的主觀感受不成比例。所以有另外一種稱作 結構相似性 的算法做出了這方面的改進。**
建議你閱讀一些關于SSIM算法的文獻來更好的理解算法,然而及時你直接看下面的源代碼,應該也能建立一個不錯的映像。請參考下面深度解析SSIM的文章:
”Z. Wang, A. C. Bovik, H. R. Sheikh and E. P. Simoncelli, “Image quality assessment: From error visibility to structural similarity,” IEEE Transactions on Image Processing, vol. 13, no. 4, pp. 600-612, Apr. 2004.”
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); ? ? ? ? ? // 不能在單字節像素上進行計算,范圍不夠。i2.convertTo(I2, d);
?Mat I2_2 ? = I2.mul(I2); ? ? ? // I2^2Mat I1_2 ? = I1.mul(I1); ? ? ? // I1^2Mat I1_I2 = I1.mul(I2); ? ? ? // I1 * I2
?/***********************初步計算 ******************************/
?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;
?/ 公式 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 = ssim_map的平均值return mssim;
}
這個操作會針對圖像的每個通道返回一個相似度,取值范圍應該在0到1之間,取值為1時代表完全符合。然而盡管SSIM能產生更優秀的數據,但是由于高斯模糊很花時間,所以在一個實時系統(每秒24幀)中,人們還是更多地采用PSNR算法。
正是這個原因,最開始的源碼里,我們用PSNR算法去計算每一幀圖像,而僅當PSNR算法計算出的結果低于輸入值的時候,用SSIM算法去驗證。為了展示數據,我們在例程里用兩個窗口顯示了原圖像和測試圖像并且在控制臺上輸出了PSNR和SSIM數據。就像4運行結果。
3 代碼
3.1如何讀取一個視頻流(攝像頭或者視頻文件)??
總的來說,視頻捕獲需要的所有函數都集成在 VideoCapture C++ 類里面。雖然它底層依賴另一個FFmpeg開源庫,但是它已經被集成在OpenCV里所以你不需要額外地關注它的具體實現方法。你只需要知道一個視頻由一系列圖像構成,我們用一個專業點兒的詞匯來稱呼這些構成視頻的圖像:“幀”(frame)。此外在視頻文件里還有個參數叫做“幀率”(frame rate)的,用來表示兩幀之間的間隔時間,幀率的單位是(幀/秒)。這個參數只和視頻的播放速度有關,對于單獨的一幀圖像來說沒有任何用途。
你需要先定義一個 VideoCapture 類的對象來打開和讀取視頻流。具體可以通過 constructor 或者通過 open 函數來完成。如果使用整型數當參數的話,就可以將這個對象綁定到一個攝像機,將系統指派的ID號當作參數傳入即可。例如你可以傳入0來打開第一個攝像機,傳入1打開第二個攝像機,以此類推。如果使用字符串當參數,就會打開一個由這個字符串(文件名)指定的視頻文件。例如在上面的例子里傳入如下參數:
video/Megamind.avi video/Megamind_bug.avi //35 10
前兩個參數傳入了兩個文件名,分別代表原始參考視頻和測試視頻。這里使用了相對地址,這也代表著系統會從軟件的工作目錄下的video子目錄里尋找文件。然后程序將針對這些參數開始進行相似性檢查
const string sourceReference = argv[1],sourceCompareWith = argv[2];
?
VideoCapture captRefrnc(sourceReference);
// 或者
VideoCapture captUndTst;
captUndTst.open(sourceCompareWith);
你可以用 isOpened 函數來檢查視頻是否成功打開與否:
if ( !captRefrnc.isOpened()){cout << "Could not open reference " << sourceReference << endl;return -1;}
當析構函數調用時,會自動關閉視頻。如果你希望提前關閉的話,你可以調用 release 函數. 視頻的每一幀都是一幅普通的圖像。因為我們僅僅需要從 VideoCapture 對象里釋放出每一幀圖像并保存成 Mat 格式。因為視頻流是連續的,所以你需要在每次調用 read 函數后及時保存圖像或者直接使用重載的>>操作符。
Mat frameReference, frameUnderTest;
captRefrnc >> frameReference;
captUndTst.open(frameUnderTest);
如果視頻幀無法捕獲(例如當視頻關閉或者完結的時候),上面的操作就會返回一個空的 Mat 對象。我們可以用下面的代碼檢查是否返回了空的圖像:
if( frameReference.empty() || frameUnderTest.empty())
{// 退出程序
}
讀取視頻幀的時候也會自動進行解碼操作。你可以通過調用 grab 和 retrieve 函數來顯示地進行這兩項操作。
視頻通常擁有很多除了視頻幀圖像以外的信息,像是幀數之類,有些時候數據較短,有些時候用4個字節的字符串來表示。所以 get 函數返回一個double(8個字節)類型的數據來表示這些屬性。然后你可以使用位操作符來操作這個返回值從而得到想要的整型數據等。這個函數有一個參數,代表著試圖查詢的屬性ID。在下面的例子里我們會先獲得食品的尺寸和幀數。
Size refS = Size((int) captRefrnc.get(CV_CAP_PROP_FRAME_WIDTH),(int) captRefrnc.get(CV_CAP_PROP_FRAME_HEIGHT)),
?
cout << "參考幀的分辨率: 寬度=" << refS.width << " 高度=" << refS.height<< " of nr#: " <<<< endl;
當你需要設置這些值的時候你可以調用 set 函數。函數的第一個參數是需要設置的屬性ID,第二個參數是需要設定的值,如果返回true的話就表示成功設定,否則就是false。接下來的這個例子很好地展示了如何設置視頻的時間位置或者幀數:
captRefrnc.set(CV_CAP_PROP_POS_MSEC, 1.2); // 跳轉到視頻1.2秒的位置
captRefrnc.set(CV_CAP_PROP_POS_FRAMES, 10); // 跳轉到視頻的第10幀
// 然后重新調用read來得到你剛剛設置的那一幀
你可以參考 get 和 set 函數的文檔來得到更多信息。
//src = imread("C:\\Users\\guoqi\\Desktop\\ch7\\4.jpg", 1);
#include <iostream> // for standard I/O
#include <string> ? // for strings
#include <iomanip> // for controlling float print precision
#include <sstream> // string to number conversion
?
#include <opencv2/imgproc/imgproc.hpp> // Gaussian Blur
#include <opencv2/core/core.hpp> ? ? ? // Basic OpenCV structures (cv::Mat, Scalar)
#include <opencv2/highgui/highgui.hpp> // OpenCV window I/O
?
using namespace std;
using namespace cv;
?
double getPSNR(const Mat& I1, const Mat& I2);
Scalar getMSSIM(const Mat& I1, const Mat& I2);
int main(int argc, char *argv[], char *window_name)
{//if (argc != 5)//{// cout << "Not enough parameters" << endl;// //return -1;//}stringstream conv;
?/*const string sourceReference = argv[1], sourceCompareWith = argv[2];*/const string sourceReference = "C:\\Users\\guoqi\\Desktop\\ch7\\Megamind.avi";const string sourceCompareWith = "C:\\Users\\guoqi\\Desktop\\ch7\\Megamind_bugy.avi";
?int psnrTriggerValue = 35;int delay = 10;//conv << argv[3] << endl << argv[4]; // put in the strings//conv >> psnrTriggerValue >> delay; // take out the numbers
?char c;int frameNum = -1; ? ? ? ? // Frame counter
?VideoCapture captRefrnc(sourceReference),captUndTst(sourceCompareWith);
?if (!captRefrnc.isOpened()){cout << "Could not open reference " << sourceReference << endl;return -1;}
?if (!captUndTst.isOpened()){cout << "Could not open case test " << sourceCompareWith << endl;return -1;}
?Size refS = Size((int)captRefrnc.get(CV_CAP_PROP_FRAME_WIDTH),(int)captRefrnc.get(CV_CAP_PROP_FRAME_HEIGHT)),uTSi = Size((int)captUndTst.get(CV_CAP_PROP_FRAME_WIDTH),(int)captUndTst.get(CV_CAP_PROP_FRAME_HEIGHT));
?if (refS != uTSi){cout << "Inputs have different size!!! Closing." << endl;return -1;}
?const char* WIN_UT = "Under Test";const char* WIN_RF = "Reference";
?// WindowsnamedWindow(WIN_RF, CV_WINDOW_AUTOSIZE);namedWindow(WIN_UT, CV_WINDOW_AUTOSIZE);cvMoveWindow(WIN_RF, 400, 0); ? ? ? ? ? ? //750, 2 (bernat =0)cvMoveWindow(WIN_UT, refS.width, 0); ? ? //1500, 2
?cout << "Reference frame resolution: Width=" << refS.width << " Height=" << refS.height<< " of nr#: " << captRefrnc.get(CV_CAP_PROP_FRAME_COUNT) << endl;
?cout << "PSNR trigger value " <<setiosflags(ios::fixed) << setprecision(3) << psnrTriggerValue << endl;
?Mat frameReference, frameUnderTest;double psnrV;Scalar mssimV;
?while (true) //Show the image captured in the window and repeat{captRefrnc >> frameReference;captUndTst >> frameUnderTest;
?if (frameReference.empty() || frameUnderTest.empty()){cout << " < < < Game over! > > > ";break;}
?++frameNum;cout << "Frame:" << frameNum << "# ";
?/ PSNR psnrV = getPSNR(frameReference, frameUnderTest); ? ? ? ? ? ? ? ? //get PSNRcout << setiosflags(ios::fixed) << setprecision(3) << psnrV << "dB";
?MSSIM /if (psnrV < psnrTriggerValue && psnrV){mssimV = getMSSIM(frameReference, frameUnderTest);
?cout << " MSSIM: "<< " R " << setiosflags(ios::fixed) << setprecision(2) << mssimV.val[2] * 100 << "%"<< " G " << setiosflags(ios::fixed) << setprecision(2) << mssimV.val[1] * 100 << "%"<< " B " << setiosflags(ios::fixed) << setprecision(2) << mssimV.val[0] * 100 << "%";}
?cout << endl;
?// Show Image /imshow(WIN_RF, frameReference);imshow(WIN_UT, frameUnderTest);
?c = cvWaitKey(delay);if (c == 27) break;}cv::waitKey();return 0;
}
?
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 bitss1 = 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 zeroreturn 0;else{double mse = sse / (double)(I1.channels() * I1.total());double psnr = 10.0*log10((255 * 255) / mse);return psnr;}
}
?
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 valuesi2.convertTo(I2, d);
?Mat I2_2 = I2.mul(I2); ? ? ? // I2^2Mat I1_2 = I1.mul(I1); ? ? ? // I1^2Mat I1_I2 = I1.mul(I2); ? ? ? // I1 * I2
?/*************************** END INITS **********************************/
?Mat mu1, mu2; ? // PRELIMINARY COMPUTINGGaussianBlur(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 mapreturn mssim;
}
?
?
4 運行效果
?
總結
以上是生活随笔為你收集整理的【OpenCV】图像/视频相似度测量PSNR( Peak signal-to-noise ratio) and SSIM,视频/图片转换的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 比德文M6价格是多少?
- 下一篇: 【MATLAB】————matlab r