终于算是闲下来点时间了,也不能算闲,该交的报告什么的算完事了。其他要交的东西现在还不急,然后考研的东西现在也不想看,再加上中午没睡好,下午也不想学习新的东西了,就抽出点时间把前段时间做的一个小项目来记录分享一下吧。
写的比较仓促,有错误请指出,共同学习!
这个项目就是给你一张有车牌的图片,如下图,你怎么把这张个车牌上的车牌号给识别出来呢?
从我前段时间的学习也可以看到,这个地方的识别我肯定想用到前面学习到的机器学习的方法。前面学习到的和识别有关的也就是那个Mnist手写数字识别了,那我接下来的思路就是怎么将这个问题转化成字符识别的问题了。
好的,现在我们知道这个问题想要干什么了,而且有了最终的目标,那么现在就是将这个问题进行分解,然后逐个击破!那么要分解成哪些小问题呢?
我是这样划分的:
看到这你可能想问了,我怎么知道要这样划分~~,也不讲讲思路啥的,写的真差劲,不看了。。。别着急别着急,静下心来听我娓娓道来(这个词是这样用的吧?算了不管了),听我讲就完事了,给我往下看!不准走,否则吃大亏了呀。okok言归正传,我来一个一个讲。
ok,首先我们想要识别这张车牌上的字符,那么首先我们得把车牌先给剪切下来吧。那么这里就和机器学习和神经网络没什么关系了,这里是图像处理的知识,什么边缘提取、灰度变换、二值化、剪切等等,没学过图像处理也没有关系,因为python里面的cv2包里已经帮我们把这些函数打包好了,只需要调用就好了,总之,最后我们要将这张图片变成神经网络能够识别的一张一张的字符图片。
大概就是这个样子吧。就是把车牌上字符分割就对了,代码我后面会贴的,其实我也是参考了很多博客,找了很多代码然后一点点调试才能差不多做到这个效果,因为这里不是侧重学习图像处理的,所以只是做了简单学习并没有深入要求特别高。
这里每一个函数的作用我就不细讲了,按着顺序看,遇到不知道的函数就去查,知道每个函数是干什么的就行了。多调试调试,看会出现什么不同的效果。
不要嫌麻烦,多调试,多看结果,这一块刚开始接触的时候可能会有那么一点点麻烦。。。坚持住!
import numpy as np
import cv2
from PIL import Image
import matplotlib.pyplot as plt # plt 用于显示图片
import matplotlib.image as mpimg # mpimg 用于读取图片
# 1、读取车牌图片函数:将要处理的车牌读入并做相应的图像处理(边缘检测、膨胀处理)
# 输入参数:照片原图像路径
# 返回值:原图像、膨胀处理后的图像
def read_image(image_path):
# 读取车牌图片
image = mpimg.imread(image_path, 1) # 读取和代码处于同一目录下的 chepai.png
# 将BGR格式转换成灰度图片
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 高斯平滑
gaussian = cv2.GaussianBlur(gray, (3, 3), 0, 0, cv2.BORDER_DEFAULT)
# 中值化处理
median = cv2.medianBlur(gaussian, 5)
# 边缘检测
sobel = cv2.Sobel(median, cv2.CV_8U, 1, 0, ksize=3)
# 二值化
ret, binary = cv2.threshold(sobel, 170, 255, cv2.THRESH_BINARY)
# 对二值化的图像进行腐蚀,膨胀,开运算,闭运算的形态学组合变换
# 膨胀和腐蚀操作的核函数
element1 = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 1))
element2 = cv2.getStructuringElement(cv2.MORPH_RECT, (8, 6))
# 膨胀一次,让轮廓突出
dilation = cv2.dilate(binary, element2, iterations=1)
# 腐蚀一次,去掉细节
erosion = cv2.erode(dilation, element1, iterations=1)
# 再次膨胀,让轮廓明显一些
dilation2 = cv2.dilate(erosion, element2, iterations=3)
# 将原始图片和膨胀后的图片返回
return image, dilation2
# 2、查找车牌区域函数
# 输入参数:膨胀处理的图片、原始图片
# 返回值:车牌的坐标
def findPlateNumberRegion(img, origin_img):
region = []
# 查找轮廓
contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 筛选面积小的
for i in range(len(contours)):
cnt = contours[i]
# 计算该轮廓的面积
area = cv2.contourArea(cnt)
# 面积小的都筛选掉
if area < 2000:
continue
# 轮廓近似,作用很小
epsilon = 0.001 * cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, epsilon, True)
# 找到最小的矩形,该矩形可能有方向
# 返回值为:矩形中心点坐标,矩形长和宽,旋转角度
rect = cv2.minAreaRect(cnt)
print("rect is: ")
print(rect)
# box是四个点的坐标
box = cv2.boxPoints(rect)
box = np.int0(box)
# 计算高和宽
height = abs(box[0][1] - box[2][1])
width = abs(box[0][0] - box[2][0])
# 筛选矩形:车牌正常情况下长高比在2.7-5之间
ratio =float(width) / float(height)
if (ratio > 5 or ratio < 2):
continue
# 将可能的车牌坐标加入到边缘列表
region.append(box)
# 根据边缘坐标画出矩形区域
for box in region:
# 绘制轮廓
cv2.drawContours(origin_img, [box], 0, (0, 255, 0), 1)
# 返回
return region
# 3、二值化图像函数
def binaryzation(img):
# 变微灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 大津法二值化
retval, dst = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU)
return dst
# 4、剪切车牌区域的函数
# 输入参数:车牌的坐标、原图像
def clip_image(reg, img):
if len(reg) > 1:
print("识别出多个车牌,出现错误!")
return
# 找出包含整个车牌的最大矩形
for box in reg:
ys = [box[0, 1], box[1, 1], box[2, 1], box[3, 1]]
xs = [box[0, 0], box[1, 0], box[2, 0], box[3, 0]]
ys_sorted_index = np.argsort(ys)
xs_sorted_index = np.argsort(xs)
x1 = box[xs_sorted_index[0], 0]
x2 = box[xs_sorted_index[3], 0]
print(x1)
print(x2)
y1 = box[ys_sorted_index[0], 1]
y2 = box[ys_sorted_index[3], 1]
print(y1)
print(y2)
# 剪裁车牌号
crop = img[y1:y2, x1:x2, :]
# cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), 2)
# 调用二值化函数
binary_crop = binaryzation(crop)
# 保存切割后和二值化后的图像
cv2.imwrite('card_img.png', crop)
cv2.imwrite('binary_card_img.png', binary_crop)
# 显示原始图像、切割车牌、二值化后的车牌
cv2.imshow('img', img) #####显示图片#######
cv2.imshow('card_img', crop)
cv2.imshow('binary_card_img', binary_crop)
cv2.waitKey(0)
return binary_crop
# 第一步:
# 调用上面3个函数将车牌从原图像上切割下来
origin_img, dila = read_image('2.png')
region = findPlateNumberRegion(dila, origin_img)
image = clip_image(region, origin_img)
# 第二步:
# 进一步剪裁(上下剪裁和左右剪裁,使图像更加规整)
# ······
# 1、读取图像,并把图像转换为灰度图像并显示
img = cv2.imread("jingAchepai.png") # 读取图片
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转换了灰度化
cv2.imshow('gray', img_gray) # 显示图片
cv2.waitKey(0)
# 2、将灰度图像二值化,设定阈值是100(转化成白底黑字)
img_thre = img_gray
cv2.threshold(img_gray, 100, 255, cv2.THRESH_BINARY_INV, img_thre)
cv2.imshow('threshold', img_thre)
cv2.waitKey(0)
# 3、保存黑白图片
# cv2.imwrite('thre_res.png', img_thre)
# 第三步:
# 对处理好的更加规则的车牌图片进行字符剪裁
# 定义,都可根据应用进行调整
binary_threshold = 100
segmentation_spacing = 0.9 # 普通车牌值0.95,新能源车牌改为0.9即可
# 4、分割字符函数
def jiancai(img_thre):
white = [] # 记录每一列的白色像素总和
black = [] # ..........黑色.......
height = img_thre.shape[0]
width = img_thre.shape[1]
white_max = 0
black_max = 0
# 计算每一列的黑白色像素总和
for i in range(width):
s = 0 # 这一列白色总数
t = 0 # 这一列黑色总数
for j in range(height):
if img_thre[j][i] == 255:
s += 1
if img_thre[j][i] == 0:
t += 1
white_max = max(white_max, s)
black_max = max(black_max, t)
white.append(s)
black.append(t)
arg = False # False表示白底黑字;True表示黑底白字
if black_max > white_max:
arg = True
# 分割图像
def find_end(start_):
end_ = start_ + 1
for m in range(start_ + 1, width - 1):
if (black[m] if arg else white[m]) > (
segmentation_spacing * black_max if arg else segmentation_spacing * white_max): # 0.95这个参数请多调整,对应下面的0.05
end_ = m
break
return end_
m = 1
n = 1
start = 1
end = 2
while n < width - 2:
n += 1
if (white[n] if arg else black[n]) > (
(1 - segmentation_spacing) * white_max if arg else (1 - segmentation_spacing) * black_max):
# 上面这些判断用来辨别是白底黑字还是黑底白字
# 0.05这个参数请多调整,对应上面的0.95
start = n
end = find_end(start)
n = end
if end - start > 5:
cj = img_thre[1:height, start:end]
cv2.imshow('caijian', cj)
cv2.waitKey(0)
# 转换图片的大小为20*20
reshape_cj = cv2.resize(cj, (20, 20))
# cv2.imshow('caijian', reshape_cj)
cv2.imwrite('new_caijianImg/{0}.png'.format(m), reshape_cj)
m = m+1
# cv2.waitKey(0)
# 调用剪裁字符函数
jiancai(img_thre)
在原来识别Mnist手写数字识别的时候,我们有给打包好的数据集,包括图片和标签,但是那个数据集不适合我们这个项目,先不说手写体和车牌上的字符样式不同,我们车牌上还有字母和汉字呀,所以这个时候我们就必须自己准备数据集。那么首先第一步去下载数据集图片,就像下面这样的,包括10个数字、24个字母(因为0和O、1和I很像)、31个汉字。每张图片都是2020像素的,和mnist手写数字大小不一样,那个大小是2828像素的。
数据集下载链接:https://download.csdn.net/download/Mu_yongheng/18243520
这里面我上传了原始的字符图片,还有我已经处理好的带有标签的数据文件,可以直接拿过来用于训练模型!
从上面的照片我们可以看到,我们处理后的图片和训练的数据还有些不同,还需要就行剪切上面边缘和添加左右边缘的进一步的图像处理操作,代码如下所示:
# 第四步:
# 对剪裁好的单个字符图像进一步处理:减去多余的边缘部分
# 函数:对多余边缘进行剪裁的函数(这里主要想要去除上下边缘)
def corp_margin(img):
img2 = img.sum(axis=2)
(row, col) = img2.shape
row_top = 0
raw_down = 0
col_top = 0
col_down = 0
for r in range(0, row):
if img2.sum(axis=1)[r] < 700 * col:
row_top = r
break
for r in range(row - 1, 0, -1):
if img2.sum(axis=1)[r] < 700 * col:
raw_down = r
break
for c in range(0, col):
if img2.sum(axis=0)[c] < 700 * row:
col_top = c
break
for c in range(col - 1, 0, -1):
if img2.sum(axis=0)[c] < 700 * row:
col_down = c
break
new_img = img[row_top:raw_down + 1, col_top:col_down + 1, 0:3]
return new_img
def diaoyong_corp_margin(img_path, save_path):
img = cv2.imread(img_path)
img_crop_margin = corp_margin(img)
#cv2.imshow('img', img_crop_margin)
cv2.imwrite(save_path, img_crop_margin)
#cv2.waitKey(0)
diaoyong_corp_margin('new_caijianImg/1.png', 'new_caijianImg/new_1.png')
diaoyong_corp_margin('new_caijianImg/2.png', 'new_caijianImg/new_2.png')
diaoyong_corp_margin('new_caijianImg/3.png', 'new_caijianImg/new_3.png')
diaoyong_corp_margin('new_caijianImg/4.png', 'new_caijianImg/new_4.png')
diaoyong_corp_margin('new_caijianImg/5.png', 'new_caijianImg/new_5.png')
diaoyong_corp_margin('new_caijianImg/6.png', 'new_caijianImg/new_6.png')
diaoyong_corp_margin('new_caijianImg/7.png', 'new_caijianImg/new_7.png')
# 给图片添加边框的函数(添加左右边框,因为训练集的数据左右是有边框的)
def image_border(src, dst, loc='a', width=3, color=(0, 0, 0)):
'''
src: (str) 需要加边框的图片路径
dst: (str) 加边框的图片保存路径
loc: (str) 边框添加的位置, 默认是'a'(
四周: 'a' or 'all'
上: 't' or 'top'
右: 'r' or 'rigth'
下: 'b' or 'bottom'
左: 'l' or 'left'
)
width: (int) 边框宽度 (默认是3)
color: (int or 3-tuple) 边框颜色 (默认是0, 表示黑色; 也可以设置为三元组表示RGB颜色)
'''
# 读取图片
# size = img.shape
# print(size)
# w = size[0]
# print(w)
# h = size[1]
# print(h)
img_ori = Image.open(src)
w = img_ori.size[0]
h = img_ori.size[1]
# 添加边框
if loc in ['a', 'all']:
w += 2*width
h += 2*width
img_new = Image.new('RGB', (w, h), color)
img_new.paste(img_ori, (width, width))
elif loc in ['t', 'top']:
h += width
img_new = Image.new('RGB', (w, h), color)
img_new.paste(img_ori, (0, width, w, h))
elif loc in ['r', 'right']:
w += width
img_new = Image.new('RGB', (w, h), color)
img_new.paste(img_ori, (0, 0, w-width, h))
elif loc in ['b', 'bottom']:
h += width
img_new = Image.new('RGB', (w, h), color)
img_new.paste(img_ori, (0, 0, w, h-width))
elif loc in ['l', 'left']:
w += width
img_new = Image.new('RGB', (w, h), color)
img_new.paste(img_ori, (width, 0, w, h))
elif loc in ['lr', 'left_and_right']:
w += 2*width
img_new = Image.new('RGB', (w, h), color)
img_new.paste(img_ori, (width, 0))
elif loc in ['tb', 'top_and_bottom']:
h += 2*width
img_new = Image.new('RGB', (w, h), color)
img_new.paste(img_ori, (0, width))
else:
pass
# 保存图片
img_new.save(dst)
# 得到最终的图片
image_border('new_caijianImg/new_1.png', 'new_caijianImg/new_1_1.png', 'lr', 5, color=(255, 255, 255))
image_border('new_caijianImg/new_2.png', 'new_caijianImg/new_2_2.png', 'lr', 5, color=(255, 255, 255))
image_border('new_caijianImg/new_3.png', 'new_caijianImg/new_3_3.png', 'lr', 5, color=(255, 255, 255))
image_border('new_caijianImg/new_4.png', 'new_caijianImg/new_4_4.png', 'lr', 5, color=(255, 255, 255))
image_border('new_caijianImg/new_5.png', 'new_caijianImg/new_5_5.png', 'lr', 5, color=(255, 255, 255))
image_border('new_caijianImg/new_6.png', 'new_caijianImg/new_6_6.png', 'lr', 5, color=(255, 255, 255))
image_border('new_caijianImg/new_7.png', 'new_caijianImg/new_7_7.png', 'lr', 5, color=(255, 255, 255))
现在我们图片处理都已经准备好了,那么现在就是准备数据集,虽然我已经将转化好的数据集上传了,但是我还是想讲一下如何读入这些图片并转换为和Mnist数据集类似的数据集。
# 生成65*65的二维矩阵(对角线为1,其余全为0),作为独热编码标签数据
labels = np.diag([1]*65)
# 存储所有读取的图片数据
array_of_img = []
# 读取相应文件夹中的所有图片,并将图片转化为灰度图(减少通道数),连同标签一起存入列表中
def read_directory(directory_name, m):
# n 用来统计这个文件中共有多少张图片,最后返回出去
n = 0
for filename in os.listdir(r"./"+directory_name):
# 打印正在读取第几张图片
n = n + 1
print("第" + str(n) + "张图片:" + filename)
# 将读取的图片存储在img中
img = cv2.imread(directory_name + "/" + filename)
# 减少通道数(变为灰度图)
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 改变图片的形状(由 20*20 变为 1*400)
reshape_gray_img = gray_img.reshape(1, 400)
# 将numpy转化为列表,使用append
reshape_gray_img = reshape_gray_img.tolist()
# 添加标签值(根据所提供的数字m)
# 最终得到(1, 465)的列表
for i in range(len(labels[m])):
reshape_gray_img[0].append(labels[m][i])
# 将照片像素值和标签都添加到列表中
array_of_img.append(reshape_gray_img[0])
# if n == 1:
# break
return n
# 统计所有文件中图片的总数
all_image = 0
# 读取0-9数字文件夹
print("**************************************读取数字为 0 的文件中的图片*****************************************")
shu = read_directory('chepaishujuji/0', 0)
all_image = all_image + shu
print("**************************************读取数字为 1 的文件中的图片*****************************************")
shu = read_directory('chepaishujuji/1', 1)
all_image = all_image + shu
print("**************************************读取数字为 2 的文件中的图片*****************************************")
shu = read_directory('chepaishujuji/2', 2)
all_image = all_image + shu
print("**************************************读取数字为 3 的文件中的图片*****************************************")
shu = read_directory('chepaishujuji/3', 3)
all_image = all_image + shu
print("**************************************读取数字为 4 的文件中的图片*****************************************")
shu = read_directory('chepaishujuji/4', 4)
all_image = all_image + shu
print("**************************************读取数字为 5 的文件中的图片*****************************************")
shu = read_directory('chepaishujuji/5', 5)
all_image = all_image + shu
print("**************************************读取数字为 6 的文件中的图片*****************************************")
shu = read_directory('chepaishujuji/6', 6)
all_image = all_image + shu
print("**************************************读取数字为 7 的文件中的图片*****************************************")
shu = read_directory('chepaishujuji/7', 7)
all_image = all_image + shu
print("**************************************读取数字为 8 的文件中的图片*****************************************")
shu = read_directory('chepaishujuji/8', 8)
all_image = all_image + shu
print("**************************************读取数字为 9 的文件中的图片*****************************************")
shu = read_directory('chepaishujuji/9', 9)
all_image = all_image + shu
# 读取A-Z字母文件夹
print("**************************************读取字母为 A 的文件中的图片*****************************************")
shu = read_directory('chepaishujuji/A', 10)
all_image = all_image + shu
print("**************************************读取字母为 B 的文件中的图片*****************************************")
shu = read_directory('chepaishujuji/B', 11)
all_image = all_image + shu
print("**************************************读取字母为 C 的文件中的图片*****************************************")
shu = read_directory('chepaishujuji/C', 12)
all_image = all_image + shu
print("**************************************读取字母为 D 的文件中的图片*****************************************")
shu = read_directory('chepaishujuji/D', 13)
all_image = all_image + shu
print("**************************************读取字母为 E 的文件中的图片*****************************************")
shu = read_directory('chepaishujuji/E', 14)
all_image = all_image + shu
print("**************************************读取字母为 F 的文件中的图片*****************************************")
shu = read_directory('chepaishujuji/F', 15)
all_image = all_image + shu
print("**************************************读取字母为 G 的文件中的图片*****************************************")
shu = read_directory('chepaishujuji/G', 16)
all_image = all_image + shu
### 后面和前面的都一样,只需要改变文件夹的名称和标签就行了,最终一共调用函数65次
# 打印出所有读取结果
print("共有" + str(all_image) + "张图片")
print(len(array_of_img))
print(len(array_of_img[0]))
# 将读取的所有数据保存为一个.csv文件
test = pd.DataFrame(data=array_of_img)
test.to_csv('H:/test3.csv')
# 打印出形状
array_of_img = np.array(array_of_img)
print(array_of_img.shape)
print(array_of_img)
这里代码我就不贴完了,反正就是重复调用函数,将65个文件夹中的图片和其所对应的独热编码标签读取并且存到一个csv文件当中,不熟悉这种编码的可以再去学习一下Mnist手写数字识别。这里的数据集准备完全是模仿那一个的。
当我们把把这些图片和对应的标签全部读进去并存成csv文件以后,将文件的第一行和第一列删掉(行和列的序号,没有用的数据),最终得到的数据应该是一个49063 * 465 的二维矩阵,49063代表我们总共读取了49063张图片,465中的前400行是每张照片的像素值(20*20),后65行是独热编码。
其实最后得到的文件就是这样的:
链接放这里,这里面只包含读取好的文件:https://download.csdn.net/download/Mu_yongheng/18266097
不想下载全部图片的可以直接下载这个。
好了,现在我们的数据集也准备好了。接下来就是怎么用这个数据集来训练我们的模型,这里的模型还是用的前面mnist手写数字识别的模型,不同的就是每一层的神经元个数需要调整。
另外一个比较重要就是划分训练集、验证集和测试集。首先需要将处理好的csv文件读取进来放到一个列表里面,方便我们后面的处理。我是这样划分的:将前46000张照片作为训练集,接着的1000张作为验证机,最后剩下的2063张作为测试集。
还有要注意打乱我们的数据集,因为我们的数据刚开始都是顺序存储的,按照0, 1,2这样的顺序存储的,所以需要打乱。并且每一轮训练都需要打乱数据,防止我们的模型产生“肌肉记忆”,所以为了方便操作,我就定义了一个函数shuffle_data(data): 这个函数的作用是打乱数据并产生训练集、验证机、测试集数据即标签。
注意多调整每一层神经元个数以及学习率的大小,我这个模型经过最终的训练可以达到接近99%的正确率!
具体代码如下:
import tensorflow as tf
import numpy as np
import csv
import random
import os # 用于保存模型
# 读取csv文件,将图片所有数据存入array数组
array = []
with open('H:/test3.csv', 'r') as csvFile:
reader = csv.reader(csvFile)
arr = []
for line in reader:
for i in range(len(line)):
arr.append(int(line[i]))
array.append(arr)
arr = []
print(len(array))
print(len(array[0]))
# 定义打乱数据函数,并从整体数据中生成训练集、验证集和测试集
def shuffle_data(data):
random.shuffle(data)
# 提取训练集(46000个)
trains = data[0:46000]
train_images = []
train_labels = []
for train in trains:
image = train[0:400]
train_images.append(image)
label = train[400:465]
train_labels.append(label)
train_images = np.array(train_images)
train_labels = np.array(train_labels)
# 验证集(1000个)
validations = data[46000:47000]
validation_images = []
validation_labels = []
for validation in validations:
image = validation[0:400]
validation_images.append(image)
label = validation[400:465]
validation_labels.append(label)
validation_images = np.array(validation_images)
validation_labels = np.array(validation_labels)
# 测试集(2063个)
tests = data[47000:49063]
test_images = []
test_labels = []
for test in tests:
image = test[0:400]
test_images.append(image)
label = test[400:465]
test_labels.append(label)
test_images = np.array(test_images)
test_labels = np.array(test_labels)
return train_images, train_labels, validation_images, validation_labels, test_images, test_labels
# 调用shuffle_data函数,生成一批训练集、验证集和测试集
train_images, train_labels, validation_images, validation_labels, test_images, test_labels = shuffle_data(array)
# 定义全连接层函数
def fcn_layer(inputs, # 输入数据
input_dim, # 输入神经元数量
output_dim, # 输出神经元数量
activation=None): # 激活函数
w = tf.Variable(tf.truncated_normal([input_dim, output_dim], stddev=0.1))
b = tf.Variable(tf.zeros([output_dim]))
xwb = tf.matmul(inputs, w) + b
if activation is None:
outputs = xwb
else:
outputs = activation(xwb)
return outputs
# 保存模型
# 模型的存储粒度
save_step = 5
# 创建模型保存的文件的目录
ckpt_dir = "./xunlian_dir/"
if not os.path.exists(ckpt_dir):
os.makedirs(ckpt_dir)
# 构建输入层
# 定义标签数据占位符
x = tf.placeholder(tf.float32, [None, 400], name="X")
y = tf.placeholder(tf.float32, [None, 65], name="Y")
# 构建隐藏层
H1_NN = 4096 # 第1隐藏层神经元数量
H2_NN = 2048 # 第2隐藏层神经元数量
H3_NN = 1024 # 第3隐藏层神经元数量
# 输入层 - 第1隐藏层参数和偏置项(构建第1隐藏层)
h1 = fcn_layer(inputs=x, input_dim=400, output_dim=H1_NN, activation=tf.nn.relu)
# 第1隐藏层 - 第2隐藏层参数和偏执项(构建第2隐藏层)
h2 = fcn_layer(inputs=h1, input_dim=H1_NN, output_dim=H2_NN, activation=tf.nn.relu)
# 第2隐藏层 - 第3隐藏层参数和偏置项(构建第3隐藏层)
h3 = fcn_layer(inputs=h2, input_dim=H2_NN, output_dim=H3_NN, activation=tf.nn.relu)
# 第3隐藏层 - 输出层参数和偏置项(构建输出层)
forward = fcn_layer(inputs=h3, input_dim=H3_NN, output_dim=65, activation=None)
pred = tf.nn.softmax(forward)
# 定义训练参数
train_epochs = 40 # 训练的轮数
batch_size = 100 # 单次训练样本数
# total_batch = int(len(trains)/batch_size)
total_batch = 470
display_step = 1 # 显示粒度
learning_rate = 0.0001 # 学习率(不断调整)
# 定义损失函数
# loss_function = tf.reduce_mean(-tf.reduce_sum(y*tf.log(pred), reduction_indices=1))
loss_function = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=forward, labels=y)) # 结合Softmax的交叉熵损失函数定义方法
# 定义优化器
optimizer = tf.train.AdamOptimizer(learning_rate).minimize(loss_function)
# 定义准确率
correct_prediction = tf.equal(tf.argmax(pred, 1), tf.argmax(y, 1))
# 准确率,将布尔值转化为浮点数,并计算平均值
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
# 声明完所有变量,调用tf.train.saver
saver = tf.train.Saver()
# 模型训练
# 记录训练开始的时间
from time import time
startTime = time()
sess = tf.Session()
sess.run(tf.global_variables_initializer())
# 开始训练
for epoch in range(train_epochs):
# 关键一步:每训练完成一轮,调用函数shuffle_data打乱并生成一批新的训练集、验证集和测试集
train_images, train_labels, validation_images, validation_labels, test_images, test_labels = shuffle_data(array)
for batch in range(total_batch):
# 把所有数据按照批次传入网络进行训练
xs = train_images[batch * batch_size:(batch + 1) * batch_size]
ys = train_labels[batch * batch_size:(batch + 1) * batch_size]
sess.run(optimizer, feed_dict={
x: xs, y: ys}) # 执行批次训练
# total_batch 个批次训练完后,使用验证数据计算准确率
loss, acc = sess.run([loss_function, accuracy], feed_dict={
x: validation_images, y: validation_labels})
# 打印训练过程中的详细信息
if (epoch + 1) % display_step == 0:
print("训练轮次:", epoch + 1, "损失值:", format(loss), "准确率:", format(acc))
# 按照模型保存粒度对模型进行保存
if (epoch+1) % save_step == 0:
saver.save(sess, os.path.join(ckpt_dir, 'mnist_h256_model_{:06d}.ckpt'.format(epoch+1))) # 存储模型
print('mnist_h256_model_{:06d}.ckpt saved'.format(epoch+1))
saver.save(sess, os.path.join(ckpt_dir, 'mnist_h256_model.ckpt'))
print("Model saved!")
# 显示运行总时间
duration = time() - startTime
print("本次训练所花的总时间为:", duration)
# 应用模型
prediction_result = sess.run(tf.argmax(pred, 1), feed_dict={
x: test_images})
print(prediction_result[0:10])
现在图片也处理好了,模型也训练好了。万事俱备只欠东风!接下来就是输入图片并进行识别了。
我将最终代码全部放到一个文件中了,如下。
还需要注意的一点就是标签与实际字符值的转换。就是后面定义的这个字典。
具体为什么我就不多说了,懂得都懂!不懂也没关系,欢迎大家提问。
最终代码实现:
from PIL import Image
import matplotlib.image as mpimg # mpimg 用于读取图片
import tensorflow as tf
import cv2
import csv
import random
import numpy as np
# 1、读取车牌图片函数:将要处理的车牌读入并做相应的图像处理(边缘检测、膨胀处理)
# 输入参数:照片原图像路径
# 返回值:原图像、膨胀处理后的图像
def read_image(image_path):
# 读取车牌图片
image = mpimg.imread(image_path, 1) # 读取和代码处于同一目录下的 chepai.png
# 将BGR格式转换成灰度图片
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 高斯平滑
gaussian = cv2.GaussianBlur(gray, (3, 3), 0, 0, cv2.BORDER_DEFAULT)
# 中值化处理
median = cv2.medianBlur(gaussian, 5)
# 边缘检测
sobel = cv2.Sobel(median, cv2.CV_8U, 1, 0, ksize=3)
# 二值化
ret, binary = cv2.threshold(sobel, 170, 255, cv2.THRESH_BINARY)
# 对二值化的图像进行腐蚀,膨胀,开运算,闭运算的形态学组合变换
# 膨胀和腐蚀操作的核函数
element1 = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 1))
element2 = cv2.getStructuringElement(cv2.MORPH_RECT, (8, 6))
# 膨胀一次,让轮廓突出
dilation = cv2.dilate(binary, element2, iterations=1)
# 腐蚀一次,去掉细节
erosion = cv2.erode(dilation, element1, iterations=1)
# 再次膨胀,让轮廓明显一些
dilation2 = cv2.dilate(erosion, element2, iterations=3)
# 将原始图片和膨胀后的图片返回
return image, dilation2
# 2、查找车牌区域函数
# 输入参数:膨胀处理的图片、原始图片
# 返回值:车牌的坐标
def findPlateNumberRegion(img, origin_img):
region = []
# 查找轮廓
contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 筛选面积小的
for i in range(len(contours)):
cnt = contours[i]
# 计算该轮廓的面积
area = cv2.contourArea(cnt)
# 面积小的都筛选掉
if area < 2000:
continue
# 轮廓近似,作用很小
epsilon = 0.001 * cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, epsilon, True)
# 找到最小的矩形,该矩形可能有方向
# 返回值为:矩形中心点坐标,矩形长和宽,旋转角度
rect = cv2.minAreaRect(cnt)
print("rect is: ")
print(rect)
# box是四个点的坐标
box = cv2.boxPoints(rect)
box = np.int0(box)
# 计算高和宽
height = abs(box[0][1] - box[2][1])
width = abs(box[0][0] - box[2][0])
# 筛选矩形:车牌正常情况下长高比在2.7-5之间
ratio =float(width) / float(height)
if (ratio > 5 or ratio < 2):
continue
# 将可能的车牌坐标加入到边缘列表
region.append(box)
# 根据边缘坐标画出矩形区域
for box in region:
# 绘制轮廓
cv2.drawContours(origin_img, [box], 0, (0, 255, 0), 1)
# 返回
return region
# 3、二值化图像函数
def binaryzation(img):
# 变微灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 大津法二值化
retval, dst = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU)
return dst
# 4、剪切车牌区域的函数
# 输入参数:车牌的坐标、原图像
def clip_image(reg, img):
if len(reg) > 1:
print("识别出多个车牌,出现错误!")
return
# 找出包含整个车牌的最大矩形
for box in reg:
ys = [box[0, 1], box[1, 1], box[2, 1], box[3, 1]]
xs = [box[0, 0], box[1, 0], box[2, 0], box[3, 0]]
ys_sorted_index = np.argsort(ys)
xs_sorted_index = np.argsort(xs)
x1 = box[xs_sorted_index[0], 0]
x2 = box[xs_sorted_index[3], 0]
# print(x1)
# print(x2)
y1 = box[ys_sorted_index[0], 1]
y2 = box[ys_sorted_index[3], 1]
# print(y1)
# print(y2)
# 剪裁车牌号
crop = img[y1:y2, x1:x2, :]
# cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), 2)
# 调用二值化函数
binary_crop = binaryzation(crop)
# 保存切割后和二值化后的图像
cv2.imwrite('card_img.png', crop)
cv2.imwrite('binary_card_img.png', binary_crop)
# 显示原始图像、切割车牌、二值化后的车牌
cv2.imshow('img', img) #####显示图片#######
cv2.imshow('card_img', crop)
cv2.imshow('binary_card_img', binary_crop)
cv2.waitKey(0)
# 第一步:
# 调用上面3个函数将车牌从原图像上切割下来
origin_img, dila = read_image('2.png')
region = findPlateNumberRegion(dila, origin_img)
clip_image(region, origin_img)
# 第二步:
# 进一步剪裁(上下剪裁和左右剪裁,使图像更加规整)
# ······
# 1、读取图像,并把图像转换为灰度图像并显示
img = cv2.imread("jingAchepai.png") # 读取图片
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转换了灰度化
cv2.imshow('gray', img_gray) # 显示图片
cv2.waitKey(0)
# 2、将灰度图像二值化,设定阈值是100(转化成白底黑字)
img_thre = img_gray
cv2.threshold(img_gray, 100, 255, cv2.THRESH_BINARY_INV, img_thre)
cv2.imshow('threshold', img_thre)
cv2.waitKey(0)
# 3、保存黑白图片
# cv2.imwrite('thre_res.png', img_thre)
# 第三步:
# 对处理好的更加规则的车牌图片进行字符剪裁
# 定义,都可根据应用进行调整
binary_threshold = 100
segmentation_spacing = 0.9 # 普通车牌值0.95,新能源车牌改为0.9即可
# 4、分割字符函数
def jiancai(img_thre):
white = [] # 记录每一列的白色像素总和
black = [] # ..........黑色.......
height = img_thre.shape[0]
width = img_thre.shape[1]
white_max = 0
black_max = 0
# 计算每一列的黑白色像素总和
for i in range(width):
s = 0 # 这一列白色总数
t = 0 # 这一列黑色总数
for j in range(height):
if img_thre[j][i] == 255:
s += 1
if img_thre[j][i] == 0:
t += 1
white_max = max(white_max, s)
black_max = max(black_max, t)
white.append(s)
black.append(t)
arg = False # False表示白底黑字;True表示黑底白字
if black_max > white_max:
arg = True
# 分割图像
def find_end(start_):
end_ = start_ + 1
for m in range(start_ + 1, width - 1):
if (black[m] if arg else white[m]) > (
segmentation_spacing * black_max if arg else segmentation_spacing * white_max): # 0.95这个参数请多调整,对应下面的0.05
end_ = m
break
return end_
m = 1
n = 1
start = 1
end = 2
while n < width - 2:
n += 1
if (white[n] if arg else black[n]) > (
(1 - segmentation_spacing) * white_max if arg else (1 - segmentation_spacing) * black_max):
# 上面这些判断用来辨别是白底黑字还是黑底白字
# 0.05这个参数请多调整,对应上面的0.95
start = n
end = find_end(start)
n = end
if end - start > 5:
cj = img_thre[1:height, start:end]
cv2.imshow('caijian', cj)
cv2.waitKey(0)
# 转换图片的大小为20*20
# reshape_cj = cv2.resize(cj, (20, 20))
# cv2.imshow('caijian', reshape_cj)
cv2.imwrite('new_caijianImg/{0}.png'.format(m), cj)
m = m+1
# cv2.waitKey(0)
# 调用剪裁字符函数
jiancai(img_thre)
# 第四步:
# 对剪裁好的单个字符图像进一步处理:减去多余的边缘部分
# 函数:对多余边缘进行剪裁的函数(这里主要想要去除上下边缘)
def corp_margin(img):
img2 = img.sum(axis=2)
(row, col) = img2.shape
row_top = 0
raw_down = 0
col_top = 0
col_down = 0
for r in range(0, row):
if img2.sum(axis=1)[r] < 700 * col:
row_top = r
break
for r in range(row - 1, 0, -1):
if img2.sum(axis=1)[r] < 700 * col:
raw_down = r
break
for c in range(0, col):
if img2.sum(axis=0)[c] < 700 * row:
col_top = c
break
for c in range(col - 1, 0, -1):
if img2.sum(axis=0)[c] < 700 * row:
col_down = c
break
new_img = img[row_top:raw_down + 1, col_top:col_down + 1, 0:3]
return new_img
def diaoyong_corp_margin(img_path, save_path):
img = cv2.imread(img_path)
img_crop_margin = corp_margin(img)
# cv2.imshow('img', img_crop_margin)
cv2.imwrite(save_path, img_crop_margin)
# cv2.waitKey(0)
diaoyong_corp_margin('new_caijianImg/1.png', 'new_caijianImg/new_1.png')
diaoyong_corp_margin('new_caijianImg/2.png', 'new_caijianImg/new_2.png')
diaoyong_corp_margin('new_caijianImg/3.png', 'new_caijianImg/new_3.png')
diaoyong_corp_margin('new_caijianImg/4.png', 'new_caijianImg/new_4.png')
diaoyong_corp_margin('new_caijianImg/5.png', 'new_caijianImg/new_5.png')
diaoyong_corp_margin('new_caijianImg/6.png', 'new_caijianImg/new_6.png')
diaoyong_corp_margin('new_caijianImg/7.png', 'new_caijianImg/new_7.png')
# 给图片添加边框的函数(添加左右边框,因为训练集的数据左右是有边框的)
def image_border(src, dst, loc='a', width=3, color=(0, 0, 0)):
'''
src: (str) 需要加边框的图片路径
dst: (str) 加边框的图片保存路径
loc: (str) 边框添加的位置, 默认是'a'(
四周: 'a' or 'all'
上: 't' or 'top'
右: 'r' or 'rigth'
下: 'b' or 'bottom'
左: 'l' or 'left'
)
width: (int) 边框宽度 (默认是3)
color: (int or 3-tuple) 边框颜色 (默认是0, 表示黑色; 也可以设置为三元组表示RGB颜色)
'''
# 读取图片
img_ori = Image.open(src)
w = img_ori.size[0]
h = img_ori.size[1]
# 添加边框
if loc in ['a', 'all']:
w += 2*width
h += 2*width
img_new = Image.new('RGB', (w, h), color)
img_new.paste(img_ori, (width, width))
elif loc in ['t', 'top']:
h += width
img_new = Image.new('RGB', (w, h), color)
img_new.paste(img_ori, (0, width, w, h))
elif loc in ['r', 'right']:
w += width
img_new = Image.new('RGB', (w, h), color)
img_new.paste(img_ori, (0, 0, w-width, h))
elif loc in ['b', 'bottom']:
h += width
img_new = Image.new('RGB', (w, h), color)
img_new.paste(img_ori, (0, 0, w, h-width))
elif loc in ['l', 'left']:
w += width
img_new = Image.new('RGB', (w, h), color)
img_new.paste(img_ori, (width, 0, w, h))
elif loc in ['lr', 'left_and_right']:
w += 2*width
img_new = Image.new('RGB', (w, h), color)
img_new.paste(img_ori, (width, 0))
elif loc in ['tb', 'top_and_bottom']:
h += 2*width
img_new = Image.new('RGB', (w, h), color)
img_new.paste(img_ori, (0, width))
else:
pass
# 保存图片
img_new.save(dst)
# 得到最终的图片
image_border('new_caijianImg/new_1.png', 'new_caijianImg/new_1_1.png', 'lr', 15, color=(255, 255, 255))
image_border('new_caijianImg/new_2.png', 'new_caijianImg/new_2_2.png', 'lr', 15, color=(255, 255, 255))
image_border('new_caijianImg/new_3.png', 'new_caijianImg/new_3_3.png', 'lr', 15, color=(255, 255, 255))
image_border('new_caijianImg/new_4.png', 'new_caijianImg/new_4_4.png', 'lr', 15, color=(255, 255, 255))
image_border('new_caijianImg/new_5.png', 'new_caijianImg/new_5_5.png', 'lr', 15, color=(255, 255, 255))
image_border('new_caijianImg/new_6.png', 'new_caijianImg/new_6_6.png', 'lr', 15, color=(255, 255, 255))
image_border('new_caijianImg/new_7.png', 'new_caijianImg/new_7_7.png', 'lr', 15, color=(255, 255, 255))
# 转化图片为指定大小
def produceImage(file_in, width, height, file_out):
image = Image.open(file_in)
resized_image = image.resize((width, height), Image.ANTIALIAS)
resized_image.save(file_out)
produceImage('new_caijianImg/new_1_1.png', 20, 20, 'new_caijianImg/chepai_1.png')
produceImage('new_caijianImg/new_2_2.png', 20, 20, 'new_caijianImg/chepai_2.png')
produceImage('new_caijianImg/new_3_3.png', 20, 20, 'new_caijianImg/chepai_3.png')
produceImage('new_caijianImg/new_4_4.png', 20, 20, 'new_caijianImg/chepai_4.png')
produceImage('new_caijianImg/new_5_5.png', 20, 20, 'new_caijianImg/chepai_5.png')
produceImage('new_caijianImg/new_6_6.png', 20, 20, 'new_caijianImg/chepai_6.png')
produceImage('new_caijianImg/new_7_7.png', 20, 20, 'new_caijianImg/chepai_7.png')
print('图像处理完毕,开始识别!')
# ----------------------------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------------------------
# ---------------------------------------------- 开始识别 ---------------------------------------------------
# ----------------------------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------------------------
# 读取数据文件
array = []
with open('H:/test3.csv', 'r') as csvFile:
reader = csv.reader(csvFile)
arr = []
for line in reader:
for i in range(len(line)):
arr.append(int(line[i]))
array.append(arr)
arr = []
print(len(array))
print(len(array[0]))
# 打乱数据
random.shuffle(array)
# 制作测试集,用来后面评价模型的准确率
tests = array[47000:49063]
test_images = []
test_labels = []
for test in tests:
image = test[0:400]
test_images.append(image)
label = test[400:465]
test_labels.append(label)
test_images = np.array(test_images)
test_labels = np.array(test_labels)
# 定义全连接层函数
def fcn_layer(inputs, # 输入数据
input_dim, # 输入神经元数量
output_dim, # 输出神经元数量
activation=None): # 激活函数
w = tf.Variable(tf.truncated_normal([input_dim, output_dim], stddev=0.1))
b = tf.Variable(tf.zeros([output_dim]))
xwb = tf.matmul(inputs, w) + b
if activation is None:
outputs = xwb
else:
outputs = activation(xwb)
return outputs
# 构建输入层
# 定义标签数据占位符
x = tf.placeholder(tf.float32, [None, 400], name="X")
y = tf.placeholder(tf.float32, [None, 65], name="Y")
# 构建隐藏层
H1_NN = 4096 # 第1隐藏层神经元数量
H2_NN = 2048 # 第2隐藏层神经元数量
H3_NN = 1024 # 第3隐藏层神经元数量
# 输入层 - 第1隐藏层参数和偏置项(构建第1隐藏层)
h1 = fcn_layer(inputs=x, input_dim=400, output_dim=H1_NN, activation=tf.nn.relu)
# 第1隐藏层 - 第2隐藏层参数和偏执项(构建第2隐藏层)
h2 = fcn_layer(inputs=h1, input_dim=H1_NN, output_dim=H2_NN, activation=tf.nn.relu)
# 第2隐藏层 - 第3隐藏层参数和偏置项(构建第3隐藏层)
h3 = fcn_layer(inputs=h2, input_dim=H2_NN, output_dim=H3_NN, activation=tf.nn.relu)
# 第3隐藏层 - 输出层参数和偏置项(构建输出层)
forward = fcn_layer(inputs=h3, input_dim=H3_NN, output_dim=65, activation=None)
pred = tf.nn.softmax(forward)
# 定义准确率
correct_prediction = tf.equal(tf.argmax(pred, 1), tf.argmax(y, 1))
# 准确率,将布尔值转化为浮点数,并计算平均值
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
# -------------------------------- 还原模型 ------------------------------------
# 1、必须指定为模型文件的存放目录
ckpt_dir = "./xunlian_dir"
# 2、读取模型
saver = tf.train.Saver() # 创建saver
sess = tf.Session()
init = tf.global_variables_initializer()
sess.run(init)
ckpt = tf.train.get_checkpoint_state(ckpt_dir)
if ckpt and ckpt.model_checkpoint_path:
saver.restore(sess, ckpt.model_checkpoint_path) # 从已经保存的模型中读取参数
print("Restore model from " + ckpt.model_checkpoint_path)
# 利用制作的测试集来验证模型的准确率
print("Accuracy: ", accuracy.eval(session=sess, feed_dict={
x: test_images, y: test_labels}))
def chuli_image(path):
img = cv2.imread(path)
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# cv2.imshow('img', gray_img)
# cv2.waitKey(0)
# 将白底黑字照片变为黑底白字(和训练集保持一致)
height = gray_img.shape[0]
width = gray_img.shape[1]
dst = np.zeros((height, width), np.uint8)
for i in range(height):
for j in range(width):
dst[i, j] = 255 - gray_img[i, j]
# cv2.imshow('img', dst)
# cv2.waitKey(0)
# 变形
dst = dst.reshape(1, 400)
return dst
picture = []
d1 = chuli_image('new_caijianImg/chepai_1.png')
picture.append(d1[0])
d2 = chuli_image('new_caijianImg/chepai_2.png')
picture.append(d2[0])
d3 = chuli_image('new_caijianImg/chepai_3.png')
picture.append(d3[0])
d4 = chuli_image('new_caijianImg/chepai_4.png')
picture.append(d4[0])
d5 = chuli_image('new_caijianImg/chepai_5.png')
picture.append(d5[0])
d6 = chuli_image('new_caijianImg/chepai_6.png')
picture.append(d6[0])
d7 = chuli_image('new_caijianImg/chepai_7.png')
picture.append(d7[0])
# 应用模型,并打印出预测的结果
prediction_result = sess.run(tf.argmax(pred, 1), feed_dict={
x: picture})
print(prediction_result)
dictionary = {
'0': '0', '1': '1', '2': '2', '3': '3', '4': '4', '5': '5', '6': '6', '7': '7', '8': '8', '9': '9',
'10': 'A', '11': 'B', '12': 'C', '13': 'D', '14': 'E', '15': 'F', '16': 'G', '17': 'H', '18': 'J', '19': 'K',
'20': 'L', '21': 'M', '22': 'N', '23': 'P', '24': 'Q', '25': 'R', '26': 'S', '27': 'T', '28': 'U', '29': 'V',
'30': 'W', '31': 'X', '32': 'Y', '33': 'Z', '34': '川', '35': '鄂', '36': '赣', '37': '甘', '38': '贵', '39': '桂',
'40': '黑', '41': '沪', '42': '冀', '43': '津', '44': '京', '45': '吉', '46': '辽', '47': '鲁', '48': '蒙', '49': '闽',
'50': '宁', '51': '青', '52': '琼', '53': '陕', '54': '苏', '55': '晋', '56': '皖', '57': '湘', '58': '新', '59': '豫',
'60': '渝', '61': '粤', '62': '云', '63': '藏', '64': '浙'
}
result = ''
str_0 = dictionary[str(prediction_result[0])]
str_1 = dictionary[str(prediction_result[1])]
str_2 = dictionary[str(prediction_result[2])]
str_3 = dictionary[str(prediction_result[3])]
str_4 = dictionary[str(prediction_result[4])]
str_5 = dictionary[str(prediction_result[5])]
str_6 = dictionary[str(prediction_result[6])]
result = str_0 + str_1 + str_2 + str_3 + str_4 + str_5 + str_6
print("识别的车牌号为:" + result)
因为这个项目主要想要学习的是机器学习识别部分,所以对于图像处理部分可能没有这么的精确,中间没处理好的也用到了“人工”剪切处理,反正就是无论你怎么处理,只要能处理成我们神经网络认识的图片就好了。(当然也可以认真研究研究怎么把图像处理的更好,由于时间有限,我就偷个懒了~~)
认真把这个的小项目搞清楚,对前面的机器学习识别这一部分绝对有更高层次的理解,并且能锻炼使用python处理数据以及编程的能力,最重要的我觉得是编程思想的提升,尽管可能这只是一个不是很复杂的小项目,不过说实话我在做这个的之一段时间,虽然有点累,但是我觉得我真的学习到了东西,也提升了不少能力。
不管怎样,保持学习永远没有错,不断学习,不断提升,共勉!
Keep Learning! Keep Moving!