使用opencv训练cascade分类器进行目标检测

0.建立训练目录

文件夹: train/

1.建立正负样本

新建3个文件夹:train/pos/、train/neg/、train/xml/

pos文件夹
放置正样本,尺寸要一致:如202020*20(一般用于Haar特征),242424*24(LBP特征)

neg文件夹
放置负样本,正样本的尺寸要保证不大于负样本的尺寸

xml文件夹
级联分类器xml文件的输出目录

2.生成正负样本的txt文件

生成正样本txt文件 train/pos/pos.txt:

内容:图片名 类别编号 左上角x 左上角y 右下角x 右下角y
pos_image1.png 1 0 0 30 30
pos_image2.png 1 0 0 30 30
… …

生成负样本txt文件 train/neg/neg.txt:

内容:图片路径名
neg/neg_image1.png
neg/neg_image2.png
… …

import os

def _get_directory_files(path, fileType, filePaths):
    if not os.path.exists(path):
        return

    files = os.listdir(path)
    for f in files:
        npath = path + '/' + f
        if (os.path.isfile(npath)):
            if (os.path.splitext(npath)[1] == fileType): 
                filePaths.append(f)

        if (os.path.isdir(npath)):
            if (f[0] == '.'):
                pass
            else:
                _get_directory_files(npath, fileType, filePaths)
    return

def _write_txt(txt_path,img_dir,is_pos,img_size,neg_dir='neg'):

    imgpaths=[]
    _get_directory_files(img_dir,'.png',imgpaths)
    with open(txt_path, "a") as f:
        for img_p in imgpaths:
            if is_pos:
                f.write('%s 1 0 0 %d %d\n' % (img_p,img_size[0],img_size[1]))
            else:
                f.write('%s/%s\n' % (neg_dir, img_p))
    return

if __name__ == "__main__":
    txt_pos = './train/pos/pos.txt'
    txt_neg = './train/neg/neg.txt'
    pos_img_dir = './train/pos'
    neg_img_dir = './train/neg'
    _write_txt(txt_pos,pos_img_dir,True,(30,30))
    _write_txt(txt_neg,neg_img_dir,False,(30,30))

3.生成 pos.vec描述文件

在 train/ 目录下,运行命令:

opencv_createsamples -vec pos.vec -info pos/pos.txt -bg neg/neg.txt -num 10000 -w 30 -h 30

命令参数如下:

  • info 输入正样本描述文件,默认NULL

  • img 输入图像文件名,默认NULL

  • bg 负样本描述文件,文件中包含一系列的被随机选作物体背景的图像文件名,默认NULL

  • num 生成正样本的数目,默认1000

  • bgcolor 背景颜色,表示透明颜色,默认0

  • bgthresh 颜色容差,所有处于bgcolor-bgthresh和bgcolor+bgthresh之间的像素被置为透明像素,也就是将白噪声加到前景图像上,默认80

  • inv 前景图像颜色翻转标志,如果指定颜色翻转,默认0(不翻转)

  • randinv 如果指定颜色将随机翻转,默认0

  • maxidev 前景图像中像素的亮度梯度最大值,默认40

  • maxxangle X轴最大旋转角度,以弧度为单位,默认1.1

  • maxyangle Y轴最大旋转角度,以弧度为单位,默认1.1

  • maxzangle Z轴最大旋转角度,以弧度为单位,默认0.5
    输入图像沿着三个轴进行旋转,旋转角度由上述3个值限定。

  • show 如果指定,每个样本都将被显示,按下Esc键,程序将继续创建样本而不在显示,默认为0(不显示)

  • scale 显示图像的缩放比例,默认4.0

  • w 输出样本宽度,默认24

  • h 输出样本高度,默认24

  • vec 输出用于训练的.vec文件,默认NULL

4.训练cascade分类器

在 train/ 目录下,运行命令:

opencv_traincascade -data xml -vec pos.vec -bg neg/neg.txt -numPos 8000 -numNeg 16000 -numStages 20 -featureType LBP -w 30 -h 30

命令参数如下:

  • data 目录名xml,存放训练好的分类器,如果不存在训练程序自行创建

  • vec pos.vec文件,由opencv_createsamples生成

  • bg 负样本描述文件, neg/neg.txt

  • numPos 每级分类器训练时所用到的正样本数目。
    应当注意,这个数值一定要比正样本时的总数少,不然会报can not get new positive sample.理由:minHitRate:影响每个强分类器阈值,当设置为0.95时如果正训练样本个数为10000个,那么其中的500个就很可能背叛别为负样本,第二次选择的时候必须多选择后面的500个,按照这种规律我们为后面的每级多增加numPos*minHitRate个正样本,根据训练的级数可以得到如下公式:
    numPos+numStages1numPos1minHitRate<=numPos+(numStages-1)*numPos*(1-minHitRate)<=准备的训练样本

  • numNeg 每级分类器训练时所用到的负样本数目,可以大于-bg指定的图片数目

  • numStages 训练分类器的级数,默认20级,一般在14-25层之间均可。
    如果层数过多,分类器的fals alarm就更小,但是产生级联分类器的时间更长,分类器的hitrate就更小,检测速度就慢。如果正负样本较少,层数没必要设置很多。

  • precalcValBufSize 缓存大小,用于存储预先计算的特征值,单位MB

  • precalcIdxBufSize 缓存大小,用于存储预先计算的特征索引,单位MB

  • baseFormatSave 仅在使用Haar特征时有效,如果指定,级联分类器将以老格式存储

  • stageType 级联类型,{ CC_BOOST }

  • featureType 特征类型,目前只支持LBP、HOG、Haar三种特征。但是HAAR训练非常非常的慢,而LBP则相对快很多,因为HAAR需要浮点运算,精度自然比LBP更高,但是LBP的效果也基本能达到HAAR的效果,推荐使用LBP。

  • w,h 训练样本的尺寸,必须跟使用opencv_createsamples创建的训练样本尺寸保持一致,并且-w和-h的比例必须符合真实目标的比例.

  • bt Boosted分类器类型,{DAB-discrete Adaboost, RAB-RealAdaboost, LB-LogiBoost, GAB-Gentle Adaboost}

  • minHitRate 分类器的每一级希望得到的最小检测率,总的最大检测率大约为min_hit_rate^number_of_stages

  • maxFalseAlarmRate 分类器的每一级希望得到的最大误检率,总的误检率大约为max_false_rate^number_of_stages

  • weightTrimRate Specifies whether trimming should beused and its weight. 一个还不错的数值是0.95

  • maxDepth 弱分类器的最大深度,一个不错数值是1,二叉树

  • maxWeightCount 每一级中弱分类器的最大数目

  • mode 训练过程使用的Haar特征类型,有BASIC/CORE/ALL三种特征组合待选的,默认情况为BASIC,三种情况下对应的特征选取分别如下:

