Python-OpenCV车牌识别简易版(1)
Python-OpenCV车牌识别简易版(1)
本程序主要在于学习OpenCV函数和建立机器视觉识别的简单思路,熟悉Python语法和numpy包的使用,熟悉Jupyter的使用,适合作为入门OpenCV的第一个小项目。
环境搭建
项目需要电脑具有Python环境,此处不作赘述。
Jupyter Notebook是一个运行在浏览器的交互式计算、编码环境,支持多种语言,以Python和R居多。它可以把代码、文本注释、数学公式、运行结果等元素组合在一起显示,动态的步进和执行。非常适合入门学习或者教学使用,我们可以很方便的看到代码每一步的执行结果。
Jupyter Notebook可以通过Anaconda或者pip安装,本文推荐用简易的pip安装的方法。
打开CMD,直接输入pip安装即可。
pip install jupyter notebook |
我们还需要安装OpenCV环境,而OpenCV有很多操作都是基于Numpy包。Numpy 是一个通用的数组处理包,提供了处理 n 维数组的工具,它是 Python 中科学计算和数据分析的基本包。继续在CMD中输入以下命令来安装numpy。
pip install numpy |
之后我们安装OpenCV-Python包,继续在CMD中输入以下命令,一路回车就行。
pip install opencv-python |
tips:如果以上安装太慢的话,可以使用国内源来安装,例如安装OpenCV可以输入pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple
两个都安装完成后,我们在CMD中输入python
回车,打开编译器输入以下命令来查看是否安装成功。
import cv2 |
from numpy import * |

其中from numpy import *
是numpy特殊的导包语句,eye(5)
是输出一个5阶对角阵。
输入exit()
退出python编译器,之后我们新建一个python虚拟环境来开始我们的学习。
所谓虚拟环境就是在本地电脑实体上借助虚拟机docker开辟出来的相对独立的Python环境,我们把独立出来的部分称为“容器“,在容器中我们可以安装本项目需要的包,避免了和其他项目相互的影响。
在Python3.3之后,自带了venv工具(之前版本需要pip安装virtualenv工具)来建立虚拟环境,我们在D盘中(或者自己想放置的文件目录下)打开CMD,输入以下代码来新建一个OpenCV_CarLicence的虚拟环境。
python -m venv OpenCV_CarLicence |
CMD不会有任何提示,但在D盘中会有一个新的文件夹,其内部结构如下所示。

进入项目目录中的Scripts文件夹中,输入activate
启动这个虚拟环境,CMD会变成以(OpenCV_CarLicence)开头的虚拟环境界面。
cd OpenCV_CarLicence |

若要退出当前虚拟环境,输入exit()
即可。
之后,我们解决如何在Jupyter Notebook中打开这个虚拟环境的问题。我们首先要在这个环境下安装Jupyter的内核kernel。
ipython kernel install --user --name=OpenCV_CarLicence |

之后我们cd ..
退回到OpenCV_CarLicence主文件中,在这里我们输入jupyter notebook
来打开jupyter的主界面。

注意,这个CMD后台不能退出,一旦退出浏览器就访问不到jupyter了。
在jupyter的由上角我们new一个Notebook,选择刚刚建立的虚拟环境。

这里就进入到了我们的代码书写界面,先回车一下,再选择第一个In[ ]:
框,再把光标移到【代码】处,选择【Markdown】,第一个输入框就变成了我们输入.md笔记的地方了。
在此我们按照.md的格式,输入#Python_OpenCV_车牌识别简易版
然后按Shirt+Enter
,就设置了本文件的大标题。

车牌定位
之后我们开始正式的编程环节,整体车牌识别要经过三个阶段:从图片中定位到车牌-把每个字符分割提取出来-对每个字符拼接。本章节我们把车牌定位出来。
我们需要两个基础的函数库,一个是cv2,是opencv的python库,一个是matplotlib,用于在Python中创建静态、动态的交互式图像。
我们先定义三个用来现实图片的方法,运行的快捷键是Shift
+Enter
。
import cv2 |
【1】from matplotlib import pyplot as plt
Pyplot 是 Matplotlib 的子库,提供了和 MATLAB 类似的绘图 API,使用的时候,我们可以使用 import 导入 pyplot 库,并设置一个别名 plt。
【2】b,g,r = cv2.split(img)
分离出图片的B,R,G颜色通道,之后要合并成BGR再imshow,cv2.imshow("合并后的图像",cv2.merge([B,G,R]))
合并(B,G,R)三通道。
之后我们加载图片,利用cv2.imread()
。
#加载图片 |

