在进行机器学习中,不管是用Haar特征,或者是Hog特征,最后进行的不论是adaboost分类,还是svm分类,都只能是检测出待检测的正样本相近的图片。
比如,为了提高准确性,我们在进行正样本的训练时,必须把正样本置于整个正样本的大部分中,以交通标志中的禁止驶入为例,如下图的就是正确的正样本之一:
我们可以看到,禁止驶入交通标志占了图片面积的大部分。这在训练时,可以尽可能的减少误差。如下图就是个错误的例子:
在这幅图片中,我们只能在右面的一小块区域发现禁止驶入的交通标志。这个是错误的正样本。因为有太多的干扰项。
那么,我们进行实际检测时,不可能只是对如图1的图片进行检测,实际情况中,往往是如图2所示,那么,我们直接用检测方法,毫无疑问的,得到的一定是错误结果。
于是,就得到了,如何从一张图片中提取到我们感兴趣的区域。如图2中,在进行实际检测时,必然是先从这张图片中检测出右上角的禁止驶入的交通标志的区域,然后,在对其进行匹配处理,看是哪个标志,或者是不是交通标志。
这里,我们先以交通标志为例,这篇文章的主要算法思想,也是针对例如交通标志这种的,颜色划分比较明显的进行叙述。
好了,正如上段所说的,我们主要的算法思想是,针对诸如交通这类,因为我们都知道,交通标志主要分为四种颜色,即RGBY,在检测时,我们可以想得到,如果把待检测图片,分成RGBY四种颜色,那么,就能得到交通标志的区域外轮廓。再用边缘检测,可以选择用canny边缘检测,并且求出每个不连通的边缘的最小外界矩形,返回这个矩形的所在位置,就能得到我们感兴趣的区域。
当然,为了方便起见,我们可以直接用opencv中的cvFindCounter()函数,这个函数的第三个矩形,可以返回一个矩形轮廓。我们分别对返回的矩形轮廓进行检测(很明显不会只返回一个轮廓,当然,下面我们会介绍几种尽可能排除掉不希望有的矩形的返回值),最后得到想要的结果。
下面是算法的具体介绍。
首先是说明,我们用的是Hog特征检测和Svm二分类。用的编译环境是vs2010,opencv2.4.9。
然后是,对待检测图片进行分色。我们知道的,原图片一般都是RGB三色通道(CMYK是打印颜色,我们这里先不做考虑),转到RGBY,opencv上没有转的函数,我们可以选用两种方法。一种是直接转,即用RGB直接分离通道,然后得到RGB的通道,Y通道,利用黄颜色是蓝色的补色,利用补色直接转。利用Y=255-B,可以得到最后的黄色的图片的结果。
注:这里的是粗转换,如果想进行准确的RGB转Y或者转CMYK的,要注意的是最好用PS转,因为我们没有进行配置文件的添加和处理,最后会由于CMYK颜色空间比RGB颜色空间小,而得不到正确的图片,具体请参考文章最后的链接,我们可以看出,不能得到和原图一样的效果,是因为颜色被压缩了。
int rgb2cmyk( Mat &image,Mat &cmyk){
if(!image.data){
cout<<"Miss Data"<<endl;
return -1;
}
int nl = image.rows; //行数
int nc = image.cols; //列数
if(image.isContinuous()){ //没有额外的填补像素
nc = nc*nl;
nl = 1; //It is now a 1D array
}
//对于连续图像,本循环只执行1次
for(int i=0;i<nl;i++){
uchar *data = image.ptr<uchar>(i);
uchar *dataCMYK = cmyk.ptr<uchar>(i);
for(int j = 0;j < nc;j++){
uchar b = data[3*j];
uchar g = data[3*j+1];
uchar r = data[3*j+2];
uchar c = 255 - r;
uchar m = 255 - g;
uchar y = 255 - b;
uchar k = min(min(c,m),y);
dataCMYK[4*j] = c - k;
dataCMYK[4*j+1] = m - k;
dataCMYK[4*j+2] = y - k;
dataCMYK[4*j+3] = k;
}
}
return 0;
}
第二种,我是自己选用的方法。是把RGB转到HSI,然后利用H是表示色度的信息,在把HSI转到RGBY。
具体算法是:把这幅图片,先转到HSI空间,然后,利用HSI空间中的H颜色信息,可以把RGB分为RGBY,其中,把H扩大倍数到360,这样,当H在0~10,或340~360时,就是红色,H在10~60,就为黄色,H在180~240时,就是蓝色,H在110~200,就为绿色,当然对纯度和明度也有一定要求,一般纯度大于0.2,鲜艳色明度大于0.3,暗淡的颜色明度大于0.1。这里,我们还可以进行手动的调节S和I,以让它符合我们所要检测的环境(手动调节是为了应付任务,不建议大家使用)。
下面是代码片段:
void RGB2HSI(int R, int G, int B, double& H, double& S, double& I)
{
if (R<0 || R>255 || G<0 || G>255 || B<0 || B>255)
{
AfxMessageBox("Value out of range!");
return;
}
double min, mid, max;
if (R>G && R>B)
{
max = R;
mid = MAX(G, B);
min = MIN(G, B);
}
else
{
if (G>B)
{
max = G;
mid = MAX(R, B);
min = MIN(R, B);
}
else
{
max = B;
mid = MAX(R, G);
min = MIN(R, G);
}
}
I = max / 255;
S = 0;
H = 0;
if (I==0 || max==min)
{
// this is a black image or grayscale image
S = 0;
H = 0;
}
else
{
S = (I - min/255) / I;
// domains are 60 degrees of
// red, yellow, green, cyan, blue or magenta
double domainBase = 0.0;
double oneSixth = 1.0/6.0;
double domainOffset = ( (mid-min) / (max-min) ) / 6.0;
if (R==max)
{
if (mid==G)
{ // green is ascending
domainBase = 0; // red domain
}
else
{ // blue is descending
domainBase = 5.0/6.0; // magenta domain
domainOffset = oneSixth - domainOffset;
}
}
else {
if (G==max)
{
if (mid==B)
{ // blue is ascending
domainBase = 2.0/6.0; // green domain
}
else
{ // red is ascending
domainBase = 1.0/6.0; // yellow domain
domainOffset = oneSixth - domainOffset;
}
}
else
{
if (mid==R)
{ // red is ascending
domainBase = 4.0/6.0; // blue domain
}
else
{ // green is descending
domainBase = 3.0/6.0; // cyan domain
domainOffset = oneSixth - domainOffset;
}
}
}
H = domainBase + domainOffset;
}
}
void Image2RBYGImage(IplImage* image, IplImage* imageR, IplImage* imageB, IplImage* imageY, IplImage* imageG)
{
int i,j;
double r,g,b,s,h,v,H;
for (i=0;i<height;i++)
for(j=0;j<width;j++)
{
b=(double)data1[i*step+j*channels+0];
g=(double)data1[i*step+j*channels+1];
r=(double)data1[i*step+j*channels+2];
RGB2HSI((int)r,(int)g,(int)b,h,s,v);
H=h*360;
if (((H>0&&H<10) || (H>340&&H<360))&&s>0.2&&v>0.1)//R (H>0&&H<10) || (H>330&&H<360)BLUEH>200&&H<270&&s>0.3,YELLOWH>20&&H<100&&s>0.2
{
data_R[i*step1+j] = 255;
}
else if (H>10&&H<60/*&&s>0.3&&v>0.3*/)//Y (H>0&&H<10) || (H>330&&H<360)BLUEH>200&&H<270&&s>0.3,YELLOWH>20&&H<100&&s>0.2
{
data_Y[i*step1+j] = 255;
}
else if (H>180&&H<240&&s>0.2 && v>0.3)//B(H>0&&H<10) || (H>330&&H<360)BLUEH>200&&H<270&&s>0.3,YELLOWH>20&&H<100&&s>0.2
{
data_B[i*step1+j] = 255;
}
else if (H>110&&H<200&&s>0.2&&v>0.3)
{
data_G[i*step1+j] = 255;
}
}
cvDilate(imageR, imageR, 0, 3); //膨胀 形态学 第3个参数若为0则为长方形结构元素 第四个参数膨胀次数
cvDilate(imageB, imageB, 0, 3);
cvDilate(imageY, imageY, 0, 3);
cvDilate(imageG, imageG, 0, 5);
cvReleaseImage(&Imagerect);
}
注:图片的声明和清空和预处理部分已经删除,请自行添加相关代码。
这样,我们就对图片进行了分色处理。之后,是检测出轮廓。
CvMemStorage* storage = cvCreateMemStorage(0);
CvSeq* contour = 0;
int height=image->height;//得到图片的size
int width=image->width;
int classID = -1;
//寻找轮廓,返回感兴趣区域轮廓的指针,指针可以指出一个矩形
cvFindContours(imagegray, storage, &contour, sizeof(CvContour), CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE );
for( ; contour != 0; contour = contour->h_next )
{
CvRect r = ((CvContour*)contour)->rect;
if (r.height>15&&r.width>15)
{
cvRectangle(image,cvPoint(r.x,r.y),cvPoint(r.x+r.width,r.y+r.height),CV_RGB(0,255,0),1);
......
}
}
注:上图主要看的是cvFindCounter()函数的用法,也可以自行百度此函数用法。
然后,就是从这些返回的矩形框中,进行svm分类的检测。最后得到效果。
当然,我们从cvFindCounter()的一个大体介绍中,也可以看出,返回的矩形框太多了。我们可以进行一个初处理,把所有长和宽小于15个像素的矩形框全部去除掉。这样,会减少一部分矩形。如果对于检测信息还有要求的话,我们还可以加上位置信息,例如,交通标志的通常放置位置是整个图片的右上方,或者水平面以上,即拍摄图片的一半或者1/3位置以上(以左下角为原点时)。还有,可以加上,只取返回的矩形框的长宽大小排名的前三进行检测,可以大大减少检测量。
对于目标物体和背景颜色相近的情况,我们可以先选出这部分,然后进行全局阈值处理(例如OTSU全局阈值处理)。
下面是otsu算法:
int BinaryOtsu(const long *Gray){
//设置Gray[256],即为原图像的所有灰度值
int k;
long n;
long n1,n2;
double sum,csum;
double m1,m2;
double c;
double c1;
int mid=0;
n = 0;
sum = csum = 0;
for (k = 0; k <= 255; k++) {
sum += (double) k * (double) Gray[k];
n += Gray[k];
}
n1 = 0;
c = -1.0;
for (k = 0; k < 255; k++) {
n1 += Gray[k];
if (!n1) { continue; }
n2 = n - n1;
if (n2 == 0) { break; }
csum += (double) k *Gray[k];
m1 = csum / n1;
m2 = (sum - csum) / n2;
c1 = (double) n1 *(double) n2 * (m1 - m2) * (m1 - m2);
if (c1 > c) {
c = c1;
mid = k;
}
}
return mid;
}
然后,就得到最后的结果,当然,对于从一张图片中提取出感兴趣的区域的方法有很多,这里只是介绍一种方法和大概思路而已。
如下图是最后处理的结果。
参考链接:http://blog.csdn.net/Solomon1558/article/details/44108869
上述链接为RGB转CMYK,看效果,确实颜色被压缩了。