一些头文件:
#include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <iostream> using namespace std; using namespace cv;
首先建立一个类:
class Histogram1D { private: //直方图的点数 int histSize[1]; //直方图的范围 float hranges[2]; //指向该范围的指针 const float* ranges[1]; //通道 int channels[1]; public: //构造函数 Histogram1D() { histSize[0] = 256; hranges[0] = 0.0; hranges[1] = 255.0; ranges[0] = hranges; channels[0] = 0; }
在OpenCV中,使用calcHist计算直方图
Mat getHistogram(const Mat &image) { Mat hist; //计算直方图函数 //参数为:源图像(序列)地址,输入图像的个数,通道数,掩码,输出结果,直方图维数,每一维的大小,每一维的取值范围 calcHist(&image,1,channels,Mat(),hist,1,histSize,ranges); return hist; }
但是,返回的结果并不是我们希望的“直方图”,hist只是记录了每个bin下的像素个数。我们需要自己利用line函数画图:
Mat getHistogramImage(const Mat &image) { //首先计算直方图 Mat hist = getHistogram(image); //获取最大值和最小值 double maxVal = 0; double minVal = 0; //minMaxLoc用来获得最大值和最小值,后面两个参数为最小值和最大值的位置,0代表不需要获取 minMaxLoc(hist,&minVal,&maxVal,0,0); //展示直方图的画板:底色为白色 Mat histImg(histSize[0],histSize[0],CV_8U,Scalar(255)); //将最高点设为bin总数的90% int hpt = static_cast<int>(0.9*histSize[0]); //为每一个bin画一条线 for(int h = 0; h < histSize[0];h++) { float binVal = hist.at<float>(h); int intensity = static_cast<int>(binVal*hpt/maxVal); //int intensity = static_cast<int>(binVal); line(histImg,Point(h,histSize[0]),Point(h,histSize[0]-intensity),Scalar::all(0)); } return histImg; }
函数中对亮度进行了“压缩”,主要目的是让直方图能够更好的显示在画板上。
查找表,就是一个一对一或者多对一函数,指定了一个亮度经过查找表以后变成另外一个像素。在OpenCV中,使用LUT函数来实现
Mat applyLookUp(const Mat& image,const Mat& lookup) { Mat result; LUT(image,lookup,result); return result; }
而变化的具体内容,依赖于你自己定义的查找表,比如,如果你想让查找表的结果为当前图像的翻转,可以在main函数中定义查找表为:
//用查找表翻转图像灰度 int dim(256); Mat lut(1,&dim,CV_8U); for(int i = 0; i < 256; i++) { lut.at<uchar>(i) = 255-i; } imshow("灰度翻转后的图像",h.applyLookUp(image,lut));
当你想让图像经过查找表以后直方图拉伸,变得效果好一点,可以这样:
将个数低于指定数目(默认为0)的bin舍去,剩下的最小值变为0,最大值变为255,中间的部分线性拉伸:
Mat strech(const Mat &image,int minValue = 0) { //首先计算直方图 Mat hist = getHistogram(image); //左边入口 int imin = 0; for(;imin< histSize[0];imin++) { cout<<hist.at<float>(imin)<<endl; if(hist.at<float>(imin) > minValue) break; } //右边入口 int imax = histSize[0]-1; for(;imax >= 0; imax--) { if(hist.at<float>(imax) > minValue) break; } //创建查找表 int dim(256); Mat lookup(1,&dim,CV_8U); for(int i = 0; i < 256; i++) { if(i < imin) { lookup.at<uchar>(i) = 0; } else if(i > imax) { lookup.at<uchar>(i) = 255; } else { lookup.at<uchar>(i) = static_cast<uchar>(255.0*(i-imin)/(imax-imin)+0.5); } } Mat result; result = applyLookUp(image,lookup); return result; }
当然,这样的效果还不是最好的,最好的均衡应该使得直方图非常平缓,每个bin中的数目差不多,在OpenCV中,使用equalizeHist函数可以实现直方图均衡功能:
Mat equalize(const Mat &image) { Mat result; equalizeHist(image,result); return result; }
有了上面定义的类,我们main函数就变得简单多了:
#include "histogram.h" int main() { //以灰度方式读取图像 Mat image = imread("D:/picture/images/group.jpg",0); imshow("源灰度图像",image); if (!image.data) return -1; Histogram1D h; imshow("直方图",h.getHistogramImage(image)); //用查找表翻转图像灰度 int dim(256); Mat lut(1,&dim,CV_8U); for(int i = 0; i < 256; i++) { lut.at<uchar>(i) = 255-i; } imshow("灰度翻转后的图像",h.applyLookUp(image,lut)); //用查找表拉伸图像灰度值 //忽略那些个数少于100个像素的bin Mat strech = h.strech(image,100); imshow("拉伸后的图像",strech); imshow("拉伸后的直方图",h.getHistogramImage(strech)); //图像均衡 Mat afterEqualize = h.equalize(image); imshow("均衡后的解果",afterEqualize); imshow("均衡后的直方图",h.getHistogramImage(afterEqualize)); waitKey(0); return 0; }
由于画的图比较多,这里就不贴结果了。