本文由:姚磊岳([email protected])于2017-5-28撰,转载请保留作者信息。
按照惯例,先吐槽:Emgu.CV的资料太少了,不知道是不是C#做图形的太小众。因为项目需要,需要对图片中的车辆进行标定。于是想到了HOG+SVM的方式。在Emgu.CV中默认提供了已经训练好的行人检测的特征,但是如果要检测其他物体,必须自己进行训练。于是在网上一通寻找,居然没有完整的代码,没办法,只有苦逼的自己动手。弄了整整一天,终于实现。应该是目前网络上比较详细的一篇EMGU.CV自行训练的文章了,大家如要转载请保留作者信息。效果如图:
方便自己,也方便他人,把今天所实现的代码上传,并详细说明。时间有限,没有做封装,凑合着看。
需要具备:
1、HOG的基本概念;
2、用于检测目标的正负样本。如:需要检测的是车辆,则需要有车辆的各种样本。本文车辆正样本只有500个,所以最终的检测效果并不好(如上图所示),如果样本足够,可以检测到图片中的全部车辆。
思路流程:
1、根据训练样本,设计HOG描述子;
2、获取正负样本HOG值;
3、训练SVM;
4、从训练好的SVM提取出hog描述值(一个一维的float数组,这是实现过程中最难的)
5、检测
检测
详解:
1、根据训练样本,设计HOG描述子(有的也叫HOG算子);
这里需要有HOG的基本概念,什么是窗口大小,什么是block,什么是cell等等,网上资料很多,这里不做过多届时。
在我的实现中,正负训练样本是36*36像素的图片,于是,我的HOG算子设计如下:
第一个参数:winsize:窗口大小,我取样本大小;
第二个参数:block,我的是12*12;当然,也可以设置为18*18或者6*6,切记:BLOCK大小不能是技术值,至于为什么,看一下cell和block的关系即可。同时:设计的过程中,BLOCK要可被winsize整除。网上的资料都是16*16,也没有说清楚为什么,其实这个看懂了HOG后可以自行设置
第三个参数:stride,我的设置是6*6,当然也可以设置为12*12或者4*4,不同的设置,将得到不同的纬度数量;
第四个参数:cell,4个cell构成1个block,这也就是block一定要是偶数的原因。
第五个参数:需要得到几个channel的值,默认是9个,具体原因请参见HOG原论文
当我们设置完毕后,纬度信息就可以计算出来了
a = (winsize宽-block宽) / stride宽 +1
b = (winsize高-block高) / stride高 +1
c = block宽 / cell宽
d = block高/block高
纬度 = a*b*c*d*channel数,在本例中为:5*5*2*2*9 = 900
这个值,在通过hog.compute(图片)时也可以得到,当然,自己算一下也未尝不可,至少不用像网上资料那样进行判断。
2、获取正负样本HOG值
这一步相对来说很简单,即:将正负样本图片一一读入,然后逐一计算,并添加到matrix中即可。直接上代码:
有必要说明的是:
(1)Image<Gray, byte> im = new Emgu.CV.Image<Gray, byte>(filePath);
hog支持的是灰度图片,不能用彩色图片进行训练。
(2)
Matrix<float> HogFeatureData = new Matrix<float>(967, 900);
//500样本+467负样本,HOG纬度=(36-12)/6 + 1) *(36-12)/6 + 1) * 4 *9
Matrix<int> featureClasses = new Matrix<int>(967, 1);
这是SVM训练所需要的两个参数,HogFeatureData是全部样本(正负样本都在)的hog矩阵,其中967是样本数量,900是HOG的纬度,怎么来的,前面已经说明了计算方法。
featureClasses是每一个样本对应的类别,正样本对应1,负样本对应-1
3、训练SVM
这步比较简单,没有什么复杂的地方,直接看代码即可。
4、从训练好的SVM提取出hog描述值
这是实现过程中最难的,也是我本人在实现过程中花时间最多的。当然首先是自己没有用LibSVM等其他的技术辅助(我也不会,汗……)。我实现的方法很笨,如果各位有LibSVM基础的,可以忽略我的这个步骤。
首先,得感谢:http://www.cnblogs.com/KC-Mei/p/4553024.html博客的作者,虽然还有很多方法可以得到项对应的值,但是我也懒得动脑筋,直接拿来主义了。
这里一共要获得4个变量。详细代码请下载本文最终所提供的全部源代码。至于怎么计算,老样子,先上代码:
(1)首先,如果已经训练过样本,并保存了(本例在第三步所做的事),则可以通过直接读取XML避免再次训练。
resultMat = -1 * alphaMat * svmMat;
这个是计算hog detector的关键步骤,原理可寻找相关网上资料,这里就不详叙了。
(2)float[] mydetector = new float[DescriptorDim + 1];
for (int i = 0; i < DescriptorDim; i++)
{
mydetector[i] = resultMat[0, i];
}
mydetector[DescriptorDim] = rhoValue;
这里一定要说明一下,hog的纬度在本例中是900,但是detector要加上一纬偏移,也就是901纬。组后一纬度的值是rho的节点值。第三步训练完毕后,我们可以打开保存的XML,找到这个节点。
这个步骤牵涉到较多的HOG原理,很奇怪为什么EmguCv不能直接提供一个封装的方法。要使用者大费周章去自己计算。也许这就是开源的不足之处吧,有时间了,自己要封装一个方法,每次这样写代码得累死,也毫无意义。
接下来第五步:预测 就简单多了。
(1)为了自己编程方便,定义了一个类:myRect,其中:rct用来存放检测出来的矩形框类性;score是这个矩形框的得分(得分越高,检测越准,在编程过程当中,我们可以通过对score进行阈值设定,以屏蔽一些不靠谱的检测)
(2)在Using这块,网上全部的资料都是:HOGDescriptor hog = new HOGDescriptor();
这是不对的,如果仅用来检测行人,或者默认的hog算子即可的话(默认hog算子为:winsize:64*128,block:16*16,stride:8*8,cell:8*8),那没有问题,但如果我们定义了我们自己的HOG算子,则必须保持和我们自己hog算子的一致。故在本例中必须:
using (HOGDescriptor hog = new HOGDescriptor(new Size(36, 36), new Size(12, 12), new Size(6, 6), new Size(6, 6), 9))
然后:
hog.SetSVMDetector(mydetector);
代表,将HOG的SVM的detector设置为我们之前计算出来的值。
设置完毕后,就可以通过我们自行训练的SVM来进行检测了。代码比较简单,就不再详述了。
全部源代码下载地址:http://download.****.net/detail/u011616825/9855112