TensorFlow进阶:车牌号识别项目

article/2025/9/21 11:36:09

终于算是闲下来点时间了,也不能算闲,该交的报告什么的算完事了。其他要交的东西现在还不急,然后考研的东西现在也不想看,再加上中午没睡好,下午也不想学习新的东西了,就抽出点时间把前段时间做的一个小项目来记录分享一下吧。

写的比较仓促,有错误请指出,共同学习!

目录

  • 一、项目简介
    • 1.想要干什么?
    • 2.整个思路介绍
  • 二、具体实现
    • 1.图像预处理
    • 2.数据集准备
    • 3.建立神经网络并进行训练
    • 4.输入图片进行识别
  • 三、项目总结

一、项目简介

1.想要干什么?

这个项目就是给你一张有车牌的图片,如下图,你怎么把这张个车牌上的车牌号给识别出来呢?
在这里插入图片描述
从我前段时间的学习也可以看到,这个地方的识别我肯定想用到前面学习到的机器学习的方法。前面学习到的和识别有关的也就是那个Mnist手写数字识别了,那我接下来的思路就是怎么将这个问题转化成字符识别的问题了。

2.整个思路介绍

好的,现在我们知道这个问题想要干什么了,而且有了最终的目标,那么现在就是将这个问题进行分解,然后逐个击破!那么要分解成哪些小问题呢?

我是这样划分的:

  1. 图像预处理
  2. 准备数据集
  3. 建立神经网络并进行训练
  4. 输入图片进行识别

看到这你可能想问了,我怎么知道要这样划分~~,也不讲讲思路啥的,写的真差劲,不看了。。。别着急别着急,静下心来听我娓娓道来(这个词是这样用的吧?算了不管了),听我讲就完事了,给我往下看!不准走,否则吃大亏了呀。okok言归正传,我来一个一个讲。

二、具体实现

1.图像预处理

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 = 0black_max = 0# 计算每一列的黑白色像素总和for i in range(width):s = 0  # 这一列白色总数t = 0  # 这一列黑色总数for j in range(height):if img_thre[j][i] == 255:s += 1if img_thre[j][i] == 0:t += 1white_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_ + 1for 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.05end_ = mbreakreturn end_m = 1n = 1start = 1end = 2while n < width - 2:n += 1if (white[n] if arg else black[n]) > ((1 - segmentation_spacing) * white_max if arg else (1 - segmentation_spacing) * black_max):# 上面这些判断用来辨别是白底黑字还是黑底白字# 0.05这个参数请多调整,对应上面的0.95start = nend = find_end(start)n = endif end - start > 5:cj = img_thre[1:height, start:end]cv2.imshow('caijian', cj)cv2.waitKey(0)# 转换图片的大小为20*20reshape_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)

2.数据集准备

在原来识别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.shaperow_top = 0raw_down = 0col_top = 0col_down = 0for r in range(0, row):if img2.sum(axis=1)[r] < 700 * col:row_top = rbreakfor r in range(row - 1, 0, -1):if img2.sum(axis=1)[r] < 700 * col:raw_down = rbreakfor c in range(0, col):if img2.sum(axis=0)[c] < 700 * row:col_top = cbreakfor c in range(col - 1, 0, -1):if img2.sum(axis=0)[c] < 700 * row:col_down = cbreaknew_img = img[row_top:raw_down + 1, col_top:col_down + 1, 0:3]return new_imgdef 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*widthh += 2*widthimg_new = Image.new('RGB', (w, h), color)img_new.paste(img_ori, (width, width))elif loc in ['t', 'top']:h += widthimg_new = Image.new('RGB', (w, h), color)img_new.paste(img_ori, (0, width, w, h))elif loc in ['r', 'right']:w += widthimg_new = Image.new('RGB', (w, h), color)img_new.paste(img_ori, (0, 0, w-width, h))elif loc in ['b', 'bottom']:h += widthimg_new = Image.new('RGB', (w, h), color)img_new.paste(img_ori, (0, 0, w, h-width))elif loc in ['l', 'left']:w += widthimg_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*widthimg_new = Image.new('RGB', (w, h), color)img_new.paste(img_ori, (width, 0))elif loc in ['tb', 'top_and_bottom']:h += 2*widthimg_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 = 0for filename in os.listdir(r"./"+directory_name):# 打印正在读取第几张图片n = n + 1print("第" + 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转化为列表,使用appendreshape_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:#     breakreturn 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
不想下载全部图片的可以直接下载这个。
在这里插入图片描述

