参考链接: https://blog.csdn.net/zhuangxiaobin/article/details/25476833

Github链接: https://github.com/chanchanchan97/ROS

1. 样本的创建

训练样本分为正样本和负样本。正样本是指待检目标样本,例如车辆,行人,人脸等,负样本是指其它任意图片。所有的样本图片都被归一化为同样的尺寸大小,例如20×20,24×24或33×33。负样本可以来自于任意的图片,但这些图片不能包含目标特征。

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

其中我们需要根据实际情况修改的参数是numwh

  • num表示正样本的数量;
  • wh表示图片压缩后的大小;

运行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. 演示视频

https://www.bilibili.com/video/av712669225/?zw

CV|Haar特征分类与车屁股识别