如何从汽车照片中扣出车牌?

为了做车牌识别训练,先单独研究下怎么从照片里扣出车牌区域。

大概思路:转化成灰度图、使用高斯平滑、中值滤波、Sobel边缘化检测、膨胀轮廓、腐蚀细节、轮廓筛选、保存轮廓区域原图。

代码如下:

vim cut_platenumber.py 
#!/usr/bin/env python
# coding=utf-8
# 从下载的图片里扣出车牌区域
# sh clear.sh && python cut_platenumber.py >log/cut_platenumber.log
import uuid
import os
import copy
import cv2
import numpy as np
import random

class CutPlateNumber:
    """从下载的图片里扣出车牌区域"""

    def preprocess(self, gray, name=''):
	"""形态学变换的预处理"""
	print '__preprocess__'
        # 高斯平滑
        gaussian = cv2.GaussianBlur(gray, (3, 3), 0, 0, cv2.BORDER_DEFAULT)
	self.write_image(gaussian, name, 'gaussion')
        # 中值滤波
        median = cv2.medianBlur(gaussian, 5)
	self.write_image(median, name, 'median')
        # Sobel算子,X方向求梯度,对图像进行边缘检测
        #sobel = cv2.Sobel(median, cv2.CV_8U, 1, 0, ksize=3)
        sobel = cv2.Sobel(median, cv2.CV_16S, 1, 0, ksize=3)
	sobel = cv2.convertScaleAbs(sobel)
        # 二值化
        ret, binary = cv2.threshold(sobel, 170, 255, cv2.THRESH_BINARY)
	self.write_image(binary, name, 'threshold')
        # 膨胀和腐蚀操作的核函数
        element1 = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 1))
        element2 = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 7))
        # 膨胀一次,让轮廓突出
        dilation = cv2.dilate(binary, element2, iterations=1)
	self.write_image(dilation, name, 'dilation')
        # 腐蚀一次,去掉细节
        erosion = cv2.erode(dilation, element1, iterations=1)
	self.write_image(erosion, name, 'erosion')
        # 再次膨胀,让轮廓明显一些
        dilation2 = cv2.dilate(erosion, element2, iterations=2) #iterations设置太大但车牌区域很小时非车牌区域容易边缘连片过度,设置太小但车牌占比过大时容易省简称和后边连不上
	self.write_image(dilation2, name, 'dilation2')
        return dilation2


    def find_region(self, img, imagePath='', name=''):
        """查找车牌所在区域"""
	print '__find_region__'
        # 查找轮廓
        binary, contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

        # 轮廓筛选
        region = {}
	standard_w_h_retio = 45/15 #标准车牌宽45cm/高15cm=3
	standard_full_retio = 0.2 #常见车牌占图片大小
	min_ratio_diff = 99 #与标准车牌长宽比差异最小的值*在图像中的占比大小
	img_regins = cv2.imread(imagePath)  #所有轮廓叠加对象
        for i in range(len(contours)):
            cnt = contours[i]
            # 计算该轮廓的面积
            area = cv2.contourArea(cnt)
            # 面积小的都筛选掉
            if (area < 2000):
                continue

            # 找到最小的矩形,该矩形可能有方向
            rect = cv2.minAreaRect(cnt)

            # box是四个点的坐标
            box = cv2.cv2.boxPoints(rect)
            box = np.int0(box)
	    #print box

	    # 不规则形状转矩形(取左上角最小和右下角最大坐标)
	    xs = [box[0, 0], box[1, 0], box[2, 0], box[3, 0]]
            ys = [box[0, 1], box[1, 1], box[2, 1], box[3, 1]]
            xs_sorted_index = np.argsort(xs)
            ys_sorted_index = np.argsort(ys)
	    #print xs,xs_sorted_index
	    #print ys,ys_sorted_index

            x1 = box[xs_sorted_index[0], 0] - 20 #左侧省简称部分边缘检测效果不太好,轮廓往左多取一点
            y1 = box[ys_sorted_index[0], 1]
            x2 = box[xs_sorted_index[3], 0]
            y2 = box[ys_sorted_index[3], 1]
	    if x1 < 1: x1 = 1
	    if y1 < 1: y1 = 1

            # 计算矩形宽高比
            width = abs(x2 - x1)
            height = abs(y2 - y1)
            wh_ratio = float(width) / float(height)
	    #计算矩形在大图中的像素占比
	    full_ratio = float(width*height) / float(img.shape[0] * img.shape[1])
            #计算矩形在图像中的上下位置与图片大小的匹配合理性(值越小表示越合理)
	    postion_right_ratio_y = abs(0.5 - float(y2-height/2)/img.shape[1]) * abs(full_ratio - standard_full_retio)  #上下位置与图片大小合理性
	    postion_right_ratio_x = abs(0.5 - float(x2-height/2)/img.shape[0]) * abs(full_ratio - standard_full_retio)  #左右位置与图片大小合理性
	    #postion_right_ratio = (postion_right_ratio_y**2*600 + postion_right_ratio_x**2*400)/2 #放大合理性的差值,上下权重高些 
	    postion_right_ratio = (postion_right_ratio_y*0.6 + postion_right_ratio_x*0.4)/2 #放大合理性的差值,上下权重高些 
            #复合权重比(高宽比与标准的差值、图像位置与图像大小的合理性)
	    ratio = abs(wh_ratio - standard_w_h_retio)*0.03 + postion_right_ratio * 0.97
	    print abs(wh_ratio - standard_w_h_retio),postion_right_ratio

	    #宽高比及拍照框占比过滤
	    print i,area,'['+str(x1)+','+str(y1)+' '+str(x2)+','+str(y2)+']',round(wh_ratio,2),round(full_ratio,3),round(postion_right_ratio_x,2),round(postion_right_ratio_y,2),round(postion_right_ratio,2),round(ratio,4)
            if (wh_ratio < standard_w_h_retio - 1.2 or wh_ratio > standard_w_h_retio + 2) or (full_ratio < 0.015 or full_ratio > 0.5):    #高宽比>2.3 && <5; 拍照框在整图中的占比应>5% && <50%
                continue

	    # 矩形轮廓标记图
	    img_regin = cv2.imread(imagePath)
	    cv2.rectangle(img_regin, (x1, y1), (x2, y2), (0, 255, 0), 2)  #单轮廓
	    cv2.rectangle(img_regins, (x1, y1), (x2, y2), (0, 255, 0), 2) #轮廓叠加
	    cv2.putText(img_regins, str(i)+' '+str(round(ratio,4)), (x1+3, y1+15), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 255), 1) #轮廓叠加图上标记轮廓序号
	    self.write_image(img_regin, name, 'regin'+str(i))
	    if ratio < min_ratio_diff: #保存复合权重最合理的那个轮廓
		min_ratio_diff = ratio
	        region = {'x1':x1,'x2':x2,'y1':y1,'y2':y2}

	self.write_image(img_regins, name, 'regin')  #轮廓叠加图像保存
        return region

    def write_image(self, img, name, step='', path='tmp'):
	"""保存图片并打印"""
	if False:
	    return
	if step != '':
	    img_file = './data/'+path+'/'+name.split('.')[0]+'.'+step+'.'+name.split('.')[1]
	else:
	    img_file = './data/'+path+'/'+name
	cv2.imwrite(img_file, img)
	print img_file

    def start(self, imagePath, name):
	"""开始裁剪"""
	print "START\t" + imagePath
        # 读取图片
        img = cv2.imread(imagePath)
        # 转化成灰度图
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
	self.write_image(gray, name, 'gray')
        # 形态学变换的预处理
        dilation = self.preprocess(gray, name)
        # 查找车牌区域
        region = self.find_region(dilation, imagePath, name)
	print region
        if len(region) > 0:  #车牌轮廓查找成功
	    # 从灰度图中扣取轮廓矩形并缩放
            img_plate = cv2.resize(gray[region['y1']:region['y2'], region['x1']:region['x2']] ,(180, 60))
	    # 保存车牌区域抠图结果
	    save_path = 'images_train'
	    if random.random() < 0.1:  #10%放到测试集
	        save_path = 'images_test'
	    self.write_image(img_plate, name, path=save_path)
	    print "FOUND PLATE NUMBER!\t" + name

