本篇中主要介绍数字图像处理中直方图的应用以及编程实现
1.显示图像的灰度直方图
OpenCV中提供了一个简单的计算数组集(通常上是图像或是分割后的通道)的直方图函数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 表示输入图像的源指针,图像源必须是同样的深度信息,可以使CV_8U或CV_32U,可以有任意的通道数
第二个参数nimages是表示图像源中图像的个数,用于单幅图像计算直方图时通常这个参数的值为1
第三个参数channels表示的是需要统计图像的通道维数数组索引,第一个数组的通道由0到arrays(0).channels()-1,第二个数组的通道由arrays(0).channels()到arrays(0).channels() + arrays(1).channels()-1,以此类推
第四个参数mask表示的是可选掩码参数。如果矩阵不是空矩阵,那么这个掩码参数必须是8bit且需要和图像源同样尺寸,掩码的非零标记需要在直方图中统计数组元素,默认设置为空图像。
第五个参数hist表示输出计算得到的直方图,通常是一个稠密或稀疏的多维数组
第六个参数dims表示输出直方图的维数,且这个参数必须是正数,需要注意的是,在OpenCV中最大的维数是32维,常用的灰度图像是1维,彩色图像是3维。
第七个参数histSize表示的是直方图横坐标的区间数,用来指出直方图数组每一维大小的数组,也就是指出每一维bin的个数的数组,如果是5,则它会将横坐标分为5份,然后统计每个区间的像素点总和
第八个参数ranges表示用于指出直方图每一维的每个bin的上下界范围数组的数组,对于均匀直方图,参数ranges是一个包含两个元素的数组
第九个参数uniform是用来指出直方图是否均匀的标志,有默认参数true
第十个参数accumulate是累计标志,用于设置是否清除开始分配时的直方图累计标志。
具体程序如下:
//显示一副图像的直方图
#include <iostream>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat Image = imread("2345.jpg");
Mat grayImage(Image.size(), Image.type());
if (!Image.data)
{
cout << "读入图片有误!" << endl;
return -1;
}
imshow("原图像", Image);
//将图像变为灰度图像
cvtColor(Image, grayImage, CV_BGR2GRAY);
//定义直方图参数
const int channels[1] = { 0 };
const int histSize[2] = { 256 };
float prange[2] = { 0, 255 };
const float* ranges[1] = { prange };
MatND hist;
//计算直方图
calcHist(&grayImage, 1, channels, Mat(), hist, 1, histSize, ranges);
//初始化画布参数
int hist_w = 500;
int hist_h = 500;
int nHistSize = 255;
//区间
int bin_w = cvRound((double)hist_w / nHistSize);
Mat histImage(hist_w, hist_h, CV_8UC3, Scalar(0, 0, 0));
//将直方图归一化到[0,histImage.rows]
normalize(hist, hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
//在直方图画布上画出直方图
for (int i = 1; i < nHistSize; i++)
{
line(histImage, Point(bin_w*(i - 1),
hist_h-cvRound(hist.at<float>(i-1))),
Point(bin_w*(i),
hist_h-cvRound(hist.at<float>(i))),
Scalar(0,0,255),2,8,0);
}
//显示直方图
imshow("histImage", histImage);
waitKey();
return 0;
}
执行后的效果如下:
2.显示图像的H-S直方图
为了描述图像中颜色的直观特性,常常需要分析图像在HSV空间下的直方图特性。
HSL和HSV都是一种将RGB色彩模型中的点在圆柱坐标系中的表示法。这两种表示法试图做到比RGB基于笛卡尔坐标系的几何结构更加直观。
HSV即色相、饱和度、明度(英语:Hue, Saturation, Value),又称HSB,其中B即英语:Brightness。
因此在分析图像的H-S直方图时,需要先将源RGB的图像转换到HSV颜色空间内,然后再将对应的H和S通道进行单元划分,再其二维空间上计算对应的直方图,最后再次通过计算直方图空间上的最大值,归一化绘制相应的直方图信息。
H-S直方图通常是应用在目标检测、特征分析以及目标特征跟踪等场景中。
下面是相关的代码:
//用来显示H-S直方图
#include <iostream>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat srcImage = imread("2345.jpg");
if (!srcImage.data)
{
cout << "读入图片失败!" << endl;
return -1;
}
Mat hsvImage;
//将图像转换到HSV空间
cvtColor(srcImage, hsvImage, CV_BGR2HSV);
//初始化灰度阶参数
int hbins = 30, sbins = 32;
int histSize[] = { hbins, sbins };
//灰度变化范围设置
float hranges[] = { 0, 180 };
//饱和度变化范围
float sranges[] = { 0, 256 };
const float *ranges[] = { hranges, sranges };
MatND hist;
//选取计算直方图通道
int channels[] = { 0, 1 };
//计算当前通道直方图
calcHist(&hsvImage, 1, channels, Mat(), hist, 2, histSize, ranges, true, false);
double maxValue;
//找到直方图的最大值
minMaxLoc(hist, 0, &maxValue, 0, 0);
int scale = 10;
Mat histImage = Mat::zeros(sbins*scale, hbins * 10, CV_8UC3);
//遍历H和S通道
for (int h = 0; h < hbins; h++)
{
for (int s = 0; s < sbins; s++)
{
float binVal = hist.at<float>(h, s);
//根据最大值计算变换范围
int intensity = cvRound(binVal * 255 / maxValue);
//进行绘图
rectangle(histImage,
Point(h*scale, s*scale),
Point((h + 1)*scale - 1, (s + 1)*scale - 1),
Scalar::all(intensity),
CV_FILLED);
}
}
imshow("原图像", srcImage);
imshow("H-S直方图", histImage);
waitKey();
return 0;
}
先介绍一下用到的一个函数,也就是下面这个语句中用到的
minMaxLoc(hist, 0, &maxValue, 0, 0);
函数原型为:
void minMaxLoc(InputArray src, CV_OUT double* minVal,CV_OUT double* maxVal=0,
CV_OUT Point* minLoc=0,CV_OUT Point* maxLoc=0, InputArray mask=noArray());
· minMaxLoc寻找矩阵(一维数组当作向量,用Mat定义) 中最小值和最大值的位置.
·参数若不需要,则置为NULL或者0,即可.
·minMaxLoc针对Mat和MatND的重载中 ,第5个参数是可选的(optional),不使用不传递即可.
执行程序后,输出结果为:
3.BGR直方图
在OpenCV中,关于彩色图像的直方图是通过多通道数组来实现的,对于CV_8UF3来说,其中每个数组通道中的元素可以取值为0到255,因此对于彩色图像求它的直方图来说,可以先进行提取彩色图像中的各个通道,然后对每个通道进行直方图计算,最后利用图像融合技术合并通道信息,求解出图像颜色分布直方图。
需要注意的是,颜色分布直方图描述的是不同色彩在整幅图像中所占的比例,而不关心每种色彩所处的空间位置。
相关的代码如下:
//计算并显示彩色图像的BGR直方图
#include <vector>
#include <iostream>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
using namespace cv;
using namespace std;
int main()
{
//图像的获取
Mat srcImage = imread("2345.jpg");
if (!srcImage.data)
{
cout << "读入图片失败!" << endl;
return -1;
}
imshow("原图像", srcImage);
//分离图像的3个通道BGR
vector<Mat> bgr_channels;
split(srcImage, bgr_channels);
//初始化直方图计算参数
int histSize = 256;
float range[] = { 0, 256 };
const float*histRange = { range };
bool uniform = true;
bool accumulate = false;
Mat b_hist, g_hist, r_hist;
//计算各个通道的直方图
calcHist(&bgr_channels[0], 1, 0, Mat(), b_hist,
1, &histSize, &histRange, uniform, accumulate);
calcHist(&bgr_channels[1], 1, 0, Mat(), b_hist,
1, &histSize, &histRange, uniform, accumulate);
calcHist(&bgr_channels[1], 1, 0, Mat(), b_hist,
1, &histSize, &histRange, uniform, accumulate);
//设置直方图绘图参数
int hist_w = 640;
int hist_h = 512;
int bin_w = cvRound((double)hist_w / histSize);
Mat histImage(hist_h, hist_w, CV_8UC3, Scalar(0, 0, 0));
//分别归一化到直方图[0,histImage.rows]
normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1);
normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1);
normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1);
//分别对每个通道进行绘图
for (int i = 0; i < histSize; i++)
{
//绘制B通道的直方图信息
line(histImage, Point(bin_w*(i - 1),
hist_h - cvRound(b_hist.at<float>(i - 1))),
Point(bin_w*(i), hist_h - cvRound(b_hist.at<float>(i))),
Scalar(255, 0, 0), 2, 8, 0);
//绘制G通道的直方图信息
line(histImage, Point(bin_w*(i - 1),
hist_h - cvRound(g_hist.at<float>(i - 1))),
Point(bin_w*(i), hist_h - cvRound(g_hist.at<float>(i))),
Scalar(255, 0, 0), 2, 8, 0);
//绘制R通道的直方图信息
line(histImage, Point(bin_w*(i - 1),
hist_h - cvRound(r_hist.at<float>(i - 1))),
Point(bin_w*(i), hist_h - cvRound(r_hist.at<float>(i))),
Scalar(255, 0, 0), 2, 8, 0);
}
imshow("calcHist", histImage);
waitKey();
return 0;
}
4.灰度直方图均衡
直方图均衡化算法大致步骤:
(1).获取输入图像的直方图
(2).求累积分布直方图,构建查找表
(3).通过图像映射,计算新的图像像素分布
使用OpenCV的具体程序如下:
//对灰度图像进行直方图均衡
#include <iostream>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat srcImage = imread("2345.jpg");
if (!srcImage.data)
{
cout << "读入图片有误!" << endl;
return -1;
}
Mat grayImage(srcImage.size(), CV_8UC1);
cvtColor(srcImage, grayImage, CV_BGR2GRAY);
imshow("灰度图像", grayImage);
//进行直方图均衡化
Mat heqResult(grayImage.size(),grayImage.type());
equalizeHist(grayImage, heqResult);
imshow("直方图均衡化后的图像", heqResult);
waitKey();
return 0;
}
其中
equalizeHist(grayImage, heqResult);
即为直方图均衡函数,函数的表示十分简单,第一个参数表示输入的灰度图像,第二个参数表示进行直方图均衡化后的结果
执行程序后,程序的输出为:
可以看出,进行直方图均衡化后的图像对比度明显增强。
5.彩色直方图均衡
彩色直方图均衡化可以实现均衡各个通道动态范围内的所有特征,变换的原理是以每个通道下的累积分布函数变换为基础,从而实现RGB颜色空间下的图像细节增强。
因此,彩色图像直方图就是将源图像从RGB颜色空间分类成单个通道,然后在每个通道上进行相应直方图均衡,最后再将每个通道下的直方图均衡的结果进行合并,最后需要说明的是彩色图像的直方图均衡化常常用于图像增强,自动白平衡和伪彩色图像处理等领域。
下面使用OpenCV中的相关函数进行实现
//对彩色图像进行直方图均衡化
#include <vector>
#include <iostream>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat srcImage = imread("2345.jpg");
if (!srcImage.data)
{
cout << "读入图片有误!" << endl;
return -1;
}
imshow("原图像", srcImage);
//存储彩色图像直方图及图像通道向量
Mat heqImage(srcImage.size(), srcImage.type());
vector<Mat> BGR_plane;
//对BGR通道进行分离
split(srcImage, BGR_plane);
//分别对三个通道进行直方图均衡化
for (auto i = 0; i < BGR_plane.size(); i++)
equalizeHist(BGR_plane[i], BGR_plane[i]);
//合并对应的各个通道
merge(BGR_plane, heqImage);
imshow("直方图均衡化后的图像", heqImage);
waitKey();
return 0;
}
主要用到的函数是直方图均衡函数还有通道分离函数split()和通道合并函数merge(),因为后两个函数比较容易,在这里不再进一步介绍。
执行程序后,图像输出为:
6.直方图的查找变换
上一篇中已经说到,直方图变换是通过改变和调整图像的直方图形状来实现改变图像灰度阶分布与结构的增强方法。图像直方图反映图像对比度、明暗细节等特征,如果一幅图像的直方图集中分布在某一个区间内,则整体画面呈现出来彩色单一,不利于观察分析。
常用的方法是通过灰度变换s=T(r)=255*(r-a)/(b-a)从而将[a,b]映射到[0,255]来进行实现的。
此外,根据直方图均衡的原理,为了尽可能使得图像的像素点分布均匀,可以通过累积平均分配的方式计算出均衡化的像素。
直方图变换查找方法的大致思路:
(1).将原图像转为灰度图像,计算图像的灰度直方图
(2).根据预设的阈值参数由低到高查找iLow,再由高到底查找iHigh
(3).根据上一步得到的直方图iLow和iHigh并进行查找表变换
(4).通过查找表进行映射变换,完成直方图查找方法变换
利用OpenCV进行相关实现的程序如下:
//进行直方图变换 ---- 查找
#include <iostream>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat srcImage = imread("2345.jpg");
if (!srcImage.data)
{
cout << "读入图片错误!" << endl;
return -1;
}
//转换为灰度图像
Mat grayImage;
cvtColor(srcImage, grayImage,CV_BGR2GRAY);
//第一步 计算灰度图像的直方图
const int channels[1] = { 0 };
const int histSize[1] = { 256 };
float hranges[2] = { 0, 255 };
const float *ranges[1] = { hranges };
MatND hist;
calcHist(&grayImage, 1, channels, Mat(), hist,
1, histSize, ranges);
//第二步 根据预设参数统计灰度级变换
int segThreshild = 50; //预设参数
//由低到高进行查找
int iLow = 0;
for (; iLow < histSize[0]; iLow++)
{
if (hist.at<float>(iLow)>segThreshild)
break;
}
//由高到底进行查找
int iHigh = histSize[0] - 1;
for (; iHigh >= 0; iHigh--)
{
if (hist.at<float>(iHigh) > segThreshild)
break;
}
//第三步 建立查找表
Mat lookUpTable(Size(1, 256), CV_8U);
for (int i = 0; i < 256; i++)
{
if (i < iLow)
{
lookUpTable.at<uchar>(i) = 0;
}
else if (i>iHigh)
{
lookUpTable.at<uchar>(i) = 255;
}
else
{
lookUpTable.at<uchar>(i) = static_cast<uchar>(
255.0*(i - iLow) / (iHigh - iLow + 0.5));
}
}
//第四步,通过查找表进行映射变换
Mat histTransResult;
LUT(grayImage, lookUpTable, histTransResult);
//显示图像
imshow("灰度图像", grayImage);
imshow("直方图查找图像", histTransResult);
waitKey();
return 0;
}
其中用到了OpenCV中的查找表函数LUT,函数原型为:
void LUT(InputArray src, InputArray lut, OutputArray dst, int interpolation=0);
使用查找表使得像素之间不必计算,只需要简单的存取即可,大大加快程序运行时间。
这个函数的具体使用方法因为篇幅较长,不宜在这里喧宾夺主,将会在另一篇中单独给出。
最后程序的运行结果如下:
中篇 完
by 大幕