使用opencv训练cascade分类器进行目标检测

5.目标检测

detectMultiScale()函数参数:

cv2.CascadeClassifier.detectMultiScale(image[, scaleFactor[, minNeighbors[, flags[, minSize[, maxSize]]]]]) → objects

cv2.CascadeClassifier.detectMultiScale(image, rejectLevels, levelWeights[, scaleFactor[, minNeighbors[, flags[, minSize[, maxSize[, outputRejectLevels]]]]]]) → objects

参数如下:

  • image: Matrix of the type CV_8U containing an image where objects are detected. 灰度图

  • objects:Vector of rectangles where each rectangle contains the detected object.

  • scaleFactor:Parameter specifying how much the image size is reduced at each image scale. 图像尺度参数,默认1.1

  • minNeighbors:Parameter specifying how many neighbors each candidate rectangle should have to retain it. 为每一个级联矩形应该保留的临近个数,默认为3,即至少有3次检测到目标,才认为是目标。

  • flags:Parameter with the same meaning for an old cascade as in the function cvHaarDetectObjects. It is not used for a new cascade.
    CV_HAAR_DO_CANNY_PRUNING,利用边缘检测来排除一些边缘很少或者很多的图像区域
    CV_HAAR_SCALE_IMAGE,按正常比例检测
    CV_HAAR_FIND_GIGGEST_OBJECT,只检测最大的物体
    CV_HAAR_DO_ROUGH_SEARCH,只做粗略检测,默认值为0

  • minSize – Minimum possible object size. Objects smaller than that are ignored.

  • maxSize – Maximum possible object size. Objects larger than that are ignored.

使用opencv训练cascade分类器进行目标检测
import cv2, time
import numpy as np
import os.path

def get_hw_by_short_size(im_height, im_width, resize):
    short_size, max_size = resize
    im_size_min = np.min([im_height, im_width])
    im_size_max = np.max([im_height, im_width])
    scale = (short_size + 0.0) / im_size_min
    if scale * im_size_max > max_size:
        scale = (max_size + 0.0) / im_size_max

    resized_height, resized_width = int(round(im_height * scale)), int(
        round(im_width * scale))
    return resized_height, resized_width


class car_detector:

    def __init__(self, cascade_file, max_detect_hw=(400, 600)):
        if not os.path.isfile(cascade_file):
            raise RuntimeError("%s: not found" % cascade_file)

        self._cascade = cv2.CascadeClassifier(cascade_file)
        self._max_detect_hw = max_detect_hw

    def detect_image(self, image):
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        gray = cv2.equalizeHist(gray)
        cars = self._cascade.detectMultiScale(gray, scaleFactor=1.3, minNeighbors=15, minSize=(60, 60))

        for (x, y, w, h) in cars:
            cv2.rectangle(image, (x, y), (x + w, y + h), (0, 0, 255), 2)

        return image

    def detect_video(self, video_path, start_frame, end_frame, ):

        cap = cv2.VideoCapture(video_path)
        cap.set(cv2.CAP_PROP_POS_FRAMES, start_frame)
        org_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        org_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
        h, w = get_hw_by_short_size(org_h, org_w, self._max_detect_hw)

        while (start_frame < end_frame):
            start_frame += 1
            ret, image = cap.read()
            if not ret: return

            resized_img = cv2.resize(image, (w, h), interpolation=cv2.INTER_CUBIC)
            result = self.detect_image(resized_img)
            cv2.imshow("Detect", result)
            cv2.waitKey(1)

if __name__ == "__main__":  
    car_cascade_lbp_21 = './train/xml/cascade.xml'
    video_path = "./test.mp4"
    start_frame = 0
    end_frame = 300
 
    detect = car_detector(car_cascade_lbp_21) 
    detect.detect_video(video_path, start_frame, end_frame)

总结

车辆检测,在训练阶段:

HOG特征
正样本尺寸303030*30,训练速度非常快,结果不收敛。
正样本尺寸646464*64,训练速度较快,结构收敛。
但是, OpenCV 3.x 中, CascadeClassifier方法不支持 HOG特征。

HAAR特征
正样本尺寸202020*20,训练速度非常慢,结果不收敛。

LBP特征
正样本尺寸303030*30,训练速度较快,结果收敛。