本节介绍opencv中利用直方图进行图像分类的函数和方法。其中包括直方图的计算和比较操作。在彩色图像中使用hsv格式对图像进行变换,排除因为光照产生的像素值偏差。使用hsv格式能真正体现出图像的像素灰度分布,当把灰度直方图看成图像的概率分布时,利用概率分类函数和算法可以对图像进行统计意义上的分类研究。
(1)、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)
void cv::calcHist (const Mat *images, int nimages, const int *channels,
InputArray mask, SparseMat &hist, int dims, const int *histSize,
const float **ranges, bool uniform=true, bool accumulate=false)
void cv::calcHist (InputArrayOfArrays images, const std::vector< int > &channels,
InputArray mask, OutputArray hist, const std::vector< int > &histSize,
const std::vector< float > &ranges, bool accumulate=false)
计算数组集的直方图。函数cv::calcHist 计算一个或多个数组的直方图。用于增加直方图组矩(histogram bin)的元组元素是从对应的输入数组集的同一个数组中取得的。下面的示例给出了怎样计算2D彩色图像的色调-饱和度(Hue-Saturation)直方图的方法:
#include
#include
using namespace cv;
int main( int argc, char** argv )
{
Mat src, hsv;
if( argc != 2 || !(src=imread(argv[1], 1)).data )
return -1;
cvtColor(src, hsv, COLOR_BGR2HSV); //转换图像格式
//量化色调(hue)为30级,饱和度(saturation)为32级
int hbins = 30, sbins = 32;
int histSize[] = {hbins, sbins};//形成直方图轴坐标标度
//色调范围是0 到179,参见cvtColor,彩色格式设定的标度范围,组矩则是将这个范
//围进行划分,本例划分为30。
float hranges[] = { 0, 180 };
//饱和度范围0(黑-灰-白)到255(纯光照色),饱和度格式设定的标度范围,设定的组矩
//将这个范围划分为32段,每段为8,即直方图坐标轴s的标度为0,8,16,24,32,…。
float sranges[] = { 0, 256 };
const float* ranges[] = { hranges, sranges };
MatND hist;
//从第0和第1通道计算直方图
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 histImg = Mat::zeros(sbins*scale, hbins*10, CV_8UC3);
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/maxVal);
rectangle( histImg, Point(h*scale, s*scale),
Point((h+1)*scale - 1, (s+1)*scale - 1),
Scalar::all(intensity), -1 );
}
namedWindow( "Source", 1 );
imshow( "Source", src );
namedWindow( "H-S Histogram", 1 );
imshow( "H-S Histogram", histImg );
waitKey();
}
参数
images |
源图像集。应该有相同的深度,CV_8U,CV_16U或CV_32F,以及相同的尺寸。每一个数组都可以有不同的通道数。 |
nimages |
源图像数(第一个参数中包含的图像个数)。 |
channels |
数组通道数列表,用于计算直方图。第一个数组的通道数是从0到 images[0].channels()-1,第二个数组的通道数则从images[0].channels()到images[0].channels() + images[1].channels()-1,以此类推。 |
mask |
可选的屏蔽数组。如果这个矩阵不空,它必须是一个8-bit数组且与images[i]同尺寸。这个数组的非0屏蔽元素标记对应数组元素在直方图中的计数。 |
hist |
输出的直方图,它是一个致密或稀松的多维数组。 |
dims |
直方图的维数,必须为正值且不大于CV_MAX_DIMS(当前opencv版本下等于32)。 |
histSize |
每一维上数组直方图的尺寸(histogram bin,组矩)。 |
ranges |
每一维上直方图组矩范围的维度数组。在直方图归一化的时候(uniform =true),对每一个维度i ,它足够指定下界(包含)L0为0-th直方图组矩及上界(不包含)UhistSize[i]−1为最后一个直方图组矩histSize[i]-1。即,在归一化直方图时,每一个ranges[i]都是一个2元素的数组。在直方图非归一化(uniform=false)时,每一个ranges[i]包含有histSize[i]+1个元素:L0,U0=L1,U1=L2,…,UhistSize[i]−2=LhistSize[i]−1,UhistSize[i]−1。没有落在L0和UhistSize[i]−1之间的数组元素不被计数到直方图。 |
uniform |
标志,表示直方图是否要归一化(参见上面)。 |
accumulate |
累加标志。如果设置,开始分配的直方图不被清空。这个特征允许在几个数组集上计算单一直方图,或随时更新直方图。 |
(2)、void cv::calcBackProject (const Mat *images, int nimages, const int *channels,
InputArray hist, OutputArray backProject, const float **ranges,
double scale=1, bool uniform=true)
void cv::calcBackProject (const Mat *images, int nimages, const int *channels,
const SparseMat &hist, OutputArray backProject, const float **ranges,
double scale=1, bool uniform=true)
void cv::calcBackProject (InputArrayOfArrays images,
const std::vector
const std::vector< float > &ranges, double scale)
计算直方图的反映射。函数cv::calcBackProject计算直方图的反映射。即,类似于calcHist,在输入图像的每个像素位置(x, y)函数都采集选中通道的值,并查找对应的直方图组矩。不是增加组矩的值,而是相反,函数读取组矩的值,使用scale参数变比,然后存储到backProject(x,y)。从统计意义上讲,这个函数用直方图表示的实际概率分布计算每一个像素值的概率。参见示例,可以看到追踪场景中亮色目标的用途:
这是一种相机移动中彩色目标跟踪的粗略算法。
参数
images |
源数组。它们全部都应该有相同的深度,CV_8U,CV_16U或CV_32F,以及相同的尺寸。每一个都可以有不同的通道数。 |
nimages |
源图像个数。 |
channels |
用于计算反映射的通道列表。通道数必须与直方图的维度匹配。第一个数组的通道计数从0开始到images[0].channels()-1,第二数组的通道计数从images[0].channels()到images[0].channels() + images[1].channels()-1,于此类推。 |
hist |
输入的直方图,可以是致密或稀松的。 |
backProject |
目的反映射数组,可以是与images[0]有相同尺寸和深度的单通道数组。 |
ranges |
直方图每一维度上的组矩界数组的列表。见calcHist的说明。 |
scale |
可选的输出反映射比例因子。 |
uniform |
标志,表示直方图是否是归一化的。 |
参见
calcHist, compareHist
(3)、double cv::compareHist (InputArray H1, InputArray H2, int method)
double cv::compareHist (const SparseMat &H1, const SparseMat &H2, int method)
比较两个直方图。函数cv::compareHist使用指定算法比较两个致密或稀松直方图,并返回 d(H1,H2) 。对于1-, 2-, 3-维致密直方图这个函数能工作的很好,但是对于高维稀松直方图,它就不太适合了。对于这样的直方图,由于混叠和采样问题,非零直方图组矩可能稍稍有点移位。要比较这样的直方图或通常的稀松结构权重点集,应考虑使用EMD函数。
参数
H1 |
要比较的第一个直方图。 |
H2 |
第二个比较直方图,与第一个H1有相同的尺寸。 |
Method |
比较算法,参见HistCompMethods |
(4)、Ptr<CLAHE> cv::createCLAHE (double clipLimit=40.0, Size tileGridSize=Size(8, 8))
建立一个指向cv::CLAHE类的智能指针,并初始化这个类。
参数
clipLimit |
对比度限制阈值。 |
tileGridSize |
均衡直方图的栅格尺寸。输入图像将被划分成等尺寸的矩形瓦块。tileGridSize定义了行列的瓦块数。 |
注:CLAHE是限制对比度适配直方图均衡化类( Contrast Limited Adaptive Histogram Equalization)。
(5)、float cv::EMD (InputArray signature1, InputArray signature2,
int distType, InputArray cost=noArray(), float *lowerBound=0,
OutputArray flow=noArray())
float cv::wrapperEMD (InputArray signature1, InputArray signature2, int distType,
InputArray cost=noArray(), Ptr
OutputArray flow=noArray())
在两个权重点集结构之间计算最小工作距离(运输距离)。这个函数在两个权重点集结构之间计算移动的地表距离或下限距离。其应用之一是图像检索中的多维直方图比较。EMD本身是一个运输问题,用于求解下限成本的某些修正的简单算法。因而,其复杂性在最坏情况下是指数级的。但是,平均来讲,它是足够快的。 在实际操作中下界的计算甚至可以更快(使用线性时间算法),并且还可以用来大概确定是否两个标记集距离足够远,以致于它们所表示的对象不可能相关。
参数
signature1 |
第一个标记,size1×dims+1 浮点矩阵。每行存储点的权重和点的坐标。如果使用用户定义的cost矩阵,这个矩阵可以允许为单列形式(仅有权重)。权重必须是非负的并且至少一个非0值。 |
signature2 |
第二个标记,与signature1同格式,行数可以不同。总体权重可以是不同的。此时在signature1或signature2中添加假点。权重必须非负且至少有一个非0值。 |
distType |
使用的标度。见 DistanceTypes. |
cost |
用户定义的size1×size2 成本矩阵。另,如果使用成本矩阵,下界参数lowerBound 就不能计算给出,因为这需要一个标尺。 |
lowerBound |
可选的输入输出参数:在两个标记集之间的距离下界,这个距离是质心距离。如果使用用户定义的成本矩阵,或如果标记仅由权重组成(标记矩阵是单列的),这个下界就不能被计算给出,因为点集结构的总体权重是不相等的,必须自己初始化lowerBound。如果在质心之间计算的距离大于或等于lowerBound (也就是说标记集足够远),这个函数就不能计算EMD。在任何时候lowerBound都被设置成用来计算质心之间的距离的。因此,如果想要计算质心之间距离和EMD,lowerBound 就应该设置到0。 |
flow |
合成的size1×size2 流矩阵:flowi,j 是一个从signature1的i 点流到signature2的j 点的流量。 |
(6)、void cv::equalizeHist (InputArray src, OutputArray dst)
均衡化灰度图像的直方图。这个函数使用如下算法均衡化输入图像的直方图:
这个算法归一化了亮度,并且增加了图像的对比度。
参数
src |
源图像,8位单通道 |
dst |
目的图像,与源图像同类型和尺寸 |
(7)、void cv::matchTemplate (InputArray image, InputArray templ,
OutputArray result, int method, InputArray mask=noArray())
比较与模板重叠的图像区域。这个函数使模板滑过图像,使用指定的算法比较与模板重叠的尺寸为w×h 的图像区域,并存储结果到result中。这是比较的算法公式(I表示图像,T 模板,R 结果)。在模板和图像块上求和:x′=0...w−1,y′=0...h−1。
函数完成比较后,最好的匹配就可以使用minMaxLoc 函数找出,全程最小(当使用TM_SQDIFF) 或最大(当时用TM_CCORR 或 TM_CCOEFF )。对于彩色图像,模板的和作为分子而总和作为分母在所有通道上执行操作,各自的均值用于各自的通道。即,这个函数可以操作彩色模板和彩色图像。其结果仍然是单通道的图像,这更易于图像分析。
参数
image |
进行搜索的图像。必须是8位或32位浮点类型 |
templ |
搜索模板。必须不大于源图像并且有相同数据类型 |
result |
比较结果图。必须是32位单通道浮点类型。如果图像是W×H 而模板是w×h ,则结果为(W−w+1)×(H−h+1) 。 |
method |
指定比较算法的参数,见TemplateMatchModes |
mask |
搜索模板的屏蔽。必须与模板templ有相同的数据类型和尺寸。默认是不需要设置的。当前,仅支持TM_SQDIFF 和TM_CCORR_NORMED 算法。 |
直方图和目标检测(Histograms and Object Detection)编程实例
图像的直方图反映的是图像色彩和饱和度(灰度)的分布情况,也称作灰度的概率分布,根据设定的组矩(histogram bin)对图像灰度分布进行统计。操作直方图然后回应射到源图,可获得一定色彩或亮度(灰度)范围内的图像属性,并过滤掉不相关的色彩灰度值,从而达到提取图像特征(特殊区域)对图像进行分类或运动跟踪的目的。也可以用于识别色彩相关的图像类。注意:直方图的组矩是对图像进行分类的关键。组矩可以理解为对图像灰度的分割方法。可以使用线性函数分割,也可以使用其他非线性函数。
1、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)
void cv::calcHist (const Mat *images, int nimages, const int *channels,
InputArray mask, SparseMat &hist, int dims,
const int *histSize, const float **ranges,
bool uniform=true, bool accumulate=false)
void cv::calcHist (InputArrayOfArrays images, const std::vector< int > &channels,
InputArray mask, OutputArray hist, const std::vector< int > &histSize,
const std::vector< float > &ranges, bool accumulate=false)
计算给定图像设定参数的直方图。其中给定图像是一个图像列表,可以是一个图像或多个图像的。输出为全部图像统计的直方图,根据通道、维数和尺寸范围,计算图像指定通道的像素值统计数。参数nimages指定输入的图像数,channels指定各图像使用的通道号,dims指定直方图的维数(使用图像的通道数,每一个计算直方图的图像都是用相同的通道个数,可以是不同的通道),histSize和ranges指定了直方图中每一维的尺寸和取值范围(histSize为组矩histogram bin的个数,ranges为划分组矩的数据范围,如0--255灰度、0--179色度等,ranges被线性等分成histSize份,或非线性划分成histSize,每一个组矩bin指定一个数据区间,在此数据区间内的像素值都属于同一个组矩)。对于变通道或非线性组矩,使用重载的矢量函数指定各参数。下面图像显示rgb图的直方图和hsv图的直方图(一维方式):
源图和灰度图。
RGB各通道的直方图,组矩为32,范围256。
HSV格式各通道直方图,其中h通道组矩为32,范围为180,其它通道组矩为32,范围256。
对于二维直方图则是组合两个通道后的图像计算直方图。
上三为RGB图像的RG、RB、GB组合的二维直方图,下三为HSV图像的HS、HV、SV组合的二维直方图(坐标为2维,灰度值为组矩值)。对于3维直方图(RGB或HSV)也可以计算,只是不能直接图像表述(空间位置的统计值是4维)。
2、void cv::calcBackProject (const Mat *images, int nimages, const int *channels,
InputArray hist, OutputArray backProject, const float **ranges,
double scale=1, bool uniform=true)
void cv::calcBackProject (const Mat *images, int nimages, const int *channels,
const SparseMat &hist, OutputArray backProject, const float **ranges,
double scale=1, bool uniform=true)
void cv::calcBackProject (InputArrayOfArrays images,
const std::vector
const std::vector< float > &ranges, double scale)
用指定的直方图反映射到图像,以直方图中的组矩值过滤图像素点,找出与直方图指定的分布概率像素区域作为活动区域。
用于定位快递图标的反映射,左图为源图,中间为反映射图,右侧为快递图标的截图。使用图标截图生成直方图,使用此直方图对源图进行反映射,然后根据图标模板定位图标窗口位置。反映射指定屏蔽参数h[10,80],s[0,256],v[0,256]。其中h为hue参数,使用图像的颜色值过滤图像。在视频跟踪时,对目标生成一个直方图后,可直接在后续的视频帧中用于反映射获取目标在视频帧中的位置。要想定位准确,一是对目标颜色进行精准设定H[L,U],一是设定目标的矩形范围,直方图反映射是根据颜色值过滤图像,然后用目标矩形范围检索映射图像,找到在矩形范围内像素个数的最大值框位置,计算转角矩形。如图:
上图中分别是:源图,定位图(结果图),直方图反映射图,直方图及采集直方图使用的图,反映射结果的源图和根据HSV格式限定值生成的屏蔽图(mask)。反映射结果图是反映射源图“与”上屏蔽图获得。根据反映射结果图,使用跟踪窗(设定矩形)使用函数CamShift()
获得,将返回的转角矩形的外切椭圆绘制到源图。使用同一个直方图数据,可以对同类型的多张图像(帧)进行处理。
3、double cv::compareHist (InputArray H1, InputArray H2, int method)
double cv::compareHist (const SparseMat &H1, const SparseMat &H2, int method)
对两个图像的直方图做相似度比较,用以确定两个图像在像素统计意义上的相似度。比如图像的放大缩小后的图像,经过几何变换后的图像等。
同一张图的直方图比较,可以看出各算法的比较结果,Correl(相关性比较),接近于1或-1则相似性好。Chisqr(卡方比较),接近于0则相似性好,Intersect(交叉比较),等于取直方图的最小值的和,相当于直方图最小值的积分,与源直方图积分之差便可说明相似度。Bhattacharyya(巴氏距离),在计算式中有一个1-x项,实际操作中只是用了x项,因此计算的巴氏距离接近于0最好(理论上在1-x下接近于1最好)。最后是HELLINGER等价于巴氏距离。
对仿射变换的图进行直方图比较,从比较数据中可以看出巴氏距离和相关度都说明两图的相似性,而卡方和交叉比较的数据则需要进一步判断。因为仿射后的图片有背景为黑的区域,因此直方图有较大差异(这也是在实际应用中需要考虑的问题)。二者的直方图:
源图的直方图左上角有背景黑表示的组矩(histogram bin)块。
同样的比较图片,添加了底色(黑)边,比较结果就不一样了。其对应直方图:
4、Ptr<CLAHE> cv::createCLAHE (double clipLimit=40.0, Size tileGridSize=Size(8, 8))
建立一个对比度限定的自适应直方图均衡化类,用于对图像直方图均衡化后变换图像。
这是原图和灰度图。
这是tileGridSize=Size(8, 8)和clipLimit=20、40、80时的均衡直方图变换图。
这是clipLimit=40和tileGridSize=Size(4, 4)、Size(16, 16)时的均衡直方图变换。
5、float cv::EMD (InputArray signature1, InputArray signature2,
int distType, InputArray cost=noArray(), float *lowerBound=0,
OutputArray flow=noArray())
float cv::wrapperEMD (InputArray signature1, InputArray signature2, int distType,
InputArray cost=noArray(), Ptr
OutputArray flow=noArray())
这是一种计算权重标记序列的最小运动距离的算法,来源于多源/多目配送优化算法(earth mover’s distance)。用于计算直方图的相似度(复杂比较算法)。此算法可以粗略地确定两个权重序列距离的远近,因而也能判断出两个序列表示的目标是否相关。作为直方图相似的比较算法,能够更精确地给出比较图像是否相关。注意,该算法对组矩数敏感,在图像分类时,应先确定组矩数,越大耗时越长,当大于20时,有明显延迟,一般不大于12。
两张完全不相同的图片进行EMD操作,使用bin=12的HSV格式,计算结果为1.47881281。
两张有关联的图片经EMD操作,在bin = 12的HSV格式下距离为0.00167492。这说明距离的关联度越接近于0,也能说明图片的关联程度。需要指出的是图片的背景一定要相同,如下图:
黑色的背景区域增加了EMD的距离,相关联的图片不再关联。而下图:
则可看出背景的重要性,如果图片尺寸一致,其EMD的关联性可能会更好。以上说明了通过EMD计算图片的关联性一般需要注意的事项。
6、void cv::equalizeHist (InputArray src, OutputArray dst)
直方图均衡化图像变换,对图像的直方图进行均衡化处理,使用处理后的直方图反映射到图像。有点类似于CLAHE类对图像进行均衡化处理。注意,仅对单通道图像操作。
上图是源图(灰度图),下图是均衡化后的图,可明显看出清晰度的增强。两个直方图,左侧为源图的直方图,右侧为均衡化后的直方图,有一定的拉伸效果。
7、void cv::matchTemplate (InputArray image, InputArray templ,
OutputArray result, int method, InputArray mask=noArray())
使用模板图像与源图像重叠部分进行比较,模板逐点滑过源图像。根据指定比较算法计算比较结果,生成结果图像。使用minMaxLoc函数计算结果图像的最小最大值及其位置,确定最佳匹配位置。注意:结果图像比原图像小:
sorWid – tepWid = dstWid;
sorHige – tepHig = dstHig;
所以,对于原图相中部分包含的模板,本函数结果未知。
模板匹配用于检测图像中的特定目标(等大小目标),效果比较好。对于目标比例变化不大的也可以有粗略定位,但是对于目标变形或尺寸变化较大的则不能产生正确定位。
总结:
图像直方图属性,是对像素颜色值的统计操作,给出像素值落在某一色彩空间点上的概率。依次通过统计分析手段对图像进行变换获得关于对比度的拉伸操作。
关于模板匹配,是通过模板在图像上的滑动比较找到匹配距离最小的图像位置块。
ccc 于 2021-11-1