OpenCV C++案例实战七《生成蒙太奇图像》续

前言

本文将使用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)比较

//计算均方误差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));
	
	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(dst(Rect(j * step_x, i * step_y, step_x, step_y)));	
		}
	}

	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方法比较两幅图相似效果会更好。

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zero___Chen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值