3.建立神经网络并进行训练

好了,现在我们的数据集也准备好了。接下来就是怎么用这个数据集来训练我们的模型,这里的模型还是用的前面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) + bif activation is None:outputs = xwbelse: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])

4.输入图片进行识别

现在图片也处理好了,模型也训练好了。万事俱备只欠东风!接下来就是输入图片并进行识别了。

我将最终代码全部放到一个文件中了,如下。

还需要注意的一点就是标签与实际字符值的转换。就是后面定义的这个字典。
在这里插入图片描述
具体为什么我就不多说了,懂得都懂!不懂也没关系,欢迎大家提问。

附最终识别效果图:
在这里插入图片描述

最终代码实现:

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 = 0black_max = 0# 计算每一列的黑白色像素总和for i in range(width):s = 0  # 这一列白色总数t = 0  # 这一列黑色总数for j in range(height):if img_thre[j][i] == 255:s += 1if img_thre[j][i] == 0:t += 1white_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_ + 1for 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.05end_ = mbreakreturn end_m = 1n = 1start = 1end = 2while n < width - 2:n += 1if (white[n] if arg else black[n]) > ((1 - segmentation_spacing) * white_max if arg else (1 - segmentation_spacing) * black_max):# 上面这些判断用来辨别是白底黑字还是黑底白字# 0.05这个参数请多调整,对应上面的0.95start = nend = find_end(start)n = endif 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.shaperow_top = 0raw_down = 0col_top = 0col_down = 0for r in range(0, row):if img2.sum(axis=1)[r] < 700 * col:row_top = rbreakfor r in range(row - 1, 0, -1):if img2.sum(axis=1)[r] < 700 * col:raw_down = rbreakfor c in range(0, col):if img2.sum(axis=0)[c] < 700 * row:col_top = cbreakfor c in range(col - 1, 0, -1):if img2.sum(axis=0)[c] < 700 * row:col_down = cbreaknew_img = img[row_top:raw_down + 1, col_top:col_down + 1, 0:3]return new_imgdef 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*widthh += 2*widthimg_new = Image.new('RGB', (w, h), color)img_new.paste(img_ori, (width, width))elif loc in ['t', 'top']:h += widthimg_new = Image.new('RGB', (w, h), color)img_new.paste(img_ori, (0, width, w, h))elif loc in ['r', 'right']:w += widthimg_new = Image.new('RGB', (w, h), color)img_new.paste(img_ori, (0, 0, w-width, h))elif loc in ['b', 'bottom']:h += widthimg_new = Image.new('RGB', (w, h), color)img_new.paste(img_ori, (0, 0, w, h-width))elif loc in ['l', 'left']:w += widthimg_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*widthimg_new = Image.new('RGB', (w, h), color)img_new.paste(img_ori, (width, 0))elif loc in ['tb', 'top_and_bottom']:h += 2*widthimg_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) + bif activation is None:outputs = xwbelse: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()    # 创建saversess = 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 dstpicture = []
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!


http://chatgpt.dhexx.cn/article/RgH9gGk5.shtml

相关文章

数字图像处理--车牌识别

数字图像处理–车牌识别 主要内容 实现车牌识别 算法流程 本文中&#xff0c;车牌识别具体流程设计以及算法使用主要分为以下几步。 1、读取源车牌图像。 2、对原始车牌图像进行预处理&#xff1a;灰度化&#xff0c;运用基于几何运算的滤波器(开运算)消除毛刺噪声。 3、二…

