车牌分割实战

对于车牌分割我会从以下几个知识点来论述,最后对车牌分割的代码进行详解

1.图像边缘

图像边缘,即表示图像中一个区域的终结和另一个区域的开始,图像中相邻区域之间的像素集合构成了图像的边缘。所以,图像边缘可以理解为图像灰度发生空间突变的像素的集合。图像边缘有两个要素,即:方向和幅度。沿着边缘走向的像素值变化比较平缓;而沿着垂直于边缘的走向,像素值则变化得比较大。因此,根据这一变化特点,通常会采用一阶和二阶导数来描述和检测边缘。
综上,图像中的边缘检测可以通过对灰度值求导数来确定,而导数可以通过微分算子计算来实现。在数字图像处理中,通常是利用差分计算来近似代替微分运算。如下图所示。
在这里插入图片描述

2.Canny算子

Canny边缘检测算法的原理:

  1. 首先要对原始图像进行灰度化
  2. 对图像进行高斯滤波:这个应该都很清楚了,就是采用高斯核函数,根据其sigma 的不同对图像进行不同程度的模糊处理,虽然叫模糊处理,实质是滤除了高频的噪声。
  3. 根据Canny算法所采用的卷积算子,对灰度图像求梯度。
    卷积算子为:在这里插入图片描述
    在这里插入图片描述
  4. 对梯度幅值进行非极大值抑制。
    通过上面的计算,就已经确定了每个像素的梯度和幅值,该点的像素梯度方向会根据邻域像素,计算8个方向的梯度直方图,由此确定该点像素的主方向。通常像素点局部最大值就是边缘点,垂直于该梯度方向,下一个点(可能点 s 和 r)为下一个边缘点(在两个像素点之间,可以通过邻域两个像素点进行插值来计算)
3.形态学(膨胀和腐蚀)

膨胀就是求局部最大值的操作。按数学方面来说,膨胀或者腐蚀操作就是将图像(或图像的一部分区域,我们称之为A)与核(我们称之为B)进行卷积,即计算核B覆盖的区域的像素点的最大值,并把这个最大值赋值给参考点指定的像素。这样就会使图像中的高亮区域逐渐增长。(膨胀的处理效果)如下图所示。
在这里插入图片描述
腐蚀就是求局部最小值的操作。(腐蚀的处理效果)如下图所示。
在这里插入图片描述
数学公式对比:
在这里插入图片描述

4.开运算和闭运算

因为没有深入去了解基于数学基础是怎么实现的,这里只赘述了开运算和闭运算对图像有什么作用,以及试验的结果:
开运算(Opening Operation)其实就是先腐蚀后膨胀的过程:开运算可以用来消除小物体、在纤细点处分离物体、平滑较大物体的边界的同时并不明显改变其面积,如下图所示。
在这里插入图片描述
闭运算(Closing Operation)是先膨胀后腐蚀的过程:闭运算能够排除小型黑洞。如下图所示。
在这里插入图片描述

5.车牌分割完整代码
import cv2
import numpy as np
from PIL import Image
import os.path
from skimage import io,data

def stretch(img):
    #图像拉伸函数
    maxi = float(img.max())
    #print(maxi) #255.0
    mini = float(img.min())
    #print(mini) 0.0
    #[0]图像的高 [1]图像的宽 [2]图像的通道数
    for i in range(img.shape[0]):
        for j in range(img.shape[1]):
            img[i,j] = (255/(maxi-mini)*img[i,j]-(255*mini)/(maxi-mini))
    #print(img.shape[0]) 266
    #print(img.shape[1]) 400
    return img

def dobinaryzation(img):
    #二值化处理函数
    maxi = float(img.max())
    #print(img.max())   #205
    mini = float(img.min())
    #print(img.min())   0
    x = maxi-((maxi-mini)/2)
    #print(x)   102.5
    #二值化,返回阈值ret和二值化操作后的图像thresh
    ret,thresh = cv2.threshold(img,x,255,cv2.THRESH_BINARY)
    #返回二值化后的黑白图像
    return thresh

