本文将使用OpenCV C++ 在案例实战二的基础上,编写新算法生成蒙太奇图像。
原图如图所示。
double calmyHist(Mat src, Mat temp) { //灰度图 if (src.channels() == 1) { int histSize = 256; float range[] = { 0,256 }; const float*histRange = { range }; Mat src_hist, temp_hist; //计算直方图 calcHist(&src, 1, 0, Mat(), src_hist, 1, &histSize, &histRange); calcHist(&temp, 1, 0, Mat(), temp_hist, 1, &histSize, &histRange); //归一化 normalize(src_hist, src_hist, 0, 1, NORM_MINMAX); normalize(temp_hist, temp_hist, 0, 1, NORM_MINMAX); //直方图比较 double dis = compareHist(src_hist, temp_hist, HISTCMP_CORREL); return dis; } //彩色图 else { //使用split进行通道分离 vector<Mat>src_bgr, temp_bgr; split(src, src_bgr); split(temp, temp_bgr); int histSize = 256; float range[] = { 0,256 }; const float*histRange = { range }; //计算直方图 Mat src_hist_b, src_hist_g, src_hist_r, temp_hist_b, temp_hist_g, temp_hist_r; calcHist(&src_bgr[0], 1, 0, Mat(), src_hist_b, 1, &histSize, &histRange); calcHist(&src_bgr[1], 1, 0, Mat(), src_hist_g, 1, &histSize, &histRange); calcHist(&src_bgr[2], 1, 0, Mat(), src_hist_r, 1, &histSize, &histRange); calcHist(&temp_bgr[0], 1, 0, Mat(), temp_hist_b, 1, &histSize, &histRange); calcHist(&temp_bgr[1], 1, 0, Mat(), temp_hist_g, 1, &histSize, &histRange); calcHist(&temp_bgr[2], 1, 0, Mat(), temp_hist_r, 1, &histSize, &histRange); //归一化 normalize(src_hist_b, src_hist_b, 0, 1, NORM_MINMAX); normalize(src_hist_g, src_hist_g, 0, 1, NORM_MINMAX); normalize(src_hist_r, src_hist_r, 0, 1, NORM_MINMAX); normalize(temp_hist_b, temp_hist_b, 0, 1, NORM_MINMAX); normalize(temp_hist_g, temp_hist_g, 0, 1, NORM_MINMAX); normalize(temp_hist_r, temp_hist_r, 0, 1, NORM_MINMAX); vector<Mat>src_Mat = { src_hist_b ,src_hist_g ,src_hist_r }; vector<Mat>temp_Mat = { temp_hist_b ,temp_hist_g ,temp_hist_r }; //将b、g、r三通道进行合并 Mat src_hist, temp_hist; merge(src_Mat, src_hist); merge(temp_Mat, temp_hist); //直方图比较 double dis = compareHist(src_hist, temp_hist, HISTCMP_CORREL); return dis; } }
上述代码块可以帮助我们比较两幅图像直方图,以此来判断两幅图像的相似程度。关于compareHist API使用请自行百度。
生成的蒙版图。
像素加权效果图。
//计算均方误差MSE double getMSE(Mat src, Mat dst) { double mse = 0.0; if (src.channels() == 1) { for (int i = 0; i < src.rows; i++) { for (int j = 0; j < src.cols; j++) { double diff = pow(src.at<uchar>(i, j) - dst.at<uchar>(i, j), 2); mse += diff; } } } else { for (int i = 0; i < src.rows; i++) { for (int j = 0; j < src.cols; j++) { double b = pow(src.at<Vec3b>(i, j)[0] - dst.at<Vec3b>(i, j)[0], 2); double g = pow(src.at<Vec3b>(i, j)[1] - dst.at<Vec3b>(i, j)[1], 2); double r = pow(src.at<Vec3b>(i, j)[2] - dst.at<Vec3b>(i, j)[2], 2); double diff = b + g + r; mse += diff; } } } double MSE = mse / (src.rows*src.cols); //均方误差 return MSE; }
我们通过计算MSE可以比较两幅图像的相似程度,MSE越小,表示两幅图像越相似;反之,MSE越大,则表示两幅图像越不相似。
生成的蒙版图。
像素加权效果图。
#include <iostream> #include<opencv2/opencv.hpp> using namespace std; using namespace cv; const int step_x = 20; const int step_y = 20; int getImagePathList(string folder, vector<String> &imagePathList) { glob(folder, imagePathList); return 0; } double calmyHist(Mat src, Mat temp) { //灰度图 if (src.channels() == 1) { cvtColor(temp, temp, COLOR_BGR2GRAY); int histSize = 256; float range[] = { 0,256 }; const float*histRange = { range }; Mat src_hist, temp_hist; //计算直方图 calcHist(&src, 1, 0, Mat(), src_hist, 1, &histSize, &histRange); calcHist(&temp, 1, 0, Mat(), temp_hist, 1, &histSize, &histRange); //归一化 normalize(src_hist, src_hist, 0, 1, NORM_MINMAX); normalize(temp_hist, temp_hist, 0, 1, NORM_MINMAX); //直方图比较 double dis = compareHist(src_hist, temp_hist, HISTCMP_CORREL); return dis; } //彩色图 else { //使用split进行通道分离 vector<Mat>src_bgr, temp_bgr; split(src, src_bgr); split(temp, temp_bgr); int histSize = 256; float range[] = { 0,256 }; const float*histRange = { range }; //计算直方图 Mat src_hist_b, src_hist_g, src_hist_r, temp_hist_b, temp_hist_g, temp_hist_r; calcHist(&src_bgr[0], 1, 0, Mat(), src_hist_b, 1, &histSize, &histRange); calcHist(&src_bgr[1], 1, 0, Mat(), src_hist_g, 1, &histSize, &histRange); calcHist(&src_bgr[2], 1, 0, Mat(), src_hist_r, 1, &histSize, &histRange); calcHist(&temp_bgr[0], 1, 0, Mat(), temp_hist_b, 1, &histSize, &histRange); calcHist(&temp_bgr[1], 1, 0, Mat(), temp_hist_g, 1, &histSize, &histRange); calcHist(&temp_bgr[2], 1, 0, Mat(), temp_hist_r, 1, &histSize, &histRange); //归一化 normalize(src_hist_b, src_hist_b, 0, 1, NORM_MINMAX); normalize(src_hist_g, src_hist_g, 0, 1, NORM_MINMAX); normalize(src_hist_r, src_hist_r, 0, 1, NORM_MINMAX); normalize(temp_hist_b, temp_hist_b, 0, 1, NORM_MINMAX); normalize(temp_hist_g, temp_hist_g, 0, 1, NORM_MINMAX); normalize(temp_hist_r, temp_hist_r, 0, 1, NORM_MINMAX); vector<Mat>src_Mat = { src_hist_b ,src_hist_g ,src_hist_r }; vector<Mat>temp_Mat = { temp_hist_b ,temp_hist_g ,temp_hist_r }; //将b、g、r三通道进行合并 Mat src_hist, temp_hist; merge(src_Mat, src_hist); merge(temp_Mat, temp_hist); //直方图比较 double dis = compareHist(src_hist, temp_hist, HISTCMP_CORREL); return dis; } } //计算均方误差MSE double getMSE(Mat src, Mat dst) { double mse = 0.0; if (src.channels() == 1) { cvtColor(dst, dst, COLOR_BGR2GRAY); for (int i = 0; i < src.rows; i++) { for (int j = 0; j < src.cols; j++) { double diff = pow(src.at<uchar>(i, j) - dst.at<uchar>(i, j), 2); mse += diff; } } } else { for (int i = 0; i < src.rows; i++) { for (int j = 0; j < src.cols; j++) { double b = pow(src.at<Vec3b>(i, j)[0] - dst.at<Vec3b>(i, j)[0], 2); double g = pow(src.at<Vec3b>(i, j)[1] - dst.at<Vec3b>(i, j)[1], 2); double r = pow(src.at<Vec3b>(i, j)[2] - dst.at<Vec3b>(i, j)[2], 2); double diff = b + g + r; mse += diff; } } } double MSE = mse / (src.rows*src.cols); //均方误差 return MSE; } int main() { Mat src = imread("Taylor.jpg"); if (src.empty()) { cout << "No image!" << endl; system("pause"); return 0; } resize(src, src, Size(step_x*30, step_y*30), 1, 1, INTER_CUBIC); vector<Mat>images; string filename = "images/"; cout << "loading..." << endl; //素材照片 vector<String> imagePathList; getImagePathList(filename, imagePathList); for (int i = 0; i < imagePathList.size(); i++) { Mat img = imread(imagePathList[i]); resize(img, img, Size(step_x, step_y), 1, 1, INTER_AREA); images.push_back(img); } cout << "size:" << images.size() << endl; cout << "done!" << endl; int rows = src.rows; int cols = src.cols; //height:表示生成的蒙太奇图像需要多少张素材图像填充rows //width:表示生成的蒙太奇图像需要多少张素材图像填充cols int height = rows / step_y, width = cols / step_x; Mat dst = Mat(src.size(), CV_8UC3, Scalar(255, 255, 255)); Mat temp; for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { //计算当前ROI区域与图库中所有图片的直方图,找出最相似的一张作为填充ROI区域图片 Mat ROI = src(Rect(j * step_x, i * step_y, step_x, step_y)); double min =1000000.0; Mat result; for (int z = 0; z < images.size(); z++) { double dis = getMSE(ROI, images[z]); if (dis < min) { min = dis; result = images[z]; } } result.copyTo(temp); //将temp图像赋值给需要生成的蒙太奇图像对应区域 temp = dst(Rect(j * step_x, i * step_y, step_x, step_y)); //index表示当前ROI索引 int index = i * width + j; cout << "匹配成功:" << index << endl; } } namedWindow("dst", WINDOW_NORMAL); imwrite("蒙版.jpg", dst); imshow("dst", dst); for (int i = 0; i < rows; ++i) { for (int j = 0; j < cols; ++j) { //像素RGB值修改 dst.at<Vec3b>(i, j)[0] = 0.312*dst.at<Vec3b>(i, j)[0] + 0.688*src.at<Vec3b>(i, j)[0]; dst.at<Vec3b>(i, j)[1] = 0.312*dst.at<Vec3b>(i, j)[1] + 0.688*src.at<Vec3b>(i, j)[1]; dst.at<Vec3b>(i, j)[2] = 0.312*dst.at<Vec3b>(i, j)[2] + 0.688*src.at<Vec3b>(i, j)[2]; } } namedWindow("蒙太奇图像", WINDOW_NORMAL); imwrite("蒙太奇图像.jpg", dst); imshow("蒙太奇图像", dst); waitKey(0); system("pause"); return 0; }
本文使用OpenCV C++生成蒙太奇图像,关键步骤有以下几点。
1、基于直方图比较:找到与待填充区域(ROI)最相似的图片进行填充。
2、基于MSE比较:找到与待填充区域(ROI)最相似的图片进行填充。
对比以上两种方法,从效果上看,个人觉得使用MSE方法比较两幅图相似效果会更好。