读取到了原图就要先高斯去噪,可以在保留主要特征的情况下去除噪音、平滑图像,高斯滤波是用一个卷积核来对整个图像的每一个像素进行加权平均。
#高斯去噪 |
【3】高斯去噪 cv2.GaussianBlur(img, ksize, sigmaX, sigmaY, borderType)
卷积核的大小决定了模糊的范围;sigmaX 和 sigmaY 是高斯核在 X 和 Y 方向上的标准差,表示模糊的强度,如果为0,则根据高斯核的大小自动计算。
borderType 是边界处理的方式,可以是 cv2.BORDER_CONSTANT常量, cv2.BORDER_REPLICATE重复, cv2.BORDER_REFLECT反射, cv2.BORDER_WRAP包裹等,默认为 cv2.BORDER_DEFAULT。
正常的图片三通道信息量太大,不适合处理,所以我们下一步把图片转为灰度图。
#灰度处理 |
之后我们对图片提取轮廓信息,特别是纵向的轮廓信息,因为车牌地区纵向的线条会比较多。Sobel边缘检测可以把图像的边缘信息提取出来,适用于灰度图和噪声较多的图片。图片的轮廓信息是灰度值变化最大的地方,Sobel利用一阶导最大值来检测边界,可以分为X轴方向和Y轴方向。
# Sobel边缘检测 |
【4】Sobel边缘检测 Sobel_y = cv2.Sobel(gray_image,cv2.CV_16S,1,0)
Sobel算子用于边缘检测的离散微分算子,在追求效率且不那么重视信息量的情况下可以使用Sobel,但是Canny才是永远的神。1和0分别是x和y方向上的求导阶数,表示只在x方向上求一阶导数,最后得到纵向边缘,Gx 用于检测纵向边缘, Gy 用于检测横向边缘,需要x方向图像就对y求微分,需要y方向图像就对x求微分,两者颠倒。
【5】像素取绝对值 dst = cv2.convertScaleAbs(src, alpha, beta)
alpha表示像素值缩放的比例因子,beta表示像素值偏移量,如果我们想要将一个16位的深度图像转换为8位的彩色图像,并增加对比度和亮度,我们可以使用以下代码:depth_image = cv2.convertScaleAbs(depth_image, alpha=0.03, beta=100)
。
现在图片上还有很多浅浅的灰色线条,我们设定一个阈值,让大于这个值的灰度色就变成白色,小于这个颜色的变成黑色。这样可以使整张图片看起来有效信息更突出。
#自适应阈值处理,处理完成之后图片非黑即白 |
【6】阈值处理ret,image = cv2.threshold(image,0,255,cv2.THRESH_OTSU)
threshold()阈值处理,输入必须是单通道的灰度图,0是用于计算阈值的参数,当使用Otsu’s二值化时,该参数无效,可以设置为任意值。
255是用于赋值给大于或等于阈值的像素的最大值,通常为255,表示白色。
cv2.THRESH_OTSU是阈值类型,表示使用Otsu’s算法自动计算阈值,并将小于阈值的像素赋值为0,表示黑色。ret是计算出的最佳阈值,可以用于后续的分析或处理。
image是阈值处理后的图像,是一个二值图像,只包含0和255两种像素值。
THRESH_BINARY:最基础的二进制阈值化,大于阈值的像素设为最大值,小于阈值的像素设为0。
THRESH_BINARY_INV:反二进制阈值化,小于阈值的像素设为最大值,大于阈值的像素设为0。
THRESH_TRUNC:截断阈值化,大于阈值的像素设为阈值,小于阈值的像素不变。
THRESH_TOZERO:阈值化为0,小于阈值的像素设为0,大于阈值的像素不变。
THRESH_TOZERO_INV:反阈值化为0,大于阈值的像素设为0,小于阈值的像素不变。
THRESH_MASK:掩码阈值化,使用掩码图像来决定哪些像素需要阈值化。THRESH_OTSU:
Otsu’s阈值化,使用Otsu’s算法自动寻找最佳阈值,只支持8位单通道图像。
THRESH_TRIANGLE:Triangle阈值化,使用Triangle算法自动寻找最佳阈值,只支持8位单通道图像。
之后对图像进行闭运算,闭运算就是先腐蚀再膨胀,可以填充图片中的小孔或使得边缘平滑。
# 闭运算,先膨胀再腐蚀,可以填充图像中的小孔或者平滑边缘 |