if __name__ == '__main__':
    img_path = './data/download_rename/'
    cutPlateNumber = CutPlateNumber()
    # 获取原图列表
    imgs = os.listdir(img_path)
    for img in imgs:
	#img = '辽B48599.jpg'
        cutPlateNumber.start(img_path + img, img)
	#break

单测结果:

各步骤效果图:

原图:

灰度图:

高斯平滑:

中值滤波:

sobel边缘检测+二值化:

轮廓膨胀一次:

腐蚀细节:

再次轮廓膨胀:

轮廓转换矩形后,筛选出的高宽比最接近车牌的区域:

序号 面积 [x1,y1 x2,y2] 宽高比 面积占比 上下位置与图片大小合理性 左右位置与图片大小合理性 平均合理性 复合权重比

3 8226.5 [291,366 483,437] 2.7 0.031 0.05363 0.00042 0.011 0.0194

从灰度图对应位置中扣取轮廓矩形并缩放

 

这个方法的关键点在边缘检测后的膨胀腐蚀和轮廓的筛选,可以通过调节preprocess和筛选参数来优化扣图效果。

 

yan 18.12.4 23:45

参考:https://blog.csdn.net/sunny2038/article/details/9170013

欢迎关注下方“非著名资深码农“公众号进行交流~

发表评论

邮箱地址不会被公开。