1.直方图统计
直方图统计是一种用于分析图像或数据的统计方法,它通过统计每个数值或像素值的频率分布来了解数据的分布情况。
在OpenCV中,可以使用函数cv::calcHist()
来计算图像的直方图。
calcHist() 函数的原型如下:
void calcHist(const Mat* images, int nimages, const int* channels,
InputArray mask, OutputArray hist, int dims,
const int* histSize, const float** ranges,
bool uniform = true, bool accumulate = false);
参数说明:
- images: 输入图像数组,可以是单张图像或多张图像的数组。
- nimages: 输入图像的数量。
- channels: 要计算直方图的通道索引数组。例如,对于灰度图像,只有一个通道,因此 channels 设置为 {0};而对于彩色图像,可以指定 {0, 1, 2} 对应于 B、G、R 三个通道。
- mask: 掩码图像,用于指定计算直方图的区域。如果不需要使用掩码,可以传入空的 Mat()。
- hist: 输出的直方图,用于存储计算结果。
- dims: 直方图的维度,通常为 1。
- histSize: 直方图的大小,即每个维度的条目数量。
- ranges: 直方图的范围,可以使用 {0, 256} 表示像素值范围为 [0, 256)。
- uniform: 指示直方图条目是否均匀分布,默认为 true。
- accumulate: 指示是否累积直方图,默认为 false。
下面是一个示例代码,展示如何使用cv::calcHist()函数计算图像的直方图:
#include <opencv2/opencv.hpp> void hist(Mat image){ // 定义直方图参数 int histSize = 256; // 直方图条目数量 const int channels[1]={0};//通道索引 float range[] = { 0, 256 }; // 像素值范围 const float* histRange = { range }; bool uniform = true; // 直方图条目是否均匀分布 bool accumulate = false; // 直方图是否累积 // 计算直方图 cv::Mat hist; cv::calcHist(&image, 1, channels, cv::Mat(), hist, 1, &histSize, &histRange, uniform, accumulate); // 绘制直方图 int histWidth = 512; int histHeight = 400; int binWidth = cvRound((double)histWidth / histSize); cv::Mat histImage(histHeight, histWidth, CV_8UC4, cv::Scalar(0, 0, 0)); cv::normalize(hist, hist, 0, histImage.rows, cv::NORM_MINMAX, -1, cv::Mat()); for (int i = 1; i < histSize; ++i){ cv::line(histImage, cv::Point(binWidth * (i - 1), histHeight - cvRound(hist.at<float>(i - 1))), cv::Point(binWidth * (i), histHeight - cvRound(hist.at<float>(i))), cv::Scalar(255, 255, 255), 2, 8, 0); } // 显示直方图 cv::imwrite("/sdcard/DCIM/histImage.jpg", histImage); }
示例代码中将原图像image转换为单通道灰度图像。然后定义了直方图的参数,包括直方图条目数量、像素值范围、均匀性和累积性。接下来使用 cv::calcHist() 函数计算了图像的直方图,存储在 hist 中。最后,通过绘制直方图数据到 histImage 中,实现了直方图的可视化。
2.直方图均衡化
直方图均衡化是一种用于增强图像对比度的图像处理技术。它通过重新分布图像像素值的频率分布来增强图像的亮度和细节。
在OpenCV中,可以使用cv::equalizeHist()函数来进行直方图均衡化。该函数的原型如下:
void equalizeHist(InputArray src, OutputArray dst);
参数说明:
- src:需要直方图均衡化的CV 8UC1图像。
- dst: 直方图均衡化后的输出图像,与src具有相同尺寸和数据类型
下面是一个示例代码,展示如何使用cv::equalizeHist()函数来进行直方图均衡化:
#include <opencv2/opencv.hpp> void drawHist(Mat &hist,string name){//归一化并绘制直方图函数 int histSize = 256; // 直方图条目数量 // 绘制直方图 int histWidth = 512; int histHeight = 400; int binWidth = cvRound((double)histWidth / histSize); cv::Mat histImage(histHeight, histWidth, CV_8UC4, cv::Scalar(0, 0, 0)); cv::normalize(hist, hist, 0, histImage.rows, cv::NORM_MINMAX, -1, cv::Mat()); for (int i = 1; i < histSize; ++i) { cv::line(histImage, cv::Point(binWidth * (i - 1), histHeight - cvRound(hist.at<float>(i - 1))), cv::Point(binWidth * (i), histHeight - cvRound(hist.at<float>(i))), cv::Scalar(255, 255, 255), 2, 8, 0); } // 显示直方图 cv::imwrite("/sdcard/DCIM/"+name+".jpg", histImage); } void EqualImage(Mat image){ //灰度化 Mat gray; cvtColor(image,gray,COLOR_BGR2GRAY); //将灰度图进行直方图均衡化 Mat equalImg; equalizeHist(gray,equalImg); cv::imwrite("/sdcard/DCIM/equalImg.jpg", equalImg); // 定义直方图参数 int histSize = 256; // 直方图条目数量 const int channels[1]={0};//通道索引 float range[] = { 0, 256 }; // 像素值范围 const float* histRange = { range }; bool uniform = true; // 直方图条目是否均匀分布 bool accumulate = false; // 直方图是否累积 // 计算直方图 cv::Mat hist; cv::calcHist(&equalImg, 1, channels, cv::Mat(), hist, 1, &histSize, &histRange, uniform, accumulate); drawHist(hist,"hist1"); }
示例代码中将原图像image转换为单通道灰度图像,然后将灰度图进行直方图均衡化,之后定义了直方图的参数,包括直方图条目数量、像素值范围、均匀性和累积性。接下来使用 cv::calcHist() 函数计算了图像的直方图,存储在 hist 中。最后,通过绘制直方图数据到 histImage 中,实现了直方图的可视化。
3.直方图匹配
直方图匹配(Histogram Matching)是一种图像处理技术,用于将一副图像的直方图映射到另一副图像上,从而使它们的亮度分布或颜色分布相似。该技术常用于图像增强、风格转换、颜色校正等应用中。
以下是一个使用OpenCV实现直方图匹配的示例代码:
#include <opencv2/opencv.hpp> #include <iostream> using namespace cv; using namespace std; void drawHist(Mat &hist,string name){//归一化并绘制直方图函数 int histSize = 256; // 直方图条目数量 // 绘制直方图 int histWidth = 512; int histHeight = 400; int binWidth = cvRound((double)histWidth / histSize); cv::Mat histImage(histHeight, histWidth, CV_8UC4, cv::Scalar(0, 0, 0)); cv::normalize(hist, hist, 0, histImage.rows, cv::NORM_MINMAX, -1, cv::Mat()); for (int i = 1; i < histSize; ++i) { cv::line(histImage, cv::Point(binWidth * (i - 1), histHeight - cvRound(hist.at<float>(i - 1))), cv::Point(binWidth * (i), histHeight - cvRound(hist.at<float>(i))), cv::Scalar(255, 255, 255), 2, 8, 0); } // 显示直方图 cv::imwrite("/sdcard/DCIM/"+name+".jpg", histImage); } void Histogram_matching(Mat img1,Mat img2){ Mat hist1,hist2; //计算两张图像直方图 const int channels[1]={0}; float inRanges[2]={0,255}; const float *ranges[1]={inRanges}; const int bins[1]={256}; calcHist(&img1,1,channels,Mat(),hist1,1,bins,ranges); calcHist(&img2,1,channels,Mat(),hist2,1,bins,ranges); //归一化两张图像的直方图 drawHist(hist1,"hist1"); drawHist(hist2,"hist2"); //计算两张图像直方图的累计概率 float hist1_cdf[256]={hist1.at<float>(0)}; float hist2_cdf[256]={hist2.at<float>(0)}; for(int i=1;i<256;i++){ hist1_cdf[i]=hist1_cdf[i-1]+hist1.at<float>(i); hist2_cdf[i]=hist2_cdf[i-1]+hist1.at<float>(i); } //构建累积概率误差矩阵 float diff_cdf[256][256]; for(int i=0; i<256; i++){ for(int j=0; j<256; j++){ diff_cdf[i][j] = fabs(hist1_cdf[i] - hist2_cdf[j]); } } uchar lutone[256]; for(int i=0;i<256;i++){ //查找源灰度级为i的映射灰度 //和i的累积概率差值最小的规定化灰度 float min=diff_cdf[i][0]; int index=0; //寻找累积概率误差矩阵中每一行中的最小值 for(int j=1;j<256;j++){ if(min>diff_cdf[i][j]){ min=diff_cdf[i][j]; index=j; } } lutone[i]=index; } //生成LUT映射表 Mat lut(1,256,CV_8UC1,lutone); Mat result,hist3; LUT(img1,lut,result); imwrite("/sdcard/DCIM/result.png",result); calcHist(&result,1,channels,Mat(),hist3,1,bins,ranges); drawHist(hist3,"hist3"); }
示例代码:计算原始图像和目标图像的直方图,归一化直方图,计算累计直方图,构建累积概率误差矩阵,根据最小差值构建映射表,最后将原始图像的灰度级根据映射表调整为目标图像的灰度级。下面是原始图像和直方图匹配后图片,可以看出直方图匹配后的图片使得图像中的细节更加清晰可见。
原图 直方图匹配的结果