C#描述-计算机视觉OpenCV(5):直方图算法
- 前文链接
- 图像直方图
- 灰度直方图的计算
- 灰度直方图的绘制
- BGR三通道的直方图
- 直方图的均衡化算法
- 相似图像检测
前文链接
文中没提到的东西,很可能都在前文描述过
C#描述-计算机视觉OpenCV(1):基础操作
C#描述-计算机视觉OpenCV(2):图像处理
C#描述-计算机视觉OpenCV(3):重映射
C#描述-计算机视觉OpenCV(4):图像分割
图像直方图
图像由各种数值的像素构成。例如在单通道灰度图像中,每个像素都有一个 0(黑色)~255(白色)的整数。对于每个灰度,都有不同数量的像素分布在图像内,具体取决于图片内容。直方图是一个简单的表格,表示一幅图像(有时是一组图像)中具有某个值的像素的数量。
因此,灰度图像的直方图有 256 个项目,也叫箱子(bin)。0 号箱子提供值为 0 的像素的数量,1 号箱子提供值为 1 的像素的数量,以此类推。很明显,如果把直方图的所有箱子进行累加,得到的结果就是像素的总数。你也可以把直方图归一化,即所有箱子的累加和等于 1。这时,每个箱子的数值表示对应的像素数量占总数的百分比。
直方图或者说对色彩的统计,是很多算法的基础。本文将以此图为例,来生成在直方图:
灰度直方图的计算
首先,我们要把原图通过CvtColor函数转换为一个灰度图像:
然后我们拿着这张图,代入CalcHist函数。
这个直方图生成函数的参数为:
int histSize[1]; // 直方图中箱子的数量
float hranges[2]; // 值范围
const float* ranges[1]; // 值范围的指针,C#中,我们直接使用rangef
int channels[1]; // 要检查的通道数量
以及一个转换为Mat[] 的Mat灰度图像,以及一个hist作为结果输出
对于这张图,由于是一个0-255的单通道灰度图像,hist是一个Mat一维数组,大小为256,值域根据原图像素,心里也可以有个估计,每个色块的总数大约占比多少,然后可以直接输出出来检视结果。
public void GetHistogram(Mat img)
{
Mat hist = new Mat();
Mat grayImg = new Mat();
Cv2.CvtColor(img, grayImg, ColorConversionCodes.BGR2GRAY);
Mat[] vimg = new Mat[] { grayImg };
int[] channels = new int[] { 0 };// 要检查的通道数量
Rangef[] ranges = new Rangef[] { new Rangef(0, 256) };// 值范围与指针
int[] histSize = new int[] { 256 };// 直方图中箱子的数量
int dims = 1; //需要统计的特征数目(只统计灰度图单通道)
Cv2.CalcHist(vimg, channels, new Mat(), hist, dims,histSize, ranges);
Cv2.ImShow("grayImg", grayImg);
for(int i=0;i<256;i++)
{
float h = hist.At<float>(i);
textBox1.Text +="value "+Convert.ToString(i)+" : " +Convert.ToString(h) + "\r\n";//打印结果来检视下
}
GetImageOfHistogram(hist);//用计算结果去生成直方图
}
到这一步,我们得到了统计数组,然后就开始写直方图绘制函数
灰度直方图的绘制
我们可以同时生成一个直方图和一个Winform自带的Chart函数来进行结果比对
public void GetImageOfHistogram(Mat hist)
{
for (int i = 0; i < 256; i++)
{
XList1.Add(i);
YList1.Add(Convert.ToInt32(hist.At<float>(i)));
chart1.Series["Hist"].Points.DataBindXY(XList1, YList1);
}
double maxVal = 0;
double minVal = 0;
Cv2.MinMaxLoc(hist, out minVal, out maxVal);
int histSize = hist.Rows;
Mat histImg = new Mat(histSize, histSize, MatType.CV_8UC1);
int hpt = Convert.ToInt32(0.9 * histSize);
for (int h = 0; h < histSize; h++)
{
float binVal = hist.At<float>(h);
if (binVal > 0)
{
int intensity = Convert.ToInt32(binVal * hpt / maxVal);
Cv2.Line(histImg, h, histSize, h, histSize - intensity, 255, 1);
}
}
Cv2.ImShow("histImg", histImg);
}
结果:
大多数情况下,直方图是单个的单通道或三通道图像,但也可以在这个函数中指定一个分布在多幅图像(即多个 Mat)上的多通道图像。这也是把输入图像数组作为函数第一个参数的原因。参数 dims 指明了直方图的维数,例如 1 表示一维直方图。在分析多通道图像时,可以只把它的部分通道用于计算直方图,将需要处理的通道放在维数确定的数组 channel 中。
在这个类的实现中只有一个通道,默认为 0。直方图用每个维度上的箱子数量(即整数数组histSize)以及每个维度(由 ranges 数组提供,数组中每个元素又是一个二元素数组)上的最小值(含)和最大值(不含)来描述。
BGR三通道的直方图
针对彩色图像,我们通过修改函数参数,来执行三通道的统计:
histSize[0]= histSize[1]= histSize[2]= 256;
hranges[0]= 0.0; // BGR 范围为 0~256
hranges[1]= 256.0;
ranges[0]= hranges; // 这个类中
ranges[1]= hranges; // 所有通道的范围都相等
ranges[2]= hranges;
channels[0]= 0; // 三个通道:B
channels[1]= 1; // G
channels[2]= 2; // R
对于执行结果,由于三维直方图很难画,我们可以生成三个单独的通道直方图。
直方图的均衡化算法
很多时候,图像的视觉缺陷并不因为它使用的强度值范围太窄,而是因为部分强度值的使用频率远高于其他强度值。有时候图像中等灰度的强度值非常多,而较暗和较亮的像素值则非常稀少,而均衡对所有像素强度值的使用频率可以作为提高图像质量的一种手段。这正是直方图均衡化这一概念背后的思想,也就是让图像的直方图尽可能地平稳。
对于直方图的均衡化,我们可以运用自带函数:
Mat res = new Mat();
Cv2.EqualizeHist(grayImg, res);
Cv2.ImShow("res", res);
效果图:
经常P图的小伙伴能看出来,这种算法增强了图像的对比度,也增加了图像的清晰度与细节呈现。
直方图均衡化后的统计结果:
相似图像检测
基于内容的图像检索是计算机视觉的一个重要课题。它包括根据一个已有的基准图像,找出一批内容相似的图像。直方图是标识图像内容的一种有效方式,因此值得研究一下能否用它来解决基于内容的图像检索问题。
这里的关键是,要仅靠比较它们的直方图就测量出两幅图像的相似度。我们需要定义一个测量函数,来评估两个直方图之间的差异程度或相似程度。人们已经提出了很多测量方法,OpenCV中compareHist 函数的实现过程中使用了其中的一些方法,在C#中,该函数调用方法为:
CompareHist(InputArray h1, InputArray h2, HistCompMethods method);
写个Demo测试下
public void HistCompTest(Mat img1, Mat img2)
{
Mat hist1 = new Mat();
Mat hist2 = new Mat();
Mat grayImg1 = new Mat();
Mat grayImg2 = new Mat();
Cv2.CvtColor(img1, grayImg1, ColorConversionCodes.BGR2GRAY);
Cv2.CvtColor(img2, grayImg2, ColorConversionCodes.BGR2GRAY);
Mat[] vimg1 = new Mat[] { grayImg1 };
Mat[] vimg2 = new Mat[] { grayImg2 };
int[] channels = new int[] { 0 };
Rangef[] ranges = new Rangef[] { new Rangef(0, 256) };
int[] histSize = new int[] { 256 };
int dims = 1;
Cv2.CalcHist(vimg1, channels, new Mat(), hist1, dims, histSize, ranges);
Cv2.CalcHist(vimg2, channels, new Mat(), hist2, dims, histSize, ranges);
for (int i = 0; i < 256; i++)
{
XList1.Add(i);
YList1.Add(Convert.ToInt32(hist1.At<float>(i)));
chart1.Series["Green"].Points.DataBindXY(XList1, YList1);
}
for (int i = 0; i < 256; i++)
{
XList2.Add(i);
YList2.Add(Convert.ToInt32(hist2.At<float>(i)));
chart2.Series["Green"].Points.DataBindXY(XList2, YList2);
}
hist1.ConvertTo(hist1, MatType.CV_32FC1);
hist2.ConvertTo(hist2, MatType.CV_32FC1);
Cv2.Normalize(hist1, hist1, 0, 1, NormTypes.MinMax, -1, null);
Cv2.Normalize(hist2, hist2, 0, 1, NormTypes.MinMax, -1, null);//将两张图片统一格式,避免报错
double x;
x = Cv2.CompareHist(hist1, hist2, HistCompMethods.Correl);
textBox1.Text = Convert.ToString(x);
}
输出的结果相似度越靠近1,也就越相似,越靠近0,差别越大。由于是直方图对比,所以图像的旋转并不会影响对比的结果。暂时这是一个很初级的算法测试,实际工程意义不大,所以不做更多测试。