Opencv对图像的直方图统计与滤波
基于Opencv实现在执行窗口可选滤波,要求如下:
设计一个软件,实现以下功能:
1.将图像拖入软件后,显示图像信息;
2.统计图像直方图并显示;
3.在监控台显示窗口输出菜单供选择滤波与增强方法,可供选择的方法包括:亮度/对比度调整、幂次变换、平滑滤波、高斯滤波、双边滤波,及按ESC或Q键结束程序;
4.按第2步选择的方法对输入图像进行图像变换或滤波,并可滑动块调节滤波参数及调节参数后的滤波结果,输出滤波图像与原图像的差异值图像;
5.计算滤波图像的MSE和PSNR;
6.将滤波图像的MSE和PSNR显示在图像的左上角;
7.按ESC后返回第1步。
代码如下
有点长,但其实很多块相似。
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include<string.h>
using namespace cv;
using namespace std;
int g_slider_position1 = 20;
int g_slider_position2 = 0;
int g_slider_position3 = 0;
int g_slider_position4 = 0;
char str_mse[20], str_psnr[20];
Mat image;
void MSE_PSNR(Mat image, Mat output) //计算MSE,PSNR
{
int sum = 0;
double mse, psnr;
for (int i = 0; i < image.rows; i++)
{
for (int j = 0; j < image.cols; j++)
{
sum = sum + (image.at<uchar>(i, j) - output.at<uchar>(i, j)) * (image.at<uchar>(i, j) - output.at<uchar>(i, j));
}
}
mse = sum / (image.rows * image.cols);
psnr = 10 * log10(255 * 255 / mse);
//以下为将数值字符串化
memset(str_mse, 0, sizeof(str_mse));
memset(str_psnr, 0, sizeof(str_psnr));
sprintf_s(str_mse, "%.2f", mse);
sprintf_s(str_psnr, "%.2f", psnr);
}
void callback_mici(int pos1, void*) //幂次变换
{
double pos11, gamma;
pos11 = (double)pos1;
gamma = pos11 / 100;
Mat image_output2, A, B;
namedWindow("幂次变换", 0);
image.convertTo(A, CV_32FC1, 1.0 / 255);
pow(A, gamma, B);
B.convertTo(image_output2, CV_8UC1, 255.0);
MSE_PSNR(image, image_output2);
putText(image_output2, str_mse, Point(100, 100), FONT_HERSHEY_COMPLEX, 1, Scalar(0, 255, 0), LINE_4, false);
putText(image_output2, str_psnr, Point(400, 100), FONT_HERSHEY_COMPLEX, 1, Scalar(0, 255, 0), LINE_4, false);
imshow("幂次变换", image_output2);
waitKey(0);
}
void callback_gaosi(int pos2, void*)
{
Mat image_gaosi;
namedWindow("高斯滤波", 0);
namedWindow("高斯滤波的差异值", 0);
GaussianBlur(image, image_gaosi, Size(3, 3), pos2, 0);
imshow("高斯滤波的差异值", image - image_gaosi);
MSE_PSNR(image, image_gaosi);
putText(image_gaosi, str_mse, Point(100, 100), FONT_HERSHEY_COMPLEX, 1, Scalar(0, 255, 0), LINE_4, false);
putText(image_gaosi, str_psnr, Point(400, 100), FONT_HERSHEY_COMPLEX, 1, Scalar(0, 255, 0), LINE_4, false);
imshow("高斯滤波", image_gaosi);
}
void callback_pinghua(int pos3, void*)
{
Mat image_pinghua;
namedWindow("平滑滤波", 0);
namedWindow("平滑滤波的差异值", 0);
boxFilter(image, image_pinghua, CV_8U, Size(pos3, pos3), Point(-1, -1), true, BORDER_DEFAULT);
MSE_PSNR(image, image_pinghua);
imshow("平滑滤波的差异值", image - image_pinghua);
putText(image_pinghua, str_mse, Point(100, 100), FONT_HERSHEY_COMPLEX, 1, Scalar(0, 255, 0), LINE_4, false);
putText(image_pinghua, str_psnr, Point(400, 100), FONT_HERSHEY_COMPLEX, 1, Scalar(0, 255, 0), LINE_4, false);
imshow("平滑滤波", image_pinghua);
}
void callback_shuangbian(int pos4, void*)
{
Mat image_shuangbian;
namedWindow("双边滤波", 0);
namedWindow("双边滤波的差异值", 0);
bilateralFilter(image, image_shuangbian, pos4, 100, 100, BORDER_DEFAULT);
imshow("双边滤波的差异值", image - image_shuangbian);
MSE_PSNR(image, image_shuangbian);
putText(image_shuangbian, str_mse, Point(100, 100), FONT_HERSHEY_COMPLEX, 1, Scalar(0, 255, 0), LINE_4, false);
putText(image_shuangbian, str_psnr, Point(400, 100), FONT_HERSHEY_COMPLEX, 1, Scalar(0, 255, 0), LINE_4, false);
imshow("双边滤波", image_shuangbian);
}
void duibidu_change1(int pos4,void*) //对比度调整
{
Mat image_output1;
convertScaleAbs(image, image_output1, pos4);
imshow("对比度调整", image_output1);
}
void duibidu_change2(int pos5, void*) //对比度调整
{
Mat image_output1;
convertScaleAbs(image, image_output1, 1.0, pos5);
imshow("对比度调整", image_output1);
}
void zhifangtu(Mat image)//仅对灰度图做直方图统计,彩色图通道数不对
{
Mat image1;
cvtColor(image, image1, COLOR_BGR2GRAY);//变为灰度图
int bins = 256;
int hist_size[] = { bins };
float range[] = { 0, 256 };
const float* ranges[] = { range };
Mat hist;
int channels[] = { 0 };
calcHist(
&image1, 1, channels, Mat(), hist, 1, hist_size, ranges, true, false);
double max_val;
minMaxLoc(hist, 0, &max_val, 0, 0);
int scale = 4;
int hist_height = 256;
Mat hist_img = Mat::zeros(hist_height, bins * scale, CV_8UC3);
for (int i = 0; i < bins; i++)
{
float bin_val = hist.at<float>(i);
int intensity = cvRound(bin_val * hist_height / max_val);
rectangle(hist_img, Point(i * scale, hist_height - intensity), Point((i + 1) * scale + 1, hist_height), CV_RGB(0, 255, 0), FILLED);
}
imshow("image1 Histogram", hist_img);
}
int main(int argc, char** argv)
{
image = imread(argv[1], -1);//用此实现拖动图像进入exe读取
if (image.empty()) return-1;
zhifangtu(image);
char selection;//选择方式
waitKey(3000);
printf("1为幂次变换,2为高斯滤波,3为平滑滤波,4为双边滤波,5为对比度调节,请选择\n");
//scanf_s("%d", &selection);
while (cin >> selection) {
if (selection == '1')
{
namedWindow("幂次变换的窗口", 0);
createTrackbar("gamma*100", "幂次变换的窗口", &g_slider_position1, 99, callback_mici);//滑动条的值为伽马值的100倍,伽马取值0-0.99。
waitKey(0);
destroyWindow("幂次变换的窗口");
destroyWindow("幂次变换");
}
if (selection == '2')
{
namedWindow("高斯滤波的窗口", 0);
createTrackbar("参数", "高斯滤波的窗口", &g_slider_position2, 20, callback_gaosi);
waitKey(0);
destroyWindow("高斯滤波的窗口");
destroyWindow("高斯滤波的差异值");
destroyWindow("高斯滤波");
}
if (selection == '3')
{
namedWindow("平滑滤波的窗口", 0);
createTrackbar("参数", "平滑滤波的窗口", &g_slider_position3, 20, callback_pinghua);
waitKey(0);
destroyWindow("平滑滤波的窗口");
destroyWindow("平滑滤波的差异值");
destroyWindow("平滑滤波");
}
if (selection == '4')
{
namedWindow("双边滤波的窗口", 0);
createTrackbar("参数", "双边滤波的窗口", &g_slider_position4, 20, callback_shuangbian);
waitKey(0);
destroyWindow("双边滤波的窗口");
destroyWindow("双边滤波的差异值");
destroyWindow("双边滤波");
}
if (selection == '5')
{
namedWindow("对比度和亮度变化", 0);
namedWindow("对比度调整", 0);
createTrackbar("对比度", "对比度和亮度变化", &g_slider_position4, 20, duibidu_change1);
createTrackbar("亮度", "对比度和亮度变化", &g_slider_position4, 20, duibidu_change2);
waitKey(0);
destroyWindow("对比度调整");
destroyWindow("对比度和亮度变化");
}
if (selection == 'q') return 0;
}
return 0;
}
对代码的解释
void MSE_PSNR(Mat image, Mat output) //计算MSE,PSNR
{
int sum = 0;
double mse, psnr;
for (int i = 0; i < image.rows; i++)
{
for (int j = 0; j < image.cols; j++)
{
sum = sum + (image.at<uchar>(i, j) - output.at<uchar>(i, j)) * (image.at<uchar>(i, j) - output.at<uchar>(i, j));
}
}
mse = sum / (image.rows * image.cols);
psnr = 10 * log10(255 * 255 / mse);
//以下为将数值字符串化
memset(str_mse, 0, sizeof(str_mse));
memset(str_psnr, 0, sizeof(str_psnr));
sprintf_s(str_mse, "%.2f", mse);
sprintf_s(str_psnr, "%.2f", psnr);
}
计算输入输出图像间的MSE,PSNR,相关公式请自行百度。(请注意此前全局变量的定义)
计算得到的int值在mse,psnr中,通过spintf将数值变为字符。(变为字符的说明:因为OpenCV中putText函数只能打印字符)
因为滤波种类太多,我以高斯滤波为例解释下代码。
if (selection == '2')
{
namedWindow("高斯滤波的窗口", 0);
createTrackbar("参数", "高斯滤波的窗口", &g_slider_position2, 20, callback_gaosi);
waitKey(0);
destroyWindow("高斯滤波的窗口");
destroyWindow("高斯滤波的差异值");
destroyWindow("高斯滤波");
}
首先是主函数里,定义你输入2时选择高斯滤波,创建高斯滤波的滑动条窗口,主体部分其实都在滑动条的回调函数里。
void callback_gaosi(int pos2, void*)
{
Mat image_gaosi;
namedWindow("高斯滤波", 0);
namedWindow("高斯滤波的差异值", 0);
GaussianBlur(image, image_gaosi, Size(3, 3), pos2, 0);
imshow("高斯滤波的差异值", image - image_gaosi);
MSE_PSNR(image, image_gaosi);
putText(image_gaosi, str_mse, Point(100, 100), FONT_HERSHEY_COMPLEX, 1, Scalar(0, 255, 0), LINE_4, false);
putText(image_gaosi, str_psnr, Point(400, 100), FONT_HERSHEY_COMPLEX, 1, Scalar(0, 255, 0), LINE_4, false);
imshow("高斯滤波", image_gaosi);
}
回调函数标准格式(int pos, void*)。
我把滤波参数设在了调整X,Y的系数值,putText的作用是把MSE,PSNR打印在显示的图片上,字体的选择请随意。
关于直方图统计,有些不足,只实现了对灰度图的直方图统计(当初偷懒,没弄彩色图的三通道直方图统计)。
void zhifangtu(Mat image)//仅对灰度图做直方图统计,彩色图通道数不对
{
Mat image1;
cvtColor(image, image1, COLOR_BGR2GRAY);//变为灰度图
int bins = 256;
int hist_size[] = { bins };
float range[] = { 0, 256 };
const float* ranges[] = { range };
Mat hist;
int channels[] = { 0 };
calcHist(
&image1, 1, channels, Mat(), hist, 1, hist_size, ranges, true, false);
double max_val;
minMaxLoc(hist, 0, &max_val, 0, 0);
int scale = 4;
int hist_height = 256;
Mat hist_img = Mat::zeros(hist_height, bins * scale, CV_8UC3);
for (int i = 0; i < bins; i++)
{
float bin_val = hist.at<float>(i);
int intensity = cvRound(bin_val * hist_height / max_val);
rectangle(hist_img, Point(i * scale, hist_height - intensity), Point((i + 1) * scale + 1, hist_height), CV_RGB(0, 255, 0), FILLED);
}
imshow("image1 Histogram", hist_img);
}
整体代码实现的效果如下
注:按q返回必须在命令窗口外,即q不能输入在命令里,而且需要趁早换成英文输入。