数字图像处理——基于matlab的车牌号识别

希望大家有问题多多评论留言鸭 码字不易&#xff0c;老阿姨头发都没啦&#xff0c;小可爱们记得给三连鸭&#xff0c;么么哒。 &#xff08;只收藏&#xff0c;不点赞&#xff0c;好运连连会中断&#xff01;&#xff01;&#xff01;&#xff09; <--!&#xff08;源码资源…

【OpenCV实战】简洁易懂的车牌号识别Python+OpenCV实现“超详解”(含代码)

前面4篇博客介绍了OpenCV图像处理的基础知识&#xff0c;本篇博客利用前4篇的知识完成一个小项目——车牌号码识别。该篇博客的代码可以满足小区门禁车牌号的识别。本篇博客是前4篇博客知识的一个综合运用。感觉学会了这个可以实现一系列的图像识别任务。。。毕竟好多技巧都是共…

网络安全检测技术

一&#xff0c;网络安全漏洞 安全威胁是指所有能够对计算机网络信息系统的网络服务和网络信息的机密性&#xff0c;可用性和完整性产生阻碍&#xff0c;破坏或中断的各种因素。安全威胁可分为人为安全威胁和非人为安全威胁两大类。 1&#xff0c;网络安全漏洞威胁 漏洞分析的…

深度学习网络安全

Introduction 我们在社区中看到的大多数深度学习应用程序通常面向营销&#xff0c;销售&#xff0c;财务等领域。我们几乎从未阅读过文章或找到有关深度学习的资源用于保护这些产品和业务&#xff0c; 恶意软件和黑客攻击。 虽然像谷歌&#xff0c;Facebook&#xff0c;微软和…

网络安全法学习整理笔记

网络安全法 一、背景 概念 网络&#xff1a;是指由计算机或者其他信息终端及相关设备组成的按照一定的规则和程序对信息进行收集、存储、传输、交换、处理的系统。网络安全&#xff1a;是指通过采取必要措施&#xff0c;防范对网络的攻击、侵入、干扰、破坏和非法使用以及意…

网络安全免费学习网址(英文)

转载 作者&#xff1a;W-Pwn 链接&#xff1a;https://www.zhihu.com/question/49222590/answer/339206050 来源&#xff1a;知乎 著作权归作者所有。商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处。 知识就是力量 但是知识太贵了&#xff0c;现在随便拎…

【论文阅读】基于强化学习的网络安全防护策略

【论文阅读】基于强化学习的网络安全防护策略 本篇文章将介绍一篇针对网络安全问题&#xff0c;运用强化学习方法寻找最优的网络防御策略。 Finding Effective Security Strategies through Reinforcement Learning and Self-Play 前言 通过强化学习和 自对弈(self-play) 寻…

还在为不知道怎么学习网络安全而烦恼吗?这篇文带你从入门级开始学习网络安全—认识网络安全

随着网络安全被列为国家安全战略的一部分&#xff0c;这个曾经细分的领域发展提速了不少&#xff0c;除了一些传统安全厂商以外&#xff0c;一些互联网大厂也都纷纷加码了在这一块的投入&#xff0c;随之而来的吸引了越来越多的新鲜血液不断涌入。 不同于Java、C/C等后端开发岗…

[网络安全自学篇] 一.入门笔记之看雪Web安全学习及异或解密示例

最近开始学习网络安全相关知识&#xff0c;接触了好多新术语&#xff0c;感觉自己要学习的东西太多&#xff0c;真是学无止境&#xff0c;也发现了好几个默默无闻写着博客、做着开源的大神。准备好好学习下新知识&#xff0c;并分享些博客与博友们一起进步&#xff0c;加油。非…

码农翻身,卧虎藏龙

