在opencv中,可以使用
cv::calcHist()
函数来计算图像的直方图,这个函数可以接受一个通道的图像(灰度图像)或多个通道的图像(彩色图像);
void cv::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:需要统计直方图的第几通道,比如{0}表示统计第一个通道,{0, 1, 2}表示统计所有三个通道;
mask:掩膜,用于指定计算直方图的区域(必须是一个8位(CV_8U)的数组并且和images的数组大小相同);
hist:输出的直方图数组,calcHist函数只是计算直方图的数据,直方图数据需要一个cv::Mat类型的变量来存储;
dims:输出直方图的维度(
由channels值决定dims的数值,比如int channels[] = { 0 }则dims=1,int channels[] = { 0, 1 }则dims=2;
对于灰度图像dims为1,因为我们只考虑了一个通道(亮度通道);
对于彩色图像,通常会考虑多个通道,比如在HSV色彩空间中,dims为2(H和S两个通道)
);
histSize:指的是直方图横坐标分成多少个区间,就是bin的个数(用于控制直方图的精细度);
把直方图横坐标ranges分成histSize个区间(比如ranges=180,histSize=30,则横坐标被分成了30个小竖条,每个小竖条的长度为6);
ranges:横轴代表像素的灰度级别,ranges相当于横坐标的取值范围(
对于灰度图像,单通道只有1个range,灰度级别的范围从0到255(
int bins = 256; // X轴被分成了256个小区间
int histSize[] = { bins }; // histSize只有1个bins
float xRanges[] = { 0, 256 }; // X轴的取值范围
const float* ranges[] = { xRanges }; // ranges只有1个
);
对于彩色图像有多个通道就有多个range,对于每个通道,ranges指定了取值范围,通常在彩色图像中,H(色相)的范围是0到180,S(饱和度)和V(明度)的范围是0到255(
// H通道X轴被分成了30个小区间,S通道X轴被分成了32个小区间
int hueBins = 30, satBins = 32;
int histSize[] = { hueBins, satBins }; // histSize有2个bins
float hueRanges[] = { 0, 180 }; // H通道X轴的取值范围
float satRanges[] = { 0, 256 }; // S通道X轴的取值范围
const float* ranges[] = { hueRanges, satRanges }; // ranges有2个
);
);
uniform:是否对得到的直方图数组进行归一化处理;
accumulate:在多个图像时,是否累积计算像素值的个数;
// 函数在图像处理过程中经常用于处理像素值,特别是当需要将浮点数转换为整数时,可以保留最接近的整数值
int cvRound(double value)
参数解释:
value:待四舍五入的浮点数;
#include
#include
#include
using namespace cv;
using namespace std;
int main() {
// 读取灰度图像
cv::Mat image = cv::imread("C:\\cpp\\image\\suzy2.jpg", cv::IMREAD_GRAYSCALE);
if (image.empty()) {
std::cerr << "Error: 无法读取图像文件." << std::endl;
return -1;
}
// 直方图计算的输出值,是一个cv::Mat对象
cv::Mat hist;
// X轴被分成了256个小区间
int bins = 256;
// 灰度图只有1个ranges,所以histSize跟要ranges对应,定义成1个
int histSize[] = { bins };
// X轴的取值范围
float xRanges[] = { 0, 256 };
// 在C++中,数组名本身就代表该数组的首地址,无需使用取地址符号&,
// const float* ranges = xRanges;
const float* ranges[] = { xRanges };
// 我们要计算灰度图像的第0个通道,所以这里channels定义成{0}
int channels[] = { 0 };
calcHist(&image, 1, channels, Mat(), hist, 1, histSize, ranges, true, false);
// 待绘制的目标图像,将直方图计算的输出值hist,绘制到histImage图像上
int histWidth = 512, histHeight = 400;
int binWidth = cvRound( (double)histWidth/bins );
cv::Mat histImage(histHeight, histWidth, CV_8UC3, cv::Scalar(0, 0, 0));
// 使用normalize()函数将直方图的值缩放到图像的高度范围内
normalize(hist, hist, 0, histImage.rows, cv::NORM_MINMAX, -1, cv::Mat());
// 使用cv::line()函数绘制直线
for (int i = 1; i<bins; i++) {
cv::line(
histImage,
Point(binWidth*(i - 1), histHeight - cvRound(hist.at<float>(i - 1))),
Point(binWidth*(i), histHeight - cvRound(hist.at<float>(i))),
Scalar(255, 0, 0),
2,
LINE_8,
0
);
}
// 显示图像和直方图
cv::imshow("Image", image);
cv::imshow("Histogram", histImage);
waitKey();
return 0;
}
对于彩色图像,你可以分别计算其颜色通道(蓝色、绿色、红色)的直方图,或者将其转换为灰度图像后计算整体的亮度直方图;
在这个示例中,我们首先读取了一个彩色图像,然后使用 cv::split()
函数将图像分离成蓝色、绿色和红色通道,接着,我们分别计算了每个通道的直方图,最后我们绘制了各个通道的直方图并显示了原始图像和直方图;
#include
#include
#include
using namespace cv;
using namespace std;
int main() {
// 读取彩色图像
cv::Mat image = cv::imread("C:\\cpp\\image\\suzy2.jpg");
if (image.empty()) {
std::cerr << "Error: 无法读取图像文件." << std::endl;
return -1;
}
// 分离通道
std::vector<cv::Mat> bgrPlanes;
cv::split(image, bgrPlanes);
// 直方图计算的输出值,是一个cv::Mat对象
cv::Mat bHist, gHist, rHist;
int bins = 256; // B,G,R通道,X轴都被分成了256个小区间
// B通道直方图计算
// B通道,灰度图只有1个ranges,所以histSize跟要ranges对应,定义成1个
int bHistSize[] = { bins };
// B通道,X轴的取值范围,亮度级别
float bXRanges[] = { 0, 256 };
// 在C++中,数组名本身就代表该数组的首地址,无需使用取地址符号&,
// const float* ranges = bXRanges;
const float* bRanges[] = { bXRanges };
// 图像通道被分离后,B通道图像只有一个通道,第0个通道,所以这里channels定义成{0}
int bChannels[] = { 0 };
calcHist(&bgrPlanes[0], 1, bChannels, Mat(), bHist, 1, bHistSize, bRanges, true, false);
// G通道直方图计算
// G通道,灰度图只有1个ranges,所以histSize跟要ranges对应,定义成1个
int gHistSize[] = { bins };
// G通道,X轴的取值范围,亮度级别
float gXRanges[] = { 0, 256 };
// 在C++中,数组名本身就代表该数组的首地址,无需使用取地址符号&,
// const float* ranges = gXRanges;
const float* gRanges[] = { gXRanges };
// 图像通道被分离后,G通道图像只有一个通道,第0个通道,所以这里channels定义成{0}
int gChannels[] = { 0 };
calcHist(&bgrPlanes[1], 1, gChannels, Mat(), gHist, 1, gHistSize, gRanges, true, false);
// R通道直方图计算
// R通道,灰度图只有1个ranges,所以histSize跟要ranges对应,定义成1个
int rHistSize[] = { bins };
// R通道,X轴的取值范围,亮度级别
float rXRanges[] = { 0, 256 };
// 在C++中,数组名本身就代表该数组的首地址,无需使用取地址符号&,
// const float* ranges = rXRanges;
const float* rRanges[] = { rXRanges };
// 图像通道被分离后,R通道图像只有一个通道,第0个通道,所以这里channels定义成{0}
int rChannels[] = { 0 };
calcHist(&bgrPlanes[2], 1, rChannels, Mat(), rHist, 1, rHistSize, rRanges, true, false);
// 绘制直方图
int histWidth = 512, histHeight = 400;
int binWidth = cvRound( (double)histWidth/bins );
cv::Mat histImage(histHeight, histWidth, CV_8UC3, cv::Scalar(0, 0, 0));
normalize(bHist, bHist, 0, histImage.rows, cv::NORM_MINMAX, -1, cv::Mat());
normalize(gHist, gHist, 0, histImage.rows, cv::NORM_MINMAX, -1, cv::Mat());
normalize(rHist, rHist, 0, histImage.rows, cv::NORM_MINMAX, -1, cv::Mat());
for (int i = 1; i<bins; i++) {
// 绘制蓝色分量直方图
cv::line(
histImage,
cv::Point( binWidth*(i-1), histHeight-cvRound( bHist.at<float>(i-1) ) ),
cv::Point( binWidth*(i), histHeight-cvRound( bHist.at<float>(i) ) ),
cv::Scalar(255, 0, 0),
2,
LINE_8,
0
);
// 绘制绿色分量直方图
cv::line(
histImage,
cv::Point( binWidth*(i - 1), histHeight-cvRound( gHist.at<float>(i-1) ) ),
cv::Point( binWidth*(i), histHeight-cvRound( gHist.at<float>(i) ) ),
cv::Scalar(0, 255, 0),
2,
LINE_8,
0
);
// 绘制红色分量直方图
cv::line(
histImage,
cv::Point( binWidth*(i - 1), histHeight-cvRound( rHist.at<float>(i-1) ) ),
cv::Point( binWidth*(i), histHeight-cvRound( rHist.at<float>(i) ) ),
cv::Scalar(0, 0, 255),
2,
LINE_8,
0
);
}
// 显示图像和直方图
cv::imshow("Image", image);
cv::imshow("Histogram", histImage);
cv::waitKey(0);
return 0;
}
二维直方图是对彩色图像进行分析时的一个重要工具,与一维直方图不同,它同时考虑了两个通道的信息,通常是颜色空间中的两个分量,例如在HSV色彩空间中的H(色相)和S(饱和度);
首先读取了一个彩色图像,然后将其从BGR颜色空间转换为HSV颜色空间,接着我们分离了H和S通道,并指定了直方图的维度、通道数、亮度级别个数和范围,然后我们使用 cv::calcHist()
计算了二维直方图并使用 cv::rectangle()
绘制;
#include
#include
using namespace cv;
using namespace std;
int main() {
// 读取彩色图像
cv::Mat image = cv::imread("C:\\cpp\\image\\suzy2.jpg");
if (image.empty()) {
std::cerr << "Error: 无法读取图像文件." << std::endl;
return -1;
}
// 2D 直方图
Mat hsv, hist;
cvtColor(image, hsv, COLOR_BGR2HSV);
// H通道X轴被分成了30个小区间,S通道X轴被分成了32个小区间
int hueBins = 30, satBins = 32;
// 彩色图像有多个ranges,所以histSize跟ranges对应也要定义多个,定义成数组的形式
int histSize[] = { hueBins, satBins };
float hueRanges[] = { 0, 180 }; // H通道X轴的取值范围
float satRanges[] = { 0, 256 }; // S通道X轴的取值范围
// 彩色图像有多个ranges,定义成数组的形式
const float* ranges[] = { hueRanges, satRanges };
// 我们要计算HSV图像的第0个通道和第1个通道的直方图,所以这里channels也要定义多个
int channels[] = { 0, 1 };
calcHist(&hsv, 1, channels, Mat(), hist, 2, histSize, ranges, true, false);
// 画出计算后的直方图
double maxVal = 0;
minMaxLoc(hist, 0, &maxVal, 0, 0);
int scale = 10;
Mat histImage = Mat::zeros(satBins*scale, hueBins*scale, CV_8UC3);
for (int h=0; h<hueBins; h++) {
for (int s=0; s<satBins; s++){
float binVal = hist.at<float>(h, s);
int intensity = cvRound( binVal*255 / maxVal );
rectangle(
histImage,
Point( h*scale, s*scale ),
Point( (h+1)*scale - 1, (s+1)*scale - 1 ),
Scalar::all(intensity),
-1
);
}
}
applyColorMap(histImage, histImage, COLORMAP_JET);
imshow("Image", image);
imshow("H-S Histogram", histImage);
cv::waitKey(0);
return 0;
}
直方图均衡化是一种用于增强图像对比度的图像处理技术,它通过重新分配图像的像素值,使得整个亮度范围得到充分利用,从而使图像看起来更清晰和具有更好的对比度;
cv::equalizeHist()
函数来实现直方图均衡化:void cv::equalizeHist(
InputArray src,
OutputArray dst
);
参数解释:
src:输入图像(灰度图像);
dst:输出图像,用于存储均衡化后的结果;
#include
#include
using namespace cv;
using namespace std;
int main() {
// 读取灰度图像
cv::Mat image = cv::imread("C:\\cpp\\image\\suzy2.jpg", cv::IMREAD_GRAYSCALE);
if (image.empty()) {
std::cerr << "Error: 无法读取图像文件." << std::endl;
return -1;
}
// 均衡化灰度直方图
cv::Mat equalizedImage;
cv::equalizeHist(image, equalizedImage);
cv::imshow("Original Gray Image", grayImage);
cv::imshow("Equalized Gray Image", equalizedImage);
cv::waitKey(0);
return 0;
}
首先将彩色图像转换为HSV色彩空间,然后对亮度(Value/V通道)进行均衡化,最后将其转换回BGR色彩空间以显示
#include
#include
using namespace cv;
using namespace std;
int main() {
// 读取彩色图像
cv::Mat image = cv::imread("C:\\cpp\\image\\suzy2.jpg", cv::IMREAD_COLOR);
if (image.empty()) {
std::cerr << "Error: 无法读取图像文件." << std::endl;
return -1;
}
// 将图像从BGR色彩空间转换为HSV色彩空间
cv::Mat hsvImage;
cv::cvtColor(image, hsvImage, cv::COLOR_BGR2HSV);
// 均衡化HSV通道的直方图
std::vector<cv::Mat> channels;
cv::split(hsvImage, channels);
cv::equalizeHist(channels[2], channels[2]);
cv::Mat equalizedHSV;
cv::merge(channels, equalizedHSV);
cv::cvtColor(equalizedHSV, equalizedHSV, cv::COLOR_HSV2BGR);
cv::imshow("Original Color Image", image);
cv::imshow("Equalized Color Image", equalizedHSV);
cv::waitKey(0);
return 0;
}
运行结果: