猫图片识别

这是一个实验楼上的简单实例项目,要求使用opencv识别图片中是否有猫的存在。
基本步骤和代码都是来自实验楼,ipython notebook可以在这里查看。

方向梯度直方图(HOG)

这部分原文来自这里,此处笔者做了删减,如需详细了解,请查看原文。

什么是方向梯度直方图

在通过机器学习进行图像处理时,我们需要确定什么是有用信息,什么是不相关信息,当我们抽取了有用信息并移除不相关信息之后,我们就得到了特征描述子,并可以用于表示图像。具体到图像识别中,一般而言,边缘信息是有用信息,而颜色信息就是无用信息,因此,我们可以用边缘检测将图像转化成只有边缘的图像,这种特征描述子就是方向梯度直方图(HOG)。

在HOG中,梯度的方向分布被作为特征使用,主要沿图片的水平轴和垂直轴表示其梯度,在图形的边缘区域,这种梯度变化会比较大,因此可以包含图形的形状信息。

方向梯度直方图的计算方法

具体到计算中,我们首先需要明确的是,因为我们需要一个像素一个像素的研究每一个像素点的方向梯度,因此,首先将图片分割成一个一个小图片来进行计算和预处理会比较方便。为了方便起见,我们将分割后的小图片称为patch。

第一步,预处理

图片的尺寸是可以任意的,但是为了统一起见,其宽高比必须固定,比如统一规定为1:2,我们需要对图片进行切割和放大或缩小来是其大小复合比例需要。

第二部,计算梯度图像

计算梯度图像主要包括两个部分:

  1. 使用一些kernel比如Sobel算子(OpenCV)中提供Sobel算子函数,来计算X轴和Y轴的梯度
  2. 根据计算得到的两个梯度,来计算梯度的幅值g和方向$\theta$

使用Python的代码如下:

1
2
3
4
5
6
7
8
9
# 计算第一步的两个梯度值
im = cv2.imread('picName.png')
im = np.float32(im)

gx = cv2.Sobel(img, cv2.CV_32F, 1, 0, ksize=1)
gy = cv2.Sobel(img, cv2.CV_32F, 0, 1, ksize=1)

# 计算幅值和方向
mag, angle = cv2.cartToPolar(gx, gy, angleInDegree=True)

通过这些处理, 我们得到的梯度可以忽略掉很多不必要的信息,而加重轮廓,使我们更容易发现物体形状。

第三步,在8*8的网格中计算梯度直方图

在这里,我们假定分割后每次处理的图片含有8*8=64个像素点,则每个像素点都肯定存在一个幅值和方向那么,我们就有了两个对应的幅值矩阵和方向矩阵并与原图形上的像素点一一对应。我们接下来需要做的就是将幅值矩阵里面的元素按照方向角度将其分配到由9个bin构成的直方图中,比如,角度为20度的对应幅值应该属于20度所在的bin,将其幅值加到对应的直方图中,如果角度值并不恰好等于bin的度数,则按照比例进行线性分割,具体示例如下图

而之所以采用直方图来表示图像特征的原因则是其优越的抗噪效果。处理完这一步后,对于一个特定的图片分割部分,我们应该可以获得一个直方图如下:

第四步,16*16归一化

理论上,第三步得到的直方图已经可以用于描述图片的关键特征了,但是,我们也必须考虑到一个重要的影响因素—光照,在强光条件和弱光条件下,hog里面的幅值是不一样的,所以我们需要通过归一化的方式,消除光照的影响。

一个比较好的归一化方式是一次将几个分割后的图片部分各自的直方图组合在一起进行归一化,如下图

通过这种方式,我们一次归一化一个蓝色框中的所有直方图。

第五步,计算HOG特征向量

经过这几步,我们理论上已经得到了我们想要的HOG特征向量,接下来我们只需要将这些特征向量组合在一起,组成一个一维的由所有直方图一起构成的向量即可。

代码结构

这一段内容包括代码主要参考自这里

定义HOG函数

取决于读者使用的library,例如skimage中已经自带HOG函数,因此读者不需要自己来写这个函数

具体函数如下:

1
2
3
4
5
6
7
8
9
10
11
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))
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)
return hist

注意这里我们调用了cv2。Sobel以及cv2.cartToPolar两个函数。

如果使用skimage,那么调用代码如下:

1
2
3
4
from skimage import feature as ft
def myHog(img):
feature = ft.hog(img, orientations=8, pixels_per_cell=(16,16), cells_per_block=(1,1))
return feature

预处理图片

此处由于给定的图片已经是大小比例裁剪适当的,因此我们不需要对图片本身进行处理,只需要将图片读入到数据集中。

我们需要将图片分别读入到代表训练集和测试集的两个list中去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
img = []
# positive
for name in os.listdir(os.getcwd() + '/cat/'):
pic = cv2.imread(os.getcwd() + '/cat/' + name, 0)
img.append(pic)
positive = len(img)
print "positive samples: {}".format(positive)

# negative
for name in os.listdir(os.getcwd() + '/other/'):
pic = cv2.imread(os.getcwd() + '/other/' + name, 0)
img.append(pic)

print "negative samples: {}".format(len(img) - positive)

# predict
predict_img = []
for name in os.listdir(os.getcwd() + '/predict/'):
pic = cv2.imread(os.getcwd() + '/predict/' + name, 0)
predict_img.append(pic)

print "prediction samples: {}".format(len(predict_img))

预处理数据

将图片读入后,我们就要调用我们自己写的或者其他库中自带的hog函数来预处理每一张图片。

1
2
3
4
5
6
7
8
9
# 训练集
HOG_feature = map(myHog, img)
vector = np.asarray(HOG_feature)
# 给训练集标上label
label = np.array(np.repeat(1, vector.shape[0]))
label[positive:] = 0
# 测试集
predict_feature = map(myHog, predict_img)
predict_vector = np.asarray(predict_feature)

训练与预测

OpenCV中自带了SVM,所以我们可以使用其自带的SVM,也可以使用其他库比如sklearn中的SVM。

OpenCV的相关代码如下:

1
2
3
4
svm_params = dict(kernel_type = cv2.SVM_LINEAR, svm_type = cv2.SVM_C_SVC, C = 2.67, gamma = 5.383)
svm = cv2.SVM()
svm.train(trainData, responses, params=svm_params)
result = svm.predict(testData)

使用sklearn的相关代码

1
2
3
clf = SVC(kernel='linear', C=2.67, gamma=5.383)
clf.fit(vector, label)
predict_label = clf.predict(predict_vector)

注意,此处的参数是直接给出的,在实际操作中,我们通常需要进行调参。

总结

总的来说,整体内容并不多,因为数据量小、没有调参过程等等,只适合于对OpenCV和HOG并不了解的初学者练习尝试。