写公众号是很不容易的&#xff0c;在现在信息爆炸的情况下&#xff0c;好文章也很容易被标题党埋没&#xff0c;在我的知识星球“码农翻身”中&#xff0c;我发起了一个活动&#xff1a;免费给写公众号的球友们做个推广。 这个不是互推&#xff0c;就是我单方面的推广&#xf…

现在转行码农的成本已经非常高了,别盲目转行..

转行码农一直是个比较火热的话题&#xff0c;也有很多读者咨询过这个问题&#xff0c;转成功的也不少&#xff0c;比如下面这位香港的同学&#xff1a; 这位朋友半年前就跟我聊过&#xff0c;他不太想干没有技术含量的体力活&#xff0c;一直在坚持自学&#xff0c;这也算如愿…

农村出身的 90 后程序员,如何逆袭为中产阶级?| 程序员有话说

作者 | 阿文 责编 | 伍杏玲 出品 | 程序人生&#xff08;ID&#xff1a;coder_life&#xff09; 小蔡&#xff0c; 90 年 10 月出生&#xff0c;Java开发工程师&#xff0c;目前就职于杭州滨江某知名互联网公司从事云计算开发工作&#xff0c;2013 年毕业就从山东来到了杭州。 …

【一哥闲聊】程序员如何打破35岁魔咒

公众号推文规则变了&#xff0c;点击上方 "数据社"关注, 设为星标 后台回复【加群】&#xff0c;申请加入数据学习交流群 大家好&#xff0c;我是一哥。今天跟大家聊聊程序员35岁以后的方向怎么选&#xff1f; 作为程序员&#xff0c;行业内一直流传着35岁的魔咒&…

码农翻身(随笔)

书一直都有在读&#xff0c;我会一直更新博文&#xff0c;欢迎大家前来阅读、指教&#xff01; XML和注解 xml&#xff1a;应用于集中配置的场合&#xff0c;比如数据源的配置&#xff1b; 注解&#xff1a;像Controller、RequestMapping、Transactional这样的注解&#xff…

读《码农翻身》有感

前几日偶得一本《码农翻身》&#xff0c;闲来品读&#xff0c;收获颇丰。 作者刘欣老师可能是码农中故事讲得最好的人&#xff0c;能把线程&#xff0c;进程&#xff0c;死锁这种概念讲成计算机内部王国漫游记&#xff0c;能把分布式事务这种高级概念讲成java王国中的权贵大臣勾…

告别码农,成为真正的程序员

本文是我借助 Google 从网上拼凑的文章&#xff0c;可能条理不是很清晰&#xff0c;希望对广大程序员们有些帮助。 一、成长的寓言&#xff1a;做一棵永远成长的苹果树 一棵苹果树&#xff0c;终于结果了。 第一年&#xff0c;它结了10个苹果&#xff0c;9个被拿走&#xff0c;…

达到年薪百万,就算码农翻身了吗?

上周末有个小伙伴问我&#xff1a;码农怎么样才能真正地翻身&#xff1f; 我自己都没有翻身&#xff0c;更没有达到财务自由&#xff0c; 回答这样的问题显然是力不从心的。 &#xff08;郑渊洁对财务自由的定义&#xff1a;从现在开始不工作&#xff0c;能保持现在的生活水平不…

《码农翻身》之技术之路

《码农翻身》读书笔记之技术之路 这是我的后端读书笔记系列文章的第四三篇&#xff0c;选取的是最近刚刚圈粉的知名博主刘欣创作的《码农翻身》。这篇文章只是最后一部分内容。 本文内容主要根据知名博主刘欣一作《码农翻身》的内容总结而来&#xff0c;本书的内容风趣幽默&a…

给大忙人看的码农翻身记

码农翻身记个人读后感 文章目录 码农翻身记个人读后感本书介绍大纲启发分享个人的能力欠缺的地方书中比较棒的建议 本书介绍 《码农翻身》用故事的方式讲解了软件编程的若干重要领域&#xff0c;侧重于基础性、原理性的知识。 非常适合刚入门大学生或者 计算机领域入行 一两年左…