def find_rectangle(contour):
    #寻找矩形轮廓
    y,x = [],[]
    for p in contour:
        y.append(p[0][0])
        x.append(p[0][1])
    return [min(y),min(x),max(y),max(x)]

def locate_license(img,afterimg):
    #定位车牌号
    img,contours,hierarchy = cv2.findContours(img,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
    #找出最大的三个区域
    n=len(contours)
    print(n)  #16
    block = []
    for c in contours:
        #找出轮廓的左上点和右下点,由此计算它的面积和长度比
        r = find_rectangle(c)
        a = (r[2]-r[0])*(r[3]-r[1])#面积
        s = (r[2]-r[0])/(r[3]-r[1])#长度比
        block.append([r,a,s])
        #选出面积最大的三个区域
        block = sorted(block,key=lambda b:b[1])[-3:]
        # print(len(block))  16
        #使用颜色识别判断找出最像车牌的区域
        maxweight,maxindex = 0,-1
    for i in range(len(block)):
        b = afterimg[block[i][0][1]:block[i][0][3],block[i][0][0]:block[i][0][2]]
        #bgr转hsv
        hsv = cv2.cvtColor(b,cv2.COLOR_BGR2HSV)
        #蓝色车牌的范围
        lower = np.array([100,50,50])
        upper = np.array([140,255,255])
        #根据阈值构建掩摸
        mask = cv2.inRange(hsv,lower,upper)
        #print(mask)
        #统计权值
        w1 = 0
        for m in mask:
            w1 += m/255
        w2 = 0
        #选出权值最大的区域
        for n in w1:
            w2 += n
        if w2 > maxweight:
            maxindex = i    #16个矩形框中的哪一个
            maxweight = w2
    return block[maxindex][0]

def find_license(img):
    #预处理函数
    m = 400*img.shape[0]/img.shape[1]
    print(m)
    #print(img.shape[0])  600 高
    #print(img.shape[1])  900 宽
    #压缩图像
    img = cv2.resize(img,(400,int(m)),interpolation=cv2.INTER_CUBIC)
    #print(img.shape[0])  266  img.shape[1]  400  (266,400)
    #bgr转换成灰度图像
    gray_img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    #灰度拉伸
    stretchedimg = stretch(gray_img)
    #开运算,用来去除噪声
    r=16
    h=w=r*2+1
    kernel =np.zeros((h,w),np.uint8)
    cv2.circle(kernel,(r,r),r,1,-1)
    #开运算
    openingimg = cv2.morphologyEx(stretchedimg,cv2.MORPH_OPEN,kernel)
    #获取差分图像,两幅图像做差
    strtimg = cv2.absdiff(stretchedimg,openingimg)
    #图像二值化
    binaryimg = dobinaryzation(strtimg)
    #canny边缘检测
    canny = cv2.Canny(binaryimg,binaryimg.shape[0],binaryimg.shape[1])
    #消除小的区域,保留大的区域,从而定位车牌
    #进项闭运算
    kernel = np.ones((5,19),np.uint8)
    closingimg = cv2.morphologyEx(canny,cv2.MORPH_CLOSE,kernel)
    #进行开运算
    openingimg = cv2.morphologyEx(closingimg, cv2.MORPH_OPEN, kernel)
    #再次进行开运算
    kernel = np.ones((11,5),np.uint8)
    openingimg = cv2.morphologyEx(openingimg,cv2.MORPH_OPEN,kernel)
    #消除小区域,定位车牌位置
    rect = locate_license(openingimg,img)
    return rect,img
def cut_license(afterimg,rect):
    #图像分割函数
    #转换为宽度和高度
    rect[2]=rect[2]-rect[0]
    rect[3]=rect[3]-rect[1]
    rect_copy = tuple(rect.copy())
    rect=[0,0,0,0]
    #创建掩摸
    mask = np.zeros(afterimg.shape[:2],np.uint8)
    #创建背景模型,大小只能为13*5 行数只能为1 单通道浮点数
    bgdmodel = np.zeros((1,65),np.float64)
    #创建前景模型
    fgdmodel = np.zeros((1,65),np.float64)
    #分割图像
    cv2.grabCut(afterimg,mask,rect_copy,bgdmodel,fgdmodel,5,cv2.GC_INIT_WITH_RECT)
    mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8')
    #找到所有确定的背景或可能的背景像素,并将它们设置为0 ,所有其他像素应该标记为1(即:,前景)
    img_show = afterimg*mask2[:,:,np.newaxis]
    return img_show
def deal_license(licenseimg):
#车牌图片二值化
    #车牌变为灰度图像
    gray_img = cv2.cvtColor(licenseimg,cv2.COLOR_BGR2GRAY)
    #均值滤波,去除噪声
    kernel = np.ones((3,3),np.float32)/9
    gray_img = cv2.filter2D(gray_img,-1,kernel)
    #二值化处理
    ret,thresh = cv2.threshold(gray_img,120,255,cv2.THRESH_BINARY)
    return thresh

if __name__ == '__main__':
    img = cv2.imread('car3.jpg',cv2.IMREAD_COLOR)
    #预处理图像
    #img = stretch(img)
    #cv2.imshow('img',img)
    rect,afterimg = find_license(img)
    #框处车牌号
    cv2.rectangle(afterimg,(rect[0],rect[1]),(rect[2],rect[3]),(0,255,0),2)
    cv2.imshow('afterimg',afterimg)
    #分割车牌与背景
    cutimg = cut_license(afterimg,rect)
    cv2.imshow('cutimg',cutimg)
    #二值化生成黑白图
    thresh = deal_license(cutimg)
    cv2.imshow('thresh',thresh)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
6.实验结果

以上是车牌区域分割的完整代码,分割效果如下:
在这里插入图片描述
在这里插入图片描述
从分割结果来看,还是不错的,那么接下来我们就仔细的剖析一下代码(说明:图片完全是我自己整理的笔记然后截图过来的,因为这是老师给我们布置的一次小实验,我是Word整理好的。图片解析的顺序也是根据函数的顺序依次进行排列的,有想要PDF或者Word的小伙伴可以直接私信我):

7.代码解析

1.预处理 图像拉伸
在这里插入图片描述
2.图像预处理 二值化处理
在这里插入图片描述
3.寻找矩形轮廓
在这里插入图片描述
在这里插入图片描述
4.对图像拉伸(涉及函数的调用)、开运算和闭运算、以及对图像Canny边缘检测
在这里插入图片描述
在这里插入图片描述
5.图像分割函数
在这里插入图片描述
6.分割得到的车牌区域二值化
在这里插入图片描述
到这里车牌分割的代码就解读完了,其实车牌分割还可以应用很多种方法,一般的车牌区域都是长方形区域,就可以根据查找车身的矩形框来做;还可以根据车牌的颜色来分割,一般的车牌都是蓝白和黄白,完全可以根据颜色实现分割。

7.实验不足与展望
首先对于车牌区域模糊的图像,处理的效果并不是很理想,还有对倾斜车身的这样的情况,车牌分割的效果也不是很理想,我看了很多的文章,有很多人用到了仿射变换的方法,这个我没有尝试。另外就是程序中,对图像进行开运算和闭运算,这个开和闭的顺序朋友们可以自己去尝试,也可以试试两次开运算一次闭运算,反之两次闭运算一次开运算。总的来说,需要改进的地方还很多~~

2022-4-28新的代码(因为上面的代码也是参考别人的,所以呢,过了好久,我在运行我以前写的程序,各种报错,实在是懒得看,所以我自己又写了一个简易的分割代码,非常简单)

import cv2
from matplotlib import pyplot as plt
## 根据每行和每列的黑色和白色像素数进行图片分割。
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
#
# 读取彩色的图片
def gray(img):
    # 转换为灰度图
    img1 = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
    plt.title("huidutu")
    plt.imshow(img1)
    plt.show()
    return img1

def gaosi(img1):
    #高斯滤波
    img2 = cv.GaussianBlur(img1, (5, 5), 10)
    plt.title("gaositu")
    plt.imshow(img2)
    plt.show()
    return img2

def sobel(img2):
    # 用Sobel进行边缘检测
    img3 = cv.Sobel(img2, cv.CV_8U, 1, 0, ksize=1)
    plt.title("bianyuantu")
    plt.imshow(img3)
    plt.show()
    return img3

def cannyy(img3):
    #canny边缘检测
    img4 = cv.Canny(img3, 250, 100)
    plt.title("cannytu")
    plt.imshow(img4)
    plt.show()
    return img4


def erzhihua(img4):
    #二值化
    i, img5 = cv.threshold(img4, 0, 255, cv.THRESH_BINARY)
    plt.title("erzhihua")
    plt.imshow(img5)
    plt.show()
    return i,img5

def fushi_pengzhang(img5):
    # 腐蚀和膨胀
    kernel = cv.getStructuringElement(cv.MORPH_RECT, (43, 33))
    img6 = cv.dilate(img5, kernel)
    plt.title("fushi_pengzahng")
    plt.imshow(img6)
    plt.show()
    return img6

def deal_license(licenseimg):
    # 车牌图片二值化
    # 车牌变为灰度图像
    gray_img = cv2.cvtColor(licenseimg, cv2.COLOR_BGR2GRAY)
    # 均值滤波,去除噪声
    kernel = np.ones((3, 3), np.float32) / 9
    gray_img = cv2.filter2D(gray_img, -1, kernel)
    # 二值化处理
    ret, thresh = cv2.threshold(gray_img, 120, 255, cv2.THRESH_BINARY)
    return thresh

if __name__=='__main__':
    #       这是调用上面函数的一个主程序
    #------------读入汽车图片------------
    img = cv.imread("car.png")
    #------显示读入的原图------
    plt.title("yuantu")
    plt.imshow(img)
    plt.show()
    # 转换为灰度图
    img1 = gray(img)
    # 高斯模糊
    img2 = gaosi(img1)
    # 用Sobel进行边缘检测
    img3 = sobel(img2)
    #  用canny边缘检测
    img4 = cannyy(img3)
    # 进行二值化处理
    i,img5 = erzhihua(img4)
    # 可以侵蚀和膨胀
    img6 = fushi_pengzhang(img5)
    # # 循环找到所有的轮廓
    i,j = cv.findContours(img6,cv.RETR_TREE,cv.CHAIN_APPROX_SIMPLE)
    result = None
    y,x = 0,0
    h,w = 0,0
    for i1 in i:
        x,y,w,h = cv.boundingRect(i1)
        if w>2*h:
            print(1)
            #  显示最后车牌区域的图片(图片标题)
            plt.title("last_img")
            plt.imshow(img[y:y+h,x:x+w])
            #  保存最后的车牌区域图片(直接这样写,是保存在该程序的同级文件夹下)
            plt.savefig('last.jpg')
            plt.show()
            result = img[y:y+h,x:x+w]
    # 对分割出来的车牌区域进行二值化生成黑白图
    thresh = deal_license(result)
    #显示图片
    cv2.imshow('thresh', thresh)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
结果如下

** 原图**
在这里插入图片描述
分割后
在这里插入图片描述
分割后二值化
在这里插入图片描述

总结

新程序里面的寻找矩形框可以结合最上面的代码进行替换,效果会更好,其实其中主要的原理就是根据opencv中的函数寻找矩形框,因为大多数车牌区域都是矩形。对二值化后的车牌区域可以进行字符识别分割,我看网上也有人实现,基本就是在该程序后面加后续的字符识别,直接堆叠就完事了,如果有不懂的,也可以在私信我。

  • 8
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

勇敢牛牛@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值