基于HOG+SVM的猫咪识别器
目的
使用HOG+SVM算法和OpenCV实现一个图片分类器,通过自己训练分类器,达到可以判断任意图片是否是猫咪的效果
实验环境
python2.7
win10
实验知识点
-HOG+SVM分类器的基本原理
-OpenCV处理图片
-训练分类器,以得到适合自己项目的分类器
-Python文件操作
下面会首先介绍一下OpenCV,HOG,SVM的相关知识
-
Opencv
1.读取图片
import cv2
img_path = 'test.jpg'
image = cv2.imread(filename=img_path)
#窗口默认一直处于弹出窗状态
cv2.waitKey()
#按任意键盘,销毁窗口
cv2.destroyAllWindows()
2.图片数据类型
图像是由像素组成的,而像素实际上就是带有坐标位置和颜色信息的点。我们把图片想象成由若干行,若干列的点组成的, 现实中有RGB颜色系统,我们可以把图中任意一点(位置在第m行,第n列)的点A表示为
A[m,n] = [blue,green,red]
我们可以看下image的shape
800行,480列的点组成的图片,RGB三色,图片为彩色
>>>print(image.shape)
>>>(800,480,3)
3.裁剪图片大小
这里介绍裁剪图片大小的方法,由于输入到分类器的图片都是固定像素的,所以我们需要对下载的图片数据进行处理,使其符合我们程序的要求。将大图片裁减成固定像素的小图片的程序如下:
# -*- coding: utf-8 -*-
'''
这段代码会扫描Python脚本所在的文件夹的子文件夹other文件夹下的所有.jpg文件,
然后使用OpenCV读取图片数据,
并按照指定的大小进行缩放,
将缩放后的结果写入到指定目录下的指定图片中。
'''
import numpy as np
import cv2
from os.path import dirname,join,basename
from glob import glob
'''
glob参考http://python.jobbole.com/81552/
os.path.join参考http://www.cnblogs.com/jsplyy/p/5634640.html
'''
num = 0
#dirname(__file__)参考http://blog.csdn.net/lxjames833539/article/details/5251608
for i in glob(join(dirname(__file__)+'\dog','*.jpg')):
img = cv2.imread(i)
'''
interpolation即插值法
INTER_AREA - 使用像素区域关系进行重采样。
它可能是图像抽取的首选方法,因为它会产生无云纹理的结果。
但是当图像缩放时,它类似于INTER_NEAREST方法
'''
res = cv2.resize(img,(194,259),interpolation = cv2.INTER_AREA)
cv2.imwrite('E:/test/'+str(num)+'.jpg',res)
num +=1
print('all done')
##窗口默认一直处于弹出窗状态
cv2.waitKey(0)
##按任意键盘,销毁窗口
cv2.destroyAllWindows()
-
HOG
https://www.leiphone.com/news/201708/ZKsGd2JRKr766wEd.html
-
SVM
实验过程
SVM是一个由分类超平面定义的判别分类器。也就是说给定一组带标签的训练样本,算法将会输出一个最优超平面对新样本(测试样本)进行分类。
这也是监督类型机器学习的特点,即,把一堆带有标签的数据输入到机器中,让机器根据给定的数据计算出规则,再利用这个规则,去对未知数据进行分类。说白了,就是先积累几年工作经验,然后去工作。
本实验是读入输入图片的灰度图,即黑白的。然后计算该图片的hog值,将计算得到的结果作为向量来代表该图片。对由很多张图片组成的向量集进行计算,找到最大间距的分类超平面,进而分类数据。
hog的全称是Histogram of Oriented Gradient, HOG,即方向梯度直方图。它是一种在计算机视觉和图像处理中用来进行物体检测的特征描述子。它通过计算和统计图像局部区域的梯度方向直方图来构成特征。Hog特征结合SVM分类器已经被广泛应用于图像识别中,尤其在行人检测中获得了极大的成功。HOG+SVM进行行人检测的方法是法国研究人员Dalal在2005的CVPR上提出的,今天的很多行人检测算法基本都是以HOG+SVM的思路。
主要思想
在一副图像中,局部目标的表象和形状(appearance and shape)能够被梯度或边缘的方向密度分布很好地描述。(本质:梯度的统计信息,而梯度主要存在于边缘的地方)。
具体的实现方法是:首先将图像分成小的连通区域,我们把它叫细胞单元。然后采集细胞单元中各像素点的梯度的或边缘的方向直方图。最后把这些直方图组合起来就可以构成特征描述器。
提高性能:把这些局部直方图在图像的更大的范围内(我们把它叫区间或block)进行对比度归一化(contrast-normalized),所采用的方法是:先计算各直方图在这个区间(block)中的密度,然后根据这个密度对区间中的各个细胞单元做归一化。通过这个归一化后,能对光照变化和阴影获得更好的效果。
优点:与其他的特征描述方法相比,HOG有很多优点。首先,由于HOG是在图像的局部方格单元上操作,所以它对图像几何的和光学的形变都能保持很好的不变性,这两种形变只会出现在更大的空间领域上。其次,在粗的空域抽样、精细的方向抽样以及较强的局部光学归一化等条件下,只要行人大体上能够保持直立的姿势,可以容许行人有一些细微的肢体动作,这些细微的动作可以被忽略而不影响检测效果。因此HOG特征是特别适合于做图像中的人体检测的。
步骤
首先,我们根据已经分类好的数据集来对分类器进行训练。我们的cat文件夹下全是猫的照片,而other文件夹下全不是猫,已经完成了贴标签这个过程了。让程序从这两组数据里学习,计算分类的方法。
使用HOG+SVM算法进行训练前,需要先计算每张图片的HOG值以得到供SVM分类器使用的输入向量。计算该值的算法实现的一般过程为:
灰度化(OpenCV处理图像时,一般都处理为灰度图像,忽略颜色干扰)
采用Gamma校正法对输入图像进行颜色空间的标准化(归一化);目的是调节图像的对比度,降低图像局部的阴影和光照变化所造成的影响,同时可以抑制噪音的干扰;
计算图像每个像素的梯度(包括大小和方向);主要是为了捕获轮廓信息,同时进一步弱化光照的干扰。
将图像划分成小cells(例如66像素/cell);
统计每个cell的梯度直方图(不同梯度的个数),即可形成每个cell的descriptor;
将每几个cell组成一个block(例如33个cell/block),一个block内所有cell的特征descriptor串联起来便得到该block的HOG特征descriptor。
将图像image内的所有block的HOG特征descriptor串联起来就可以得到该image(你要检测的目标)的HOG特征descriptor了。这个就是最终的可供分类使用的特征向量了。
在本实验中,没有严格按照上述的过程实现,我们采用了下述方法:我们在每个cell内计算X和Y方向的Sobel导数。然后找到每个像素的梯度和方向。此梯度被量化为16*16个整数值。把每个图像分成四个子图方块。对于每个子正方形,计算加权其幅度的方向(16*16bins)的直方图。因此,每个子图给我们一个包含16*16个值的向量。四个这样的向量(分别代表四个子图的16*16向量)一起给我们一个特征向量包含1024个值。这就是我们用来训练数据的特征向量。这部分的代码如下所示:
(注:这段使用了Python3.6)
bin_n = 16*16 # Number of bins
def hog(img):
x_pixel,y_pixel=194,259
gx = cv2.Sobel(img, cv2.CV_32F, 1, 0)
gy = cv2.Sobel(img, cv2.CV_32F, 0, 1)
#cartToPolar函数参照 http://www.hahack.com/wiki/opencv-image.html
mag, ang = cv2.cartToPolar(gx, gy)
bins = np.int32(bin_n*ang/(2*np.pi)) # quantizing binvalues in (0...16)
bin_cells = bins[:(int)(x_pixel/2),:(int)(y_pixel/2)], bins[(int)(x_pixel/2):,:(int)(y_pixel/2)], bins[:(int)(x_pixel/2),(int)(y_pixel/2):], bins[(int)(x_pixel/2):,(int)(y_pixel/2):]
mag_cells = mag[:(int)(x_pixel/2),:(int)(y_pixel/2)], mag[(int)(x_pixel/2):,:(int)(y_pixel/2)], mag[:(int)(x_pixel/2),(int)(y_pixel/2):], mag[(int)(x_pixel/2):,(int)(y_pixel/2):]
#bincount()统计出现的次数 参照http://blog.csdn.net/lanchunhui/article/details/50491632
#ravel()参照http://blog.csdn.net/lanchunhui/article/details/50354978
hists = [np.bincount(b.ravel(), m.ravel(), bin_n) for b, m in zip(bin_cells, mag_cells)]
#hstack()返回结果为numpy的数组
hist = np.hstack(hists) # hist is a 64 bit vector
# print hist.shape
# print type(hist)
return hist
完整的代码如下所示,程序首先扫描cat和other文件夹内的图片,然后用灰度方式读入,计算每个图片的hog值,然后建立SVM分类器,使用输入的数据进行训练,将训练结果保存于svm_cat_data.dat文件中。
# -*- coding: utf-8 -*-
import numpy as np
import cv2
#from matplotlib import pyplot as plt
from os.path import dirname, join, basename
import sys
from glob import glob
bin_n = 16*16 # Number of bins
def hog(img):
x_pixel,y_pixel=194,259
gx = cv2.Sobel(img, cv2.CV_32F, 1, 0)
gy = cv2.Sobel(img, cv2.CV_32F, 0, 1)
mag, ang = cv2.cartToPolar(gx, gy)
bins = np.int32(bin_n*ang/(2*np.pi)) # quantizing binvalues in (0...16)
bin_cells = bins[:x_pixel/2,:y_pixel/2], bins[x_pixel/2:,:y_pixel/2], bins[:x_pixel/2,y_pixel/2:], bins[x_pixel/2:,y_pixel/2:]
mag_cells = mag[:x_pixel/2,:y_pixel/2], mag[x_pixel/2:,:y_pixel/2], mag[:x_pixel/2,y_pixel/2:], mag[x_pixel/2:,y_pixel/2:]
hists = [np.bincount(b.ravel(), m.ravel(), bin_n) for b, m in zip(bin_cells, mag_cells)]
hist = np.hstack(hists) # hist is a 64 bit vector
# print hist.shape
# print type(hist)
return hist
#print glob(join(dirname(__file__)+'/cat','*.jpg'))
img={}
num=0
for fn in glob(join(dirname(__file__)+'/cat', '*.jpg')):
img[num] = cv2.imread(fn,0)#参数加0,只读取黑白数据,去掉0,就是彩色读取。
# print img[num].shape
num=num+1
print num,' num'
print 'the file path is ', dirname(__file__)
positive=num
for fn in glob(join(dirname(__file__)+'/other', '*.jpg')):
img[num] = cv2.imread(fn,0)#参数加0,只读取黑白数据,去掉0,就是彩色读取。
# print img[num].shape
num=num+1
print num,' num'
print positive,' positive'
trainpic=[]
for i in img:
# print type(i)
trainpic.append(img[i])
svm_params = dict( kernel_type = cv2.SVM_LINEAR,
svm_type = cv2.SVM_C_SVC,
C=2.67, gamma=5.383 )
#img = cv2.imread('02.jpg',0)
#hist_full = cv2.calcHist([img],[0],None,[256],[0,256])
#print hist_full
#plt.plot(hist_full)
#plt.show()
#img1 = cv2.imread('02.jpg',0)
#temp=img[0].ravel()
#print temp
#print len(temp)
temp=hog(img[0])
print temp.shape
#hogdata = [map(hog,img[i]) for i in img]
hogdata = map(hog,trainpic)
print np.float32(hogdata).shape,' hogdata'
trainData = np.float32(hogdata).reshape(-1,bin_n*4)
print trainData.shape,' trainData'
responses = np.float32(np.repeat(1.0,trainData.shape[0])[:,np.newaxis])
responses[positive:trainData.shape[0]]=-1.0
print responses.shape,' responses'
print len(trainData)
print len(responses)
print type(trainData)
svm = cv2.SVM()
svm.train(trainData,responses, params=svm_params)
svm.save('svm_cat_data.dat')
机器学习是一个不断迭代的过程。训练的数据集越大越好,训练时间当然也是越长效果越好。当机器认错了图片的时候,我们要把这个图片拿出来,标记正确,输入机器再训练一遍,如此迭代下去。本实验只训练了一次以演示原理。
我们得到了训练好的数据svm_cat_data.dat后,可以用它来分类测试图片。
建立程序如下:
# -*- coding: utf-8 -*-
import numpy as np
import cv2
#from matplotlib import pyplot as plt
from os.path import dirname, join, basename
import sys
from glob import glob
bin_n = 16*16 # Number of bins
def hog(img):
x_pixel,y_pixel=194,259
gx = cv2.Sobel(img, cv2.CV_32F, 1, 0)
gy = cv2.Sobel(img, cv2.CV_32F, 0, 1)
mag, ang = cv2.cartToPolar(gx, gy)
bins = np.int32(bin_n*ang/(2*np.pi)) # quantizing binvalues in (0...16)
bin_cells = bins[:x_pixel/2,:y_pixel/2], bins[x_pixel/2:,:y_pixel/2], bins[:x_pixel/2,y_pixel/2:], bins[x_pixel/2:,y_pixel/2:]
mag_cells = mag[:x_pixel/2,:y_pixel/2], mag[x_pixel/2:,:y_pixel/2], mag[:x_pixel/2,y_pixel/2:], mag[x_pixel/2:,y_pixel/2:]
hists = [np.bincount(b.ravel(), m.ravel(), bin_n) for b, m in zip(bin_cells, mag_cells)]
hist = np.hstack(hists) # hist is a 64 bit vector
# print hist.shape
# print type(hist)
return hist
#print glob(join(dirname(__file__)+'/cat','*.jpg'))
img={}
num=0
for fn in glob(join(dirname(__file__)+'/cat', '*.jpg')):
img[num] = cv2.imread(fn,0)#参数加0,只读取黑白数据,去掉0,就是彩色读取。
# print img[num].shape
num=num+1
print num,' num'
positive=num
for fn in glob(join(dirname(__file__)+'/other', '*.jpg')):
img[num] = cv2.imread(fn,0)#参数加0,只读取黑白数据,去掉0,就是彩色读取。
# print img[num].shape
num=num+1
print num,' num'
print positive,' positive'
trainpic=[]
for i in img:
# print type(i)
trainpic.append(img[i])
svm_params = dict( kernel_type = cv2.SVM_LINEAR,
svm_type = cv2.SVM_C_SVC,
C=2.67, gamma=5.383 )
temp=hog(img[0])
print temp.shape
#hogdata = [map(hog,img[i]) for i in img]
hogdata = map(hog,trainpic)
print np.float32(hogdata).shape,' hogdata'
trainData = np.float32(hogdata).reshape(-1,bin_n*4)
print trainData.shape,' trainData'
#trainData.shape[0] = 89
#重新定义一个89行,转置为89列,向量转置np.newaxis,每个都赋值为1.0
responses = np.float32(np.repeat(1.0,trainData.shape[0])[:,np.newaxis])
# print responses
responses[positive:trainData.shape[0]]=-1.0
# print trainData
print responses.shape,' responses'
print len(trainData)
print len(responses)
print type(trainData)
svm = cv2.SVM()
svm.load('svm_cat_data.dat')
img = cv2.imread('E:/Python/cat/predict/05.jpg',0)
#print img.shapes,' img_test0'
hogdata = hog(img)
testData = np.float32(hogdata).reshape(-1,bin_n*4)
print testData.shape,' testData'
result = svm.predict(testData)
print result
if result > 0:
print 'this pic is a cat!'
test_temp=[]
for fn in glob(join(dirname(__file__)+'/predict', '*.jpg')):
img=cv2.imread(fn,0)#参数加0,只读取黑白数据,去掉0,就是彩色读取。
test_temp.append(img)
print len(test_temp),' len(test_temp)'
hogdata = map(hog,test_temp)
testData = np.float32(hogdata).reshape(-1,bin_n*4)
print testData.shape,' testData'
result = [svm.predict(eachone) for eachone in testData]
print result