作者:冰不语
来源:公众号「CVPy」
前言
首先需要说明,
这里所说的数字识别不是手写数字识别!
但凡对机器学习有所了解的人,相信看到数字识别的第一反应就是MNIST。MNIST是可以进行数字识别,但是那是手写数字。我们现在要做的是要识别从九宫格图片中提取出来的印刷体的数字。手写数字集训练出来的模型用来识别印刷体数字,显然不太专业。而且手写体跟印刷体相差不小,我们最看重的正确率问题不能保证。
本文从零开始做一遍数字识别,展示了数字识别的完整流程。从收集数据开始,到数据预处理,再到训练KNN,最后进行数字识别。
我们一步一步来说。
数据收集
为了便于处理,我百度找到了10张下面这样按照1-9-0顺序排列的图片,作为我们的初始数据集。
有的图片可能本来除数字区域外,周围空白部分比较多。为了便于处理,首先用windows自带的画图软件把图片裁剪成上面这样只包含数字区域的样子。
这十张数据集基本涵盖了印刷数字体的不同样式、字体,而且颜色、背景甚至渐变方式都各不相同。
数据处理
显然,我们第一步要做的就是上一节的内容,那就是把图片中的数字分别提取出来。
训练knn,还有其他任何有监督的机器学习模型,不光要有样本数据,还要有知道每一个样本对应的标签。这也是为什么我要选择上面这样按顺序排列的数字图片。
提取数字之后,我们可以对每一个数字的位置进行排序,然后根据位置信息可以知道每一个数字是几。标签也就由此生成了。
这一部分的内容可以分两部分来说,第一部分就是提取数字,第二部分是提取数字之后的数据预处理。
1.提取数字
提取数字的处理流程与上一篇内容差不多:
1.遍历文件夹下的原始数字图片;
2.对每一张图片进行轮廓提取操作,只提取外围轮廓(参考上一节讲解);
img_path = gb.glob("numbers\\*")
k = 0
labels = []
samples = []
for path in img_path:
img = cv2.imread(path)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray,(5,5),0)
thresh
= cv2.adaptiveThreshold(blur,255,1,1,11,2)
image, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
3.求轮廓外包矩形,并根据矩形大小信息筛选出所有的数字轮廓;
4.然后根据位置信息对数字框排序,显然第一排依次是12345,第二排依次是67890;
height,width = img.shape[:2]
w = width/5
rect_list = []
list1 = []
list2 = []
for cnt in contours:
#if cv2.contourArea(cnt)>100:
[x,y,w,h] = cv2.boundingRect(cnt
)
if w>30 and h > (height/4):
if y < (height/2):
list1.append([x,y,w,h])
else:
list2.append([x,y,w,h])
list1_sorted = sorted(list1,key = lambda t : t[0])
list2_sorted = sorted(list2,key = lambda t : t[0])
5.提取出每一个数字所在的矩形框,作为ROI取出。
for i in range(5):
[x1,y1,w1,h1] = list1_sorted[i]
[x2,y2,w2,h2] = list2_sorted[i]
number_roi1 = gray[y1:y1+h1, x1:x1+w1] #Cut the frame to size
number_roi2 = gray[y2:y2+h2, x2:x2+w2] #Cut the frame to size
数据预处理
为了加快训练速度,我们不用原图作为输入,而是对每一个数字原图做一定的处理。此处可选方案很多,提取特征有很多经典特征可选,也可以是自己设计的特征。
这里我用比较简单的方法,把每一张数字图片ROI转换为二值图像。大致流程是这样的:
1.把每一张ROI大小统一变换为40 x 20。
2.阈值分割。
resized_roi1=cv2.resize(number_roi1,(20,40))
thresh1 = cv2.adaptiveThreshold(resized_roi1,255,1,1,11,2)
resized_roi2=cv2.resize(number_roi2,(20,40))
thresh2 = cv2.adaptiveThreshold(resized_roi2,255,1,1,11,2)
3.把二值图像转换为0-1二值图像。
4.把处理完的数字图片保存到对应数字的文件夹中。(此为中间过程,可注释掉)
number_path1 = "number\\%s\\%d"
% (str(i+1),k) + '.jpg'
j = i+6
if j ==10:
j = 0
number_path2 = "number\\%s\\%d" % (str(j),k) + '.jpg'
k+=1
normalized_roi1 = thresh1/255.
normalized_roi2 = thresh2/255.
cv2.imwrite(number_path1,thresh1)
cv2.imwrite(number_path2,thresh2)
处理完之后保存的文件夹如下:
每一个文件夹里面类似这样,可以看到背景有黑有白,数字也是有黑有白:
5.把处理完的二值图像展开成一行。
6.最后把展开成的一行行样本保存起来作为训练用的数据。
7.对应的,把数字标签按照数字的保存顺序对应保存成训练用的数据。
sample1 = normalized_roi1.reshape((1,800))
samples.append(sample1[0])
labels.append(float(i+1))
sample2 = normalized_roi2.reshape((1,800))
samples.append(sample2[0])
labels.append(float(j))
import numpy as np
samples = np.array(samples,np.float32)
labels