Haar特征级联分类器的训练与检测
参考文献:
(1) https://blog.csdn.net/zhuangxiaobin/article/details/25476833
1.样本的创建
训练样本分为正样本和负样本。正样本是指待检目标样本(例如车辆,行人,人脸等),负本指其它任意图片。所有的样本图片都被归一化为同样的尺寸大小(例如,20x20,24x24或33x33)。负样本可以来自于任意的图片,但这些图片不能包含目标特征。
1.1准备正样本图片集
进入路径为tools\temp\positive\rawdata的文件夹中,然后在里面放入含有正样本的图片。正样本数目要足够大,一般要是1000以上。
注:图片要用BMP格式,如果图片不是BMP格式的话,可以在文件夹中创建一个空的文本文件,在文本文件中输入ren *.jpg *.bmp,并将该文本文件改成.bat后缀的批处理文件,双击该批处理文件即可将文件夹中.JPG格式的图片批量转换成.BMP格式。
1.2截取目标区域
双击\tools\temp\positive下的objectmarker.exe可执行文件,开始截图目标区域。
使用方法可见readme文件,每次截好一次都要按一次空格键保存区域,多目标的图片可以继续截图并按空格键保存,若这张图片截取完成则按下回车键可以跳到下一张图片。
这个截图的过程不能中断,不然就无法产生相应的文件了,所以在样本太多的情况下,可以先分组,大约100张图片放进rawdata文件夹中,这样子如果有什么意外的话,损失也就100张图的时间而已,最后只需要把几个文件的内容合成一个大文件,并且把所有相关的图片放进rawdata文件夹中,最终的效果是一样的。截图完成后会在路径\tools\temp\positive下生成info.txt文件。
info.txt文件内容说明:
rawdata/1000986.BMP 1 0 1 127 93
①rawdata/1000986.BMP表示文件的相对路径;
②第2位的1表示图片中截取的区域数量,此处仅包含一个目标;
③第3和第4位的0 1表示截取的矩形框的起点坐标;
④第5和第6位的127 93表示截取的矩形框的宽和高;
注:第2位的数字若为2,即截取的目标数量为2个,则后面相应的矩形框起点坐标和矩形框尺寸也应该有2个,否则会在之后生成正样本的描述文件时报如下错误:
info.txt(1) : parse errorDone. Created 0 samples
1.3创建正样本描述文件vec文件
打开\tools下的samples_creation.bat文件,可见到如下指令:
cd temp
createsamples.exe -info positive/info.txt -vec data/vector.vec -num 4346 -w 24 -h 24
rem positive/positive_bmp_list.txt - file containing list of positive bmp files along with the rectagle coordinates from objectmarker.exe
rem -vec data/vector.vec - file created by the createsamples tool
rem all the other paramteres are described in the literature (links in the how_to.txt)
pause
其中我们需要根据实际情况修改的参数是-num、-w和-h。
①-num表示正样本的数量;
②-w和-h表示图片压缩后的大小;
运行samples_creation.bat文件后,会在\tools\temp\data下面生成vector.vec文件,这个就是向量描述文件了。
1.4准备负样本图片集
进入路径为\tools\temp\negative的文件夹中,然后在里面放入负样本图片。一般来说,负样本要比正样本数目多很多。为什么呢?因为负样本表示的是随机情况,只要有大量随机样本的情况下,才可以充分表示与正样本相反的情况,大约2-5倍。但也不能太多,不然的话花费的时间太长,分类效果可能反而不好,因为比例偏移太严重。
注:负样本图片也需要采用.BMP格式。
1.5创建负样本描述文件
运行\tools\temp\negative下的create_list.bat文件,可以自动生成描述文件。如果没有找到这个create_list.bat批处理文件,可以自行在该目录下创建一个.bat文件,内容就一句话:dir /b *.BMP >infofile.txt,然后保存。
2.训练分类器
2.1训练器的配置
打开\tools下的haarTraining.bat文件,首先对训练器进行配置。
①-data<dir_name>:存放训练好的分类器的路径;
②-vec<vec_file_name>:正样本描述文件路径;
③-bg<background_file_name>:负样本描述文件路径;
④-npos<number_of_positive_samples>:在每个阶段用于训练的正样本数目;
⑤-nneg<number_of_negative_samples>:在每个阶段用于训练的负样本数目。程序会自动切割负样本图片,使其和正样本大小一致,这个参数一般设置为正样本数目的1~3倍;
⑥-nstages<number_of_stages>:训练的分类器级数,一般选择15~20。每一级的训练目的是为了得到一个强分类器;
⑦-nsplits<number_of_splits>:一个弱分类器中分裂的子节点数目或特征个数。如果1,则只有一个简单的stump classifier被使用。如果是2或者更多,则带有number_of_splits 个内部节点的CART分类器被使用;
⑧-mem<memory_in_MB>:训练时的可用内存,单位为MB。内存越大则训练的速度越快;
⑨-sym(default)和-nonsym:后面不用跟其他参数,指定训练的目标对象是否垂直对称。垂直对称提高目标的训练速度。
⑩-minhitrate<min_hit_rate>:每一级分类器需要的最小的命中率(正检率),默认为0.995。针对每一个正样本累加这级强分类器的所有弱分类器的权重,然后对所有正样本得到的累加权重按从小到大排序,因为minhitrate决定至少要分对minhitrate*npos个正样本,所以取第(1.0-minhitrate)*npos正样本的权重为这级强分类器的阈值,通过这个阈值将大于这个阈值的分为正类,否则分为负类。所以minhitrate这个值决定每个阶段分类器的正样本的正检率,总的正检率为min_hit_rate 的number_of_stages 次方;
⑪-maxfalsealarm<max_false_alarm_rate>:每一级分类器的最大错误报警率(误检率),默认为0.5,总的误检率为max_false_alarm_rate 的number_of_stages 次方。前面通过minhitrate参数与弱分类器的权重累加和,得到了该强分类器的阈值后,通过这个阈值来分类负样本,负样本的分错个数不能超过numneg*maxfalsealarm,如果超过则继续训练本级强分类器,增加新弱分类器直到阈值将负样本分错个数小于numneg*maxfalsealarm为止就跳出循环,则本级强分类器训练成功;
⑫-weighttrimming<weight_trimming>:权重调整系数weightfraction。在每级强分类器的训练中每训练一个弱分类器前将所有样本中,包括正样本与负样本,按权重从小到大排列将权重最低的weightfraction * (npos+nneg)的样本去除掉,因为权重很小说明这个样本总能被正确分类了,删除这些已经能正确分类的样本,让训练更多集中于还不能正确分类的样本上,提高训练效率。默认为0.95;
⑬-eqw:如果有-eqw则equalweights值为1,否则默认为0。1表示所有样本的初始化权重相等,0表示不等;
⑭-mode<BASIC,CORE,ALL>:BASIC仅使用垂直特征(适用于人脸等),CORE仅使用45度旋转特征,ALL使用Haar特征集的种类既有垂直又有45度角旋转的;
⑮-w<sample_width>:样本图片的宽度(以像素为单位);
⑯-h<sample_height>:样本图片的高度(以像素为单位);
⑰-bt<DAB,RAB,LB,GAB>:Discrete AdaBoost、Real AdaBoost、LogitBoost和Gentle AdaBoost分别代表4个应用的boost算法的种类,默认为GAB;
⑱-err<misclass,gini,entropy,least sum of squares>:分别代表四种在训练弱分类器时的计算阈值方法,默认为misclass;
⑲-maxtreesplits<maximum_number_of_nodes_in_tree>:树节点数的最大值,默认为0;
⑳-minpos<min_positive>:训练过程中,节点可使用的正样本数目。正样本根据节点被分类。通常来说,minpos不小于npos/nsplits。
2.2运行训练器
①BACKGROUND PROCESSING TIME:负样本切割时间,单位是秒,一般会占用很长时间;
②N:训练的弱分类器序号;
③%SMP:由weightfraction决定,表示通过剔除小权值的样本后与总体样本数的比值;
④ST. THR:弱分类器的阈值;
⑤HR:该阈值下本级强分类器的正检率;
⑥FA:该阈值下本级强分类器的误检率,只有当每一级训练的FA低于定义的最大误检率maxfalsealarm才会进入下一级的训练;
⑦EXP. ERR:经验错误率;
训练过程会在\tools\temp\data\cascade中产生相应级数的文件,训练花费的时间跟电脑配置和训练数据的大小有关。如果训练过程中断也没关系,再次启动训练可以继续训练下去的。
训练完成后,将\temp\data\cascade下的N(这里N=18)个弱分类器文件夹拷贝到cascade2xml\data下,运行\tools\cascade2xml下的convert.bat文件就可以生成.xml分类器文件了,默认在同级目录下生成output.xml。
3.目标检测
如下是launch文件的内容:
<launch>
<node pkg="robot_vision" name="vehicle_detector" type="vehicle_detector.py" output="screen">
<rosparam>
haar_scaleFactor: 1.2
haar_minNeighbors: 2
haar_minSize: 20
haar_maxSize: 55
</rosparam>
<param name="cascade_vehicle" value="$(find robot_vision)/data/haar_detectors/output.xml" />
<param name="video_vehicle" value="$(find robot_vision)/data/video/video2.mp4" />
</node>
</launch>
参数说明:
①haar_scaleFactor:检测窗口的缩放比例,一般为1.1或1.2;
②haar_minNeighbors:构成检测目标的相邻矩形的最小个数(默认为-1)。如果组成检测目标的小矩形的个数和小于min_neighbors-1 都会被排除。如果min_neighbors 为 0, 则函数不做任何操作就返回所有的被检候选矩形框,这种设定值一般用在用户自定义对检测结果的组合程序上;
③haar_minSize:检测窗口的最小尺寸,默认情况下被设为分类器训练时采用的样本尺寸(即24x24);
④haar_maxSize:检测窗口的最大尺寸;
⑤cascade_vehicle:训练好的分类器.xml文件路径;
⑥video_vehicle:需要进行目标识别的视频文件路径。
如下是vehicle_detector.py文件内容:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import rospy
import cv2
import numpy as np
from sensor_msgs.msg import Image, RegionOfInterest
from cv_bridge import CvBridge, CvBridgeError
class vehicleDetector:
def __init__(self):
# 创建cv_bridge
self.bridge = CvBridge()
self.image_pub = rospy.Publisher("cv_bridge_image", Image, queue_size=1)
video = rospy.get_param("~video_vehicle", "")
cascade_vehicle = rospy.get_param("~cascade_vehicle", "")
# 使用级联表初始化haar特征检测器
self.cascade_vehicle = cv2.CascadeClassifier(cascade_vehicle)
# 设置级联表的参数,优化目标识别,可以在launch文件中重新配置
self.haar_scaleFactor = rospy.get_param("~haar_scaleFactor", 1.2)
self.haar_minNeighbors = rospy.get_param("~haar_minNeighbors", 2)
self.haar_minSize = rospy.get_param("~haar_minSize", 40)
self.haar_maxSize = rospy.get_param("~haar_maxSize", 60)
self.color = (50, 255, 50)
cap = cv2.VideoCapture(video)
# 获取视频图像
while(cap.isOpened()):
ret, cv_image = cap.read()
cv_image_compressed = cv2.resize(cv_image, (1280,720))
frame = cv_image_compressed
if ret==True:
# 创建灰度图像
grey_image = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 创建平衡直方图,减少光线影响
grey_image = cv2.equalizeHist(grey_image)
vehicle_result = self.detect_vehicle(grey_image)
# 将识别到的车辆框出来
if len(vehicle_result)>0:
for vehicle in vehicle_result:
x, y, w, h = vehicle
cv2.rectangle(cv_image_compressed, (x, y), (x+w, y+h), self.color, 2)
cv2.imshow('frame',cv_image_compressed)
if cv2.waitKey(25) & 0xFF == ord('q'):
break
else:
break
def detect_vehicle(self, input_image):
# 匹配车辆模型
if self.cascade_vehicle:
vehicles = self.cascade_vehicle.detectMultiScale(input_image,
self.haar_scaleFactor,
self.haar_minNeighbors,
cv2.CASCADE_SCALE_IMAGE,
(self.haar_minSize, self.haar_maxSize))
return vehicles
def cleanup(self):
print "Shutting down vision node."
cv2.destroyAllWindows()
if __name__ == '__main__':
try:
# 初始化ros节点
rospy.init_node("vehicle_detector")
vehicleDetector()
rospy.spin()
except KeyboardInterrupt:
print "Shutting down face detector node."
cv2.destroyAllWindows()
4.演示视频
<iframe allowfullscreen="true" data-mediaembed="bilibili" id="e1dqR03m-1621140550419" src="https://player.bilibili.com/player.html?aid=712669225"></iframe>CV|Haar特征分类与车屁股识别