[到手飞] 用OpenCV的级联分类器一键训练自己的目标检测数据集
Posted Z_MiCT
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[到手飞] 用OpenCV的级联分类器一键训练自己的目标检测数据集相关的知识,希望对你有一定的参考价值。
最近需要编写一个目标检测项目,本来用的是YOLO框架,结果一通大刀阔斧自信满满的“优化”再加上长达3个小时的高强度训练后,十个测试样本能检测到一个都算走运了。迫于无奈与时间紧迫,试了一下OpenCV的级联分类器,没想到效果虽谈不上惊人但绝对不算差。关键时刻还得是OpenCV靠谱。
训练步骤:
1.基础文件准备:
需要创建一个文件夹,并且该文件夹下至少存在如下文件:
附上完整下载地址(免积分):
其中
# opencv_traincascade.exe 并不依赖于python所安装的opencv版本(例如这里的训练器版本源自 OpenCV 4.5.2 而我python环境下的OpenCV版本为 3.4.1)
# pos 文件夹下存放正样本,neg文件夹下存放负样本。其中正样本长宽比最好一致,因为后面算法会自动下采样至统一尺寸,长宽比保持一直可尽可能保证特征不失真,负样本尺寸无限制。同时正负样本数最好保证在1:3左右。存放样例如下图所示(图片文件名随意):
# xml文件夹存在即可
2.开始训练
附上 train.py 代码:
import cv2
import os
class train_xml:
def __init__(self):
self.work_path = os.getcwd()
# 正样本目标尺度
self.aim_w = '28'
self.aim_h = '28'
# 样本数记录数据
self.pos_num = 0
self.neg_num = 0
def generate_txt(self):
# 生成正样本txt数据说明以及规范数据格式
write_str = ''
for root, dirs, files in os.walk(self.work_path + '\\\\pos'): # 工作目录, 子目录, 文件
for img_name in files:
img = cv2.imread('pos\\\\' + img_name)
h, w = img.shape[:2]
if w < int(self.aim_w) or h < int(self.aim_h): # 尺寸过小的样本将被舍弃
pass
else:
write_str += 'pos\\\\' + img_name + ' 1 0 0 ' + str(w) + ' ' + str(h) + '\\n'
self.pos_num += 1
del img
with open('pos.txt', 'w') as result_file:
result_file.write(write_str)
# 生成负样本txt数据说明以及规范数据格式
write_str = ''
for root, dirs, files in os.walk(self.work_path + '\\\\neg'): # 工作目录, 子目录, 文件
for img_name in files:
write_str += 'neg\\\\' + img_name + '\\n'
self.neg_num += 1
with open('neg.txt', 'w') as result_file:
result_file.write(write_str[:-1])
# 生成正样本vec数据文件以及打印当前命令
cmd = 'opencv_createsamples.exe -info ' + self.work_path + '\\\\pos.txt -vec pos.vec -num ' +\\
str(self.pos_num) + ' -w ' + self.aim_w + ' -h ' + self.aim_h
print('command: ', cmd)
os.system(cmd)
def start_train(self, batch_size=48):
# 这里只是类似于batch size, 即为每一级分类器所用到的正样本数, 设置的数量要小于总体正样本数, 太大会报错
# 生成批处理bat数据文件
if worker.pos_num <= batch_size:
pos_use = worker.pos_num
else:
pos_use = batch_size
if 3 * pos_use >= self.neg_num:
neg_use = self.neg_num
else:
neg_use = 3 * pos_use
write_str = 'opencv_traincascade -data xml -vec pos.vec -bg ' + self.work_path + '\\\\neg.txt ' +\\
'-numStages 20 -featureType LBP ' + '-minHitRate 0.996 -maxFalseAlarmRate 0.25 -mode ALL ' +\\
'-w ' + self.aim_w + ' -h ' + self.aim_h +\\
' -numPos ' + str(pos_use) + ' -numNeg ' + str(neg_use) + '\\n\\npause'
with open('start.bat', 'w') as result_file:
result_file.write(write_str)
# 输入1开始训练
continue_switch = int(input('\\nFile writing completed. Continue? (0 & 1)\\necho: '))
if continue_switch:
cmd = 'start.bat'
print('\\ncommand: ', cmd)
os.system(cmd)
if __name__ == '__main__':
worker = train_xml()
worker.generate_txt()
worker.start_train()
路径等参数都已自动获取写入,需要自定义的参数如下:
# 正样本目标尺度
self.aim_w = '228'
self.aim_h = '228'
# 这决定了最终训练时的输入图像大小,尽量与训练的正样本保持纵横比一直,需要从两个方面考虑:
一方面是硬件的的RAM限制,这里由于我的笔记本RAM是16G,在大约在230多的像素时内存占用就将近90%,所以这东西真的很吃内存;
二是使用时实际环境的输入图片中目标像素大小的限制,而并非这个训练的正样本尺寸越大越清晰就实际使用效果越好。因为最终检测时大多是将输入图像以不同尺度的下采样,再以当前这个目标尺度作为滑动窗口去目标检测。所以这个目标尺度最好包含在实际待检测图片中的实际物体尺寸除以缩放比例组(例如1.1、1.1^2 ...)所组成的尺度组中。可参考下图(图片来源自网络):
# 生成批处理bat数据文件
write_str = 'opencv_traincascade -data xml -vec pos.vec -bg ' + self.work_path + '\\\\neg.txt ' +\\
'-numStages 20 -featureType LBP ' + '-minHitRate 0.996 -maxFalseAlarmRate 0.25 -mode ALL ' +\\
'-w ' + self.aim_w + ' -h ' + self.aim_h +\\
' -numPos ' + str(self.pos_num) + ' -numNeg ' + str(self.neg_num) + '\\n\\npause'
with open('start.bat', 'w') as result_file:
result_file.write(write_str)
# 以及一些训练参数需要视具体场景调整(如偏重纹理特征则使用LBP训练),这里就不再做解释,具体原理参见其他文章:
https://spacevision.blog.csdn.net/article/details/82012519https://spacevision.blog.csdn.net/article/details/82012519# 训练样例(就很费内存):
同时貌似该训练器运算是单线程,即在我8核心16线程的CPU下,CPU占用率稳定下来占用率也只达到百分之十几,这可能也正是它最遗憾的缺陷之一。
3.完成测试
先附上测试代码:
import numpy as np
import cv2
class Static_detection:
def __init__(self):
# 加载分类器
self.my_Cascade = cv2.CascadeClassifier('cascade.xml')
self.img = None
def detect(self, generate_preview=False):
if self.img is not None:
result = []
aim = self.my_Cascade.detectMultiScale(self.img,
scaleFactor=1.1,
minNeighbors=8,
minSize=(28, 28))
# 生成预览
if generate_preview:
for (x, y, w, h) in aim:
cv2.rectangle(self.img, (x, y), (x + w, y + h), (255, 0, 0), 2)
result.append([x, y, w, h])
result = np.array(result)
cv2.imshow('detection', self.img)
cv2.waitKey(0)
print('locations are: ', result)
return result
# 直接返回 Bounding Box
else:
for (x, y, w, h) in aim:
result.append([x, y, w, h])
result = np.array(result)
return result
if __name__ == '__main__':
eagle_eye = Static_detection()
eagle_eye.img = cv2.imread('test.jpg', 1)
eagle_eye.img = cv2.resize(eagle_eye.img, None, fx=0.121, fy=0.121, interpolation=cv2.INTER_AREA) # 对输入图像进行放缩, 最好是和输入的负样本背景图等大
eagle_eye.detect(generate_preview=True)
将该py文件置于xml文件夹内即可,测试效果:
这里应项目要求是识别某几个大臭蛾子,样本较多时识别效果还不错。
训练完成后xml文件夹下会出现如上文件,除了两个标红的文件(其中cascade.xml正是训练结果文件)建议保留外,其他都是临时文件可以删除,这些临时文件只是帮助你在训练中断后恢复训练进度时用的。
训练参数可以很大的影响识别精度,所以需要耐心调教。
You Only need to Click Once
Thanks !
以上是关于[到手飞] 用OpenCV的级联分类器一键训练自己的目标检测数据集的主要内容,如果未能解决你的问题,请参考以下文章