导读
对于人类来说,识别手写的数字是一件非常容易的事情。我们甚至不用思考,就可以看出下面的数字分别是1,2,3。
![](https://i-blog.csdnimg.cn/blog_migrate/9f0e39fdd7fe7a6ac7619a4ad08e6944.png)
本期将使用Tensorflow搭建卷积神经网络,进行手写数字的识别。代码可关注公众号 IamZLT ,后台回复「手写数字识别」即可免费获取。
本系列文章Part1:基于CNN的数字OCR识别part2:基于CNN的汉字识别
预处理
对于手写数字的初始图片如下所示。在搭建网络前我们需要对其进行预处理。
![](https://i-blog.csdnimg.cn/blog_migrate/0f77dfbbfaa60738a704673a97437294.png)
▌读入图片并进行二值化
图像二值化( Image Binarization)就是将图像上的像素点的灰度值设置为0或255,也就是将整个图像呈现出明显的黑白效果的过程。
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('s.png')
lowerb = (0,0,116)
upperb = (255,255,255)
back_mask = cv2.inRange(cv2.cvtColor(img, cv2.COLOR_BGR2HSV), lowerb, upperb)
cv2.imwrite(s2.png', back_mask)
![](https://i-blog.csdnimg.cn/blog_migrate/b62ff5457542a5bc34fb6f892f37843d.png)
▌腐蚀与反色
形态学图像处理是在图像中移动一个结构元素,然后将结构元素与下面的二值图像进行交、并等集合运算;先腐蚀后膨胀的过程称为开运算。
# 形态学操作, 圆形核腐蚀
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(3,3))
back_mask = cv2.erode(back_mask, kernel, iterations=1)
# 反色 变为数字的掩模
num_mask = cv2.bitwise_not(back_mask)
▌中值滤波
中值滤波法是一种非线性平滑技术,它将每一像素点的灰度值设置为该点某邻域窗口内的所有像素点灰度值的中值。
# 中值滤波
num_mask = cv2.medianBlur(num_mask,3)
cv2.imwrite('s3', num_mask)
中值滤波是基于排序统计理论的一种能有效抑制噪声的非线性信号处理技术,其基本原理是把数字图像或数字序列中一点的值用该点的一个邻域中各点值的中值代替,让周围的像素值接近的真实值,从而消除孤立的噪声点。
以上结果运行如下:
![](https://i-blog.csdnimg.cn/blog_migrate/99334a3cdb21e7da954c7c91a5bc2f8f.png)
图像分割
读入图片,进行二值化操作,通过外接矩形获得轮廓的位置信息,提取数字轮廓的图片,与索引组成轮廓信息的字典,循环轮廓,使用外接矩形的位置信息参数并获得分割数字。
寻找轮廓
bimg, contours, hier = cv2.findContours(num_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours_sort(contours)
canvas = cv2.cvtColor(num_mask, cv2.COLOR_GRAY2BGR)
def getStandardDigit(img):
STD_WIDTH = 32 # 标准宽度
STD_HEIGHT = 64
height,width = img.shape
# 判断是否存在长条的1
new_width = int(width * STD_HEIGHT / height)
if new_width > STD_WIDTH:
new_width = STD_WIDTH
# 以高度为准进行缩放
resized_num = cv2.resize(img, (new_width,STD_HEIGHT), interpolation = cv2.INTER_NEAREST)
# 新建画布
canvas = np.zeros((STD_HEIGHT, STD_WIDTH))
x = int((STD_WIDTH - new_width) / 2)
canvas[:,x:x+new_width] = resized_num
return canvas
minWidth = 5 # 最小宽度
minHeight = 20 # 最小高度
base = 1000 # 计数编号
imgIdx = base # 当前图片的编号
# 检索满足条件的区域
for cidx,cnt in enumerate(contours):
(x, y, w, h) = cv2.boundingRect(cnt)
if w < minWidth or h < minHeight:
# 如果不满足条件就过滤掉
continue
# 获取ROI图片
digit = num_mask[y:y+h, x:x+w]
digit = getStandardDigit(digit)
cv2.imwrite('./digits_bin/{}.png'.format(imgIdx), digit)
imgIdx+=1
# 原图绘制圆形
cv2.rectangle(canvas, pt1=(x, y), pt2=(x+w, y+h),color=(0, 255, 255), thickness=2)
cv2.imwrite('ss.png', canvas)
▌获取数字位置信息
![](https://i-blog.csdnimg.cn/blog_migrate/9224054bf3feadcff15ea678cb6aa256.png)
▌获取单张图片
![](https://i-blog.csdnimg.cn/blog_migrate/ac3a188764ce5dbe262cc912dd2ab838.png)
卷积神经网络
通过上述操作,我们可以对图片中的数字进行提取。在将图片传入网络前,我们需要进行网络的搭建。卷积神经网络(CNN)是一类包含卷积计算且具有深度结构的前馈神经网络,是深度学习的代表算法之一 。卷积神经网络具有表征学习能力,能够按其阶层结构对输入信息进行平移不变分类,因此也被称为“平移不变人工神经网络。
![](https://i-blog.csdnimg.cn/blog_migrate/78365f5ce7c782d4d57052366f78de55.png)
卷积神经网络与普通神经网络非常相似,它们都由具有可学习的权重和偏置常量(biases)的神经元组成。每个神经元都接收一些输入,并做一些点积计算,输出是每个分类的分数,普通神经网络里的一些计算技巧到这里依旧适用。
卷积神经网络利用输入图片的特点,把神经元设计成三个维度:width, height, depth。一个卷积神经网络由很多层组成,它们的输入是三维的,输出也是三维的,有的层有参数,有的层不需要参数。
▌核心代码
model = Sequential()
model.add(Conv2D(filters= 32, kernel_size=(5,5), padding='Same', activation='relu',input_shape=(28,28,1)))
model.add(Conv2D(filters= 32, kernel_size=(5,5), padding='Same', activation='relu'))
model.add(MaxPool2D(pool_size=(2,2)))
model.add(Dropout(0.25))
model.add(Conv2D(filters= 64, kernel_size=(3,3), padding='Same', activation='relu'))
model.add(Conv2D(filters= 64, kernel_size=(3,3), padding='Same', activation='relu'))
model.add(MaxPool2D(pool_size=(2,2), strides=(2,2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(256, activation='relu'))
model.add(Dropout(0.25))
model.add(Dense(10, activation='softmax'))
model.compile(loss='categorical_crossentropy',optimizer='adam',metrics=['accuracy'])
model.fit(x=x_Train_normalize,y=y_train,epochs = 10,batch_size = 200)
model.save('minst.h5')
▌迭代结果
Train on 60000 samples
Epoch 1/10
60000/60000 [==============================] - 207s 3ms/sample - loss: 0.2343 - accuracy: 0.9249
Epoch 2/10
60000/60000 [==============================] - 179s 3ms/sample - loss: 0.0600 - accuracy: 0.9818
Epoch 3/10
60000/60000 [==============================] - 182s 3ms/sample - loss: 0.0438 - accuracy: 0.9868
Epoch 4/10
60000/60000 [==============================] - 188s 3ms/sample - loss: 0.0345 - accuracy: 0.9895
Epoch 5/10
60000/60000 [==============================] - 180s 3ms/sample - loss: 0.0296 - accuracy: 0.9910
Epoch 6/10
60000/60000 [==============================] - 187s 3ms/sample - loss: 0.0262 - accuracy: 0.9922
Epoch 7/10
60000/60000 [==============================] - 172s 3ms/sample - loss: 0.0218 - accuracy: 0.9930
Epoch 8/10
60000/60000 [==============================] - 171s 3ms/sample - loss: 0.0204 - accuracy: 0.9934
Epoch 9/10
60000/60000 [==============================] - 195s 3ms/sample - loss: 0.0177 - accuracy: 0.9938
Epoch 10/10
60000/60000 [==============================] - 170s 3ms/sample - loss: 0.0175 - accuracy: 0.9942
▌模型结构
![](https://i-blog.csdnimg.cn/blog_migrate/7f24d258f451534d7b7674b10b25b8d9.png)
数字识别
通过上述操作,我们已经搭建好卷积神经网络,并且用MNIST进行了模型的训练。MNIST由70000张手写数字(0~9)图片组成,每张图片的大小是28 x 28像素,数字的大小是20 x 20,位于图片的中心。
当前我们提取的图片特征与其有出入,如下图。为了得到与其近似的图片,我们还需要将提取的数字图片缩放为28*28大小。
![](https://i-blog.csdnimg.cn/blog_migrate/b430155b698d5a658f69db266c118ef1.png)
▌核心代码
def plot_image(image):
fig=plt.gcf()
fig.set_size_inches(2,2)
plt.imshow(image,cmap=plt.cm.binary)
plt.show()
for xx in range(11):
num_mask = cv2.imread('./pig/{}.png'.format(xx),0)
num_mask = cv2.resize(num_mask,(28,28))
plot_image(num_mask)
cv2.imwrite('./pngn/{}.png'.format(xx), num_mask)
number.append(num_mask)
print(num_mask.shape)
▌运行结果
![](https://i-blog.csdnimg.cn/blog_migrate/2ce1e6dbd1c6d9e1ef3ec799e320f7dc.png)
但从mnist数据集考虑特点,其数字位于图片中央,而我们分割出的数字却顶格到图片边缘。对此我们对图片进行优化。
![](https://i-blog.csdnimg.cn/blog_migrate/b0babae1eaad8f974e30d3ef4529b9ee.png)
▌核心代码
img28 = np.zeros([28, 28], np.uint8)
cv2.imwrite('./img28.png', img28)
for xx in range(11):
num_mask = cv2.imread('./pig/{}.png'.format(xx),0)
num_mask = cv2.resize(num_mask,(20,20))
img28 = Image.open('./img28.png')
num_mask = Image.open('./pngn/{}.png'.format(xx))
img28.paste(num_mask,(4,4))
plot_image(img28)
img28 = cv2.cvtColor(np.asarray(img28),cv2.COLOR_RGB2BGR)
cv2.imwrite('./pic/{}.png'.format(xx), img28)
▌运行结果
![](https://i-blog.csdnimg.cn/blog_migrate/33e7983da97cf939e39fd15a2c13b8a4.png)
最后我们便可以加载训练的模型,对上面提取、处理的图片进行识别。
▌核心代码
number2 = np.array(number)
print(number2.shape)
testingNorm = number2.reshape(11,28,28,1).astype('float32')
model = keras.models.load_model('minst.h5')
result=model.predict_classes(testingNorm)
print(result)
sum = 0
re = [1,7 ,8 ,5, 3, 5, 9, 1, 2, 3, 4]
for i in range(0,len(result)):
if result[i] == re[i]:
sum += 1
sum /= len(result)
print("sum=", sum)
with open("reuslt.txt","w") as f:
f.write(str(result))
▌运行结果
(11, 28, 28)
[1 7 8 5 3 5 9 1 2 3 4]
sum= 1.0