【7】闭运算 getStructuringElement() morphologyEx()
cv2.getStructuringElement (shape, ksize, anchor = new cv.Point (-1, -1)) 创建一个指定形状和大小的卷积核,用于后续的形态学操作。
shape 表示结构元素的形状,可以是以下值之一:
cv2.MORPH_RECT 表示矩形形状,即所有的元素都是1;
cv2.MORPH_CROSS 表示十字形状,即中心行和列的元素都是1,其余为0;
cv2.MORPH_ELLIPSE 表示椭圆形状,即根据指定的大小生成一个椭圆;
ksize 表示结构元素的大小,是一个二元组,表示宽度和高度。
anchor 表示结构元素的锚点位置,是一个二元组,表示结构元素中心的坐标。默认值是 (-1, -1)。
cv2.morphologyEx (src, op, kernel, dst, anchor, iterations, borderType, borderValue) 这个函数是对图像进行形态学变换的通用函数,可以实现腐蚀、膨胀、开、闭、梯度、顶帽、黑帽等操作。
src 表示输入图像,可以是任意通道数,但深度必须是 cv2.CV_8U, cv2.CV_16U, cv2.CV_16S, cv2.CV_32F 或 cv2.CV_64F 之一。
op 表示形态学操作的类型,可以是以下值之一:
cv2.MORPH_ERODE 表示腐蚀操作,即将结构元素滑动到图像的每个位置,如果结构元素覆盖的像素都是1,那么输出图像的对应位置也是1,否则是0。这样可以减少图像中的白色区域,去除小白点或分离相连的物体;
cv2.MORPH_DILATE 表示膨胀操作,即将结构元素滑动到图像的每个位置,如果结构元素覆盖的像素中有一个是1,那么输出图像的对应位置也是1。这样可以增加图像中的白色区域,填补小孔或连接断裂的物体;
cv2.MORPH_OPEN 表示开操作,即先进行腐蚀操作,再进行膨胀操作。这样可以去除小的白色噪声,或者分离相连的物体;
cv2.MORPH_CLOSE 表示闭操作,即先进行膨胀操作,再进行腐蚀操作。这样可以填补物体内部的小孔,或者去除物体上的小黑点;
cv2.MORPH_GRADIENT 表示梯度操作,即膨胀图像和腐蚀图像的差。这样可以得到物体的轮廓;
cv2.MORPH_TOPHAT 表示顶帽操作,即原始图像和开操作之后的图像的差。这样可以得到比周围亮的区域;
cv2.MORPH_BLACKHAT 表示黑帽操作,即闭操作之后的图像和原始图像的差。这样可以得到比周围暗的区域。
kernel 表示结构元素,可以是 cv2.getStructuringElement 函数创建的,也可以是自定义的二维数组。
dst 表示输出图像,和输入图像有相同的大小和类型。
anchor 表示结构元素的锚点位置,和 cv2.getStructuringElement 函数的参数含义相同。默认值是 (-1, -1),表示结构元素的中心是其几何中心。
iterations 表示迭代次数,默认值是1。
borderType 表示边界填充的方式,和 cv2.copyMakeBorder 函数的参数含义相同。可以是以下值之一:
cv2.BORDER_CONSTANT 表示用常数填充边界,需要指定 borderValue 参数;
cv2.BORDER_REPLICATE 表示用边界像素的值填充边界;
cv2.BORDER_REFLECT 表示用边界像素的镜像填充边界,不包括边界像素本身;
cv2.BORDER_REFLECT_101 表示用边界像素的镜像填充边界,包括边界像素本身;
cv2.BORDER_WRAP 表示用另一边的像素填充边界;
cv2.BORDER_DEFAULT 表示用 cv2.BORDER_REFLECT_101 的方式填充边界;
borderValue 表示边界填充的常数值,只有当 borderType 为 cv2.BORDER_CONSTANT 时有效。默认值是 cv2.morphologyDefaultBorderValue (),表示自动选择一个合适的值。
现在的图片上小白点还是很多,这些都不会是车牌出现的位置,我们去除这些小白点。
#去除一些小白点 |

可见图片上还有一些细小的白条,没有和大片的区域链接起来,我们采用中值滤波让他们图片变的平滑,让这些本该在一起的连接起来。
# 中值滤波 除去噪点 |

【8】中值滤波 image = cv2.medianBlur(image,15)
dst = cv2.medianBlur(src, ksize)是OpenCV中用于对图像进行中值滤波的函数。
中值滤波是一种非线性滤波方法,它的原理是用邻域内所有像素值的中位数来替换当前像素值,从而消除图像中的椒盐噪声等。ksize表示滤波核的大小,必须是大于1的奇数。