设一副数字图像的像素总数为 N N N,灰度级为 L L L,第 k k k个灰度级的灰度等级为 r k r_k rk,共有像素 n k n_k nk个,则第 k k k个灰度级或者说 r k r_k rk出现的频率: h k = n k / N h_k=n_k/N hk=nk/N,其中 k = 0 , 1........ ( L − 1 ) k=0,1........(L-1) k=0,1........(L−1)。
以 k k k为横坐标(或 r k r_k rk为横坐标), h k h_k hk为纵坐标画出的柱状图,就成为了直方图。比如下图的直方图:
要画出直方图可以先通过opencv提供的calcHist函数来计算图像直方图。
calcHist的定义如下:
CV_EXPORTS 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:需要统计通道的索引,对于灰度图像,只有一个通道,索引就是0;
mask:可选的操作掩码,无掩膜操作填Mat();
hist:输出的目标直方图;
dims:需要计算的直方图的维数,必须是正数;
histSize:存放每个维度直方图的尺寸的数组,即直方图有多少根柱子(bins);
ranges:表示每一维数值的取值范围;
uniform:直方图是否均匀的标识符,默认为true;
accumulate:累计标识符,默认为false。
计算出图像直方图后,得到hist并不是一副图像,只是一个存放直方图数据的数组,这时候需要根据得到的hist画出其直方图即可。
其实现代码如下:
#include
#include
using namespace cv;
using namespace std;
Mat getHistogram1DImage(Mat& dstHist,int scale=1);
int main(int argc, char* argv[])
{
Mat src= imread("1.jpg",0);
if (!src.data)
{
cout << "图片加载失败" << endl;
return -1;
}
namedWindow("原图", NORM_MINMAX);
imshow("原图",src);
Mat dstHist;
int dims = 1;
float hranges[] = {
0,256 };
const float* ranges[] = {
hranges };
int bins ={
256};
int channels = {
0};
calcHist(&src,1,&channels,Mat(),dstHist,dims,&bins,ranges);
//这里得到的dstHist是一个bins*1的数组
Mat dstImg= getHistogram1DImage(dstHist);
imshow("Histogram", dstImg);
cv::waitKey(0);
return 0;
}
Mat getHistogram1DImage( Mat& dstHist,int scale ) {
int bins = dstHist.rows;
Mat dstImg(bins, bins * scale, CV_8UC3, Scalar(0));//Mat::Mat(int rows, int cols, int type, const Scalar& s)
//注意构造时时先行后列
double minValue = 0;
double maxValue = 0;
minMaxLoc(dstHist, &minValue, &maxValue, 0, 0);
double hpt = saturate_cast<double>(0.9 * bins);
for (int i = 0; i < bins; i++) {
float binValue = dstHist.at<float>(i);
int realValue = saturate_cast<int>((double)binValue * hpt / maxValue);
rectangle(dstImg, Point(i * scale, bins - 1), Point(i * scale, bins - realValue), Scalar(0, 255, 0), 1);
/*
rectangle用线段代替也可以
line(dstImg, Point(i * scale, bins - 1), Point(i * scale, bins - realValue), Scalar(0, 255, 0), 1, 8);
*/
}
return dstImg;
}
对于数字图像,,设原图像像素总数为 N N N,有 L L L个灰度级,第 k k k个灰度级灰度 r k r_k rk出现的频数为 n k n_k nk。若原图像素点在点 ( i , j ) (i,j) (i,j)的灰度为 r k r_k rk,则直方图均化处理后在点 ( i , j ) (i,j) (i,j)的灰度值的变换公式为:
其中 k = 0 , 1 , . . . . . . . . . . . . , L − 1 , h k k=0,1,............,L-1,h_k k=0,1,............,L−1,hk称作第 k k k个灰度级出现的概率。
从直方图的算法可以看出,其核心思想在于使用灰度累计概率作为变换后的输出结果,替代原图像素的灰度。
#include
#include
using namespace cv;
using namespace std;
Mat avgHistogram(Mat input);
Mat CalcGetHistogram3DImage(Mat src, int scale = 1, Color color=Color());//计算且得到直方图像
int main(int argc, char* argv[])
{
Mat src = imread("1.jpg");
if (!src.data)
{
cout << "图片加载失败" << endl;
return -1;
}
namedWindow("原图", NORM_MINMAX);
imshow("原图", src);
Mat dstImg = CalcGetHistogram3DImage(src);
imshow("原图的直方图", dstImg);
Mat dst;
dst = avgHistogram(src);
namedWindow("均化后", NORM_MINMAX);
imshow("均化后", dst);
dstImg = CalcGetHistogram3DImage(dst);
imshow("均化后的直方图", dstImg);
waitKey(0);
return 0;
}
Mat avgHistogram(Mat input ) {
double MaxGrey;//最大灰度值
minMaxLoc(input, 0, &MaxGrey);
int totalsize = input.rows * input.cols;//像素总数
double *grey=new double[MaxGrey + 1];//原始图像灰度
uchar* newgrey = new uchar[MaxGrey + 1];//变换后图像灰度
int channels = input.channels();
for (int k = 0; k < channels; k++) {
for (int i = 0; i <= MaxGrey; i++) {
grey[i] = 0;
newgrey[i] = 0;
}
for (int i = k; i < input.cols*channels; i=i+channels) {
for (int j = 0; j < input.rows; j++) {
grey[input.at<uchar>(j, i)]++;//统计各灰度包含的像素总数
}
}
for (int i = 0; i <= MaxGrey; i++) {
grey[i] /= totalsize;//提前归一化,免得数据过大后面操作出现溢出
}
for (int i = 1/*注意初始,防止越界*/; i <= MaxGrey; i++) {
grey[i] += grey[i - 1];//计算累积概率
}
for (int i = 0; i <= MaxGrey; i++) {
newgrey[i] = (uchar)(grey[i] * MaxGrey);
}
for (int i = k; i < input.cols*channels; i=i+channels) {
for (int j = 0; j < input.rows; j++) {
input.at<uchar>(j, i) = newgrey[input.at<uchar>(j, i)];//灰度替换
}
}
}
return input;
}
Mat CalcGetHistogram3DImage(Mat src, int scale, Color color ) {
Mat dstHist;
int dims = 1;
float hranges[] = {
0,256 };
const float* ranges[] = {
hranges };
int bins = {
256 };
int channels = src.channels();
Mat dstImg(bins,bins*scale*channels, CV_8UC3, Scalar(0));
for (int channelsIndex = 0; channelsIndex < channels; channelsIndex++) {
calcHist(&src, 1, &channelsIndex, Mat(), dstHist, dims, &bins, ranges);
//这里得到的dstHist是一个bins*1的数组
double minValue = 0;
double maxValue = 0;
minMaxLoc(dstHist, &minValue, &maxValue, 0, 0);
double hpt = saturate_cast<double>(0.9 * bins);
for (int i = channelsIndex * bins; i < bins + channelsIndex * bins; i++) {
float binValue = dstHist.at<float>(i-channelsIndex*bins);
int realValue = saturate_cast<int>((double)binValue * hpt / maxValue);
rectangle(dstImg, Point(i * scale, bins - 1), Point(i * scale, bins - realValue), color.geti(channelsIndex), 1);
}
}
return dstImg;
}
直接通过calcHist计算出的直方图数据进行均化处理
代码如下:
#include
#include
using namespace cv;
using namespace std;
class Color {
private:
Scalar c1;//颜色分量1
Scalar c2;//颜色分量2
Scalar c3;//颜色分量3
public:
Color(Scalar c1 = Scalar(255, 0, 0),
Scalar c2 = Scalar(0, 255, 0),
Scalar c3 = Scalar(0, 0, 255)) {
this->c1 = c1;
this->c2 = c2;
this->c3 = c3;
}
Scalar geti(int i) {
if (i == 0) return c1;
else if (i == 1)return c2;
else if (i == 2) return c3;
else {
cout << "Color::get(int i)参数错误" << endl;
}
}
};
Mat avgHistogram1(Mat input);
Mat CalcGetHistogram3DImage(Mat src, int scale = 1, Color color=Color());//计算且得到直方图像
int main(int argc, char* argv[])
{
Mat src = imread("1.jpg");
if (!src.data)
{
cout << "图片加载失败" << endl;
return -1;
}
namedWindow("原图", NORM_MINMAX);
imshow("原图", src);
Mat dstImg = CalcGetHistogram3DImage(src);
imshow("原图的直方图", dstImg);
Mat dst;
dst = avgHistogram1(src);
namedWindow("均化后", NORM_MINMAX);
imshow("均化后", dst);
dstImg = CalcGetHistogram3DImage(dst);
imshow("均化后的直方图", dstImg);
waitKey(0);
return 0;
}
Mat avgHistogram1(Mat input) {
//另一种直方图均化的方法
Mat Hist;
const int channels = input.channels();
float hranges[] = {
0,256 };
const float* ranges[] = {
hranges };
int bins = {
256 };
int dims = 1;
for (int channelsIndex = 0; channelsIndex < channels; channelsIndex++) {
calcHist(&input, 1, &channelsIndex, Mat(), Hist, dims, &bins, ranges);
int MaxGrey = 255;
double* newgrey = new double[MaxGrey+1];
int totalSize = input.cols * input.rows;
for (int i = 0; i <= MaxGrey; i++) {
newgrey[i] = 0;
for (int j = 0; j <= i; j++) {
newgrey[i] += (double)Hist.at<float>(j)*MaxGrey/totalSize;
//我这里Hist.at不是float就会出现异常,不知道怎么回事,因为这个异常让我停滞许久
}
}
for (int i = 0; i < input.rows; i++) {
for (int j = channelsIndex; j < input.cols * channels; j = j + channels) {
input.at<uchar>(i, j) = (uchar)newgrey[input.at<uchar>(i, j)];//灰度替换
}
}
}
return input;
}
Mat CalcGetHistogram3DImage(Mat src, int scale, Color color ) {
Mat dstHist;
int dims = 1;
float hranges[] = {
0,256 };
const float* ranges[] = {
hranges };
int bins = {
256 };
int channels = src.channels();
Mat dstImg(bins,bins*scale*channels, CV_8UC3, Scalar(0));
for (int channelsIndex = 0; channelsIndex < channels; channelsIndex++) {
calcHist(&src, 1, &channelsIndex, Mat(), dstHist, dims, &bins, ranges);
//这里得到的dstHist是一个bins*1的数组
double minValue = 0;
double maxValue = 0;
minMaxLoc(dstHist, &minValue, &maxValue, 0, 0);
double hpt = saturate_cast<double>(0.9 * bins);
for (int i = channelsIndex * bins; i < bins + channelsIndex * bins; i++) {
float binValue = dstHist.at<float>(i-channelsIndex*bins);
int realValue = saturate_cast<int>((double)binValue * hpt / maxValue);
rectangle(dstImg, Point(i * scale, bins - 1), Point(i * scale, bins - realValue), color.geti(channelsIndex), 1);
}
}
return dstImg;
}