调整基于HAAR特征的AdaBoost级联分类器的物体识别的参数
1. 基于HAAR特征的AdaBoost级联分类器的物体识别问题
很多训练好的XML文件不好用。
2. 调整参数的意义
既然训练好的XML文件不好用,是不是意味着要重新训练分类器?如果需要检测的物体在OpenCV中有,那么尽量用OpenCV中自带的分类器。因为自带的分类器包含了很多工作人员的心血,绝对不会那么轻易不好用的。所以根据需要根据实际情况查找问题。主要问题包括:图像分辨率,参数设置和物体摆放的位姿。
(1)图像分辨率
如果图像分辨率过低导致图像中的物体不清晰,甚至连人眼都看不清楚,当然检测不出来物体;如果图像分辨率过高,导致物体的大小超出分类器的尺寸,即分类器最多只能检测到物体的一部分,但检测不到整体,这样也不会检测出物体。
(2)参数设置
参数设置的内容会在调整参数的步骤中详细说明。
(3)物体摆放的位姿
举个极端的例子:分类器训练的样本都是物体的正面,而实际检测的为物体的反面,物体正面和反面又是截然不同的,所以也有可能检测不出物体。简单地说,训练的样本和测试的样本虽然是同一类物体,但在图像中表现得差异过大。有两种解决办法:数据库不完备,增加数据库样本重新训练;摆正样本。
3. 代码实现
有人声称用不到25行的代码实现人脸检测,笔者为说明问题,暂时不作代码长度的考虑。下面为笔者应用OpenCV库中检测人脸,左眼,右眼,鼻子,嘴巴等XML文件的实现代码。
import cv2
import numpy as np
from matplotlib import pyplot as plt
################################################################################
print 'Load Object Cascade Classifier'
faceCascade = cv2.CascadeClassifier('D:/OpenCV 2.4.9/opencv/sources/data/haarcascades/haarcascade_frontalface_default.xml')
lefteyeCascade = cv2.CascadeClassifier('D:/OpenCV 2.4.9/opencv/sources/data/haarcascades/haarcascade_mcs_lefteye.xml')
righteyeCascade = cv2.CascadeClassifier('D:/OpenCV 2.4.9/opencv/sources/data/haarcascades/haarcascade_mcs_righteye.xml')
noseCascade = cv2.CascadeClassifier('D:/OpenCV 2.4.9/opencv/sources/data/haarcascades/haarcascade_mcs_nose.xml')
mouthCascade = cv2.CascadeClassifier('D:/OpenCV 2.4.9/opencv/sources/data/haarcascades/haarcascade_mcs_mouth.xml')
################################################################################
print 'Load Image'
imgFile = 'images/face.jpg'
# load an original image
img = cv2.imread(imgFile)
# convert color space from bgr to gray
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
imgGray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
faces = faceCascade.detectMultiScale(imgGray, scaleFactor = 1.3, minNeighbors = 4, minSize = (60,60), maxSize = (300,300), flags = cv2.cv.CV_HAAR_SCALE_IMAGE)
for (x,y,w,h) in faces:
cv2.rectangle(img, (x,y), (x+w,y+h), (255,0,0), 2)
faceGray = imgGray[y:y+h, x:x+w]
faceColor = img[y:y+h, x:x+w]
'''
# for small Alice
lefteye = lefteyeCascade.detectMultiScale(faceGray, scaleFactor = 1.3, minNeighbors = 5, minSize = (20,20), maxSize = (80,80), flags = cv2.cv.CV_HAAR_SCALE_IMAGE)
righteye = righteyeCascade.detectMultiScale(faceGray, scaleFactor = 1.3, minNeighbors = 5, minSize = (20,20), maxSize = (80,80), flags = cv2.cv.CV_HAAR_SCALE_IMAGE)
# cannot search nose successfully!
nose = noseCascade.detectMultiScale(faceGray, scaleFactor = 1.1, minNeighbors = 0, minSize = (5,5), maxSize = (80,80), flags = cv2.cv.CV_HAAR_SCALE_IMAGE)
# mistaken eye for mouth!
mouth = mouthCascade.detectMultiScale(faceGray, scaleFactor = 1.05, minNeighbors = 10, minSize = (5,5), maxSize = (80,80), flags = cv2.cv.CV_HAAR_SCALE_IMAGE)
'''
'''
# for big Alice
lefteye = lefteyeCascade.detectMultiScale(faceGray, scaleFactor = 1.3, minNeighbors = 18, minSize = (40,40), maxSize = (80,80), flags = cv2.cv.CV_HAAR_SCALE_IMAGE)
righteye = righteyeCascade.detectMultiScale(faceGray, scaleFactor = 1.3, minNeighbors = 16, minSize = (40,40), maxSize = (80,80), flags = cv2.cv.CV_HAAR_SCALE_IMAGE)
nose = noseCascade.detectMultiScale(faceGray, scaleFactor = 1.3, minNeighbors = 10, minSize = (40,40), maxSize = (80,80), flags = cv2.cv.CV_HAAR_SCALE_IMAGE)
mouth = mouthCascade.detectMultiScale(faceGray, scaleFactor = 1.3, minNeighbors = 10, minSize = (40,40), maxSize = (80,80), flags = cv2.cv.CV_HAAR_SCALE_IMAGE)
'''
# for face
lefteye = lefteyeCascade.detectMultiScale(faceGray, scaleFactor = 1.3, minNeighbors = 18, minSize = (40,40), maxSize = (80,80), flags = cv2.cv.CV_HAAR_SCALE_IMAGE)
righteye = righteyeCascade.detectMultiScale(faceGray, scaleFactor = 1.3, minNeighbors = 16, minSize = (40,40), maxSize = (80,80), flags = cv2.cv.CV_HAAR_SCALE_IMAGE)
nose = noseCascade.detectMultiScale(faceGray, scaleFactor = 1.3, minNeighbors = 16, minSize = (40,40), maxSize = (80,80), flags = cv2.cv.CV_HAAR_SCALE_IMAGE)
mouth = mouthCascade.detectMultiScale(faceGray, scaleFactor = 1.3, minNeighbors = 10, minSize = (40,40), maxSize = (80,80), flags = cv2.cv.CV_HAAR_SCALE_IMAGE)
############################################################################
print 'Add Objects on Face'
for (ex,ey,ew,eh) in lefteye:
cv2.rectangle(faceColor, (ex,ey), (ex+ew,ey+eh), (0,255,0), 2)
for (ex,ey,ew,eh) in righteye:
cv2.rectangle(faceColor, (ex,ey), (ex+ew,ey+eh), (255,255,0), 2)
for (ex,ey,ew,eh) in nose:
cv2.rectangle(faceColor, (ex,ey), (ex+ew,ey+eh), (0,255,255), 2)
for (ex,ey,ew,eh) in mouth:
cv2.rectangle(faceColor, (ex,ey), (ex+ew,ey+eh), (0,100,100), 2)
################################################################################
print 'Display Face Image'
plt.subplot(1,1,1), plt.imshow(img), plt.title('Face Image'), plt.xticks([]), plt.yticks([])
plt.show()
################################################################################
print 'Goodbye!'
4. 调整参数的步骤
(1)默认参数:设置默认参数观察是否检测到物体;
(2)检测物体的范围:修改minSize(检测的对象最小尺寸,单位:像素*像素)和maxSize(检测对象的最大尺寸),使对象落在检测器的大小范围内;
(3)投票数:设置minNeighbor为0,观察所有的投票结果,投票结果最集中的位置为最终的检测结果。增加投票数滤除误检测的对象,检测到对象的条件越苛刻;减少投票数增加检测到对象的几率,检测到对象的条件越宽松;
(4)尺度:被检测对象的尺度变化,尺度变化的图像的集合可以构成图像金字塔。scaleFactor的合理的范围在1.1~1.4之间,尺度越大,越容易漏掉检测的对象,但检测速度加快;尺度越小,检测越细致准确,但检测速度变慢。
5. 实验结果
第一幅和第二幅人脸为标准的正脸,应该说检测的过程得还挺完美。
第三幅人脸检测结果也算是尽力了。刚开始用第二幅图的参数时,检测人脸的分类器会在左边的蜗牛壳那边检测出第二个人脸。通过调整minSize和maxSize才将背景的伪正结果给去掉了。接着是左眼的检测,大爱丽丝的左眼比小爱丽丝的左眼范围大太多,所以需要将左眼的minSize和maxSize调小。调整右眼的参数时,右眼的检测结果重叠在左眼上,我认为是左眼的位姿比较暧昧,同时满足了左右眼的检测标准。检测鼻子时,一直检测不到结果,因为肉眼查看图像发现小爱丽丝的鼻子为细长状,头微微低下使鼻尖处的宽度不够大。检测嘴巴时,嘴巴的投票全部在左眼上,确实小爱丽丝的嘴由于脸部微侧显得特别小,并且相比之下,左眼的形状也更像嘴巴。
结语
Paul Viola大神的文章只给出了大致的框架。本来希望自己写一遍这个实现的过程,但我只写完了积分图像和AdaBoost分类器。最后怎么结合还要查找更多的材料,但有可能就到此为止了,毕竟这个库函数调一调速度可以很快的。