【图像处理】利用numpy、opencv、python实现车牌检测

|

利用opencv实现车牌检测

整体流程涉及5个部分

  • 图像通道转换
  • 对比度增强
  • 边缘连接
  • 二值化
  • 边界区域裁剪

图像通道转换

将RGB图像转换为HSV图像,仅保留V通道。V通道表示颜色的明暗,常用于图像对比度拉伸、直方图均衡化等流程。

原图像:

V通道图像:

对比度增强

通过顶帽变换来实现对比度增强。顶帽变换用于提取图像的小区域和局部细节。白顶帽变换用于提取图像中比周围环境亮的小物体或细节;黑顶帽变换用于提取图像中比周围环境暗的小物体或细节。

白顶帽变换:

黑顶帽变化:

通过白顶帽、黑顶帽的联合处理: I e n h a n c e d = I o r i g i n a l + I w h i t e − t o p _ h a t − I b l a c k − t o p _ h a t I_{enhanced}=I_{original}+I_{white-top\hat}-I{black-top\hat} Ienhanced=Ioriginal+Iwhite−top_hat−Iblack−top_hat,其中 I o r i g i n a l I{original} Ioriginal表示原图像, I w h i t e − t o p _ h a t I_{white-top\hat} Iwhite−top_hat表示白顶帽处理后图像, I b l a c k − t o p _ h a t I{black-top\_hat} Iblack−top_hat表示黑顶帽处理后图像,得到对比度增强后的图像:

边缘连接

增强对比度后,很多车牌边缘不连续,例如

需要通过膨胀操作(Dilation Operation)来扩展边缘,实现边缘连接的目的。

添加膨胀操作后,图像转变为:

二值化

将单通道V图像转换为二值图像,具体策略为Adaptive thresholding

边界区域裁剪

  • 首先,利用cv2.findContours检测边界,并且获得边界的层级(hierarchy)。
  • 车牌检测可以理解为找到内边界,而整个图像的背景可以理解为是外边界。下图是检测出的内边界

    对内边界进行阈值判断处理,过滤掉明显错误的情况。例如过滤面积小于2000的内边界(具体数值需要按照实际情况来定)
  • 对于每个内边界,计算外接最小的矩形(可以通过统计边界内最左、最上、最右、最下的点来合成矩形),作为初步检测框
  • 有一些检测框可能包括多个车牌,宽度、高度比较大。对于这种情况,需要对检测框按照宽度、高度均匀分割。以下是一个高度过大的例子,需按高度均分
  • 有一些车牌因为自身比较模糊,导致检测框不准确,可以通过统计信息来过滤掉,本方法暂不处理。例如

最终,整张图有41个车牌,通过上述方法,检测到了40个车牌,效果不错。漏检的车牌本身边缘不清晰,检测难度较大

消融实验

方法 最终图像检测框 车牌检测数量
最终方法 40
去掉对比度增强 39
去掉边缘连接 39
内边界面积过滤阈值4000 38
内边界面积过滤阈值5000 38

代码

"""
主要的步骤为:
1)提取单通道图片,选项为 (灰度图片/HSV中的value分支)
2)提升对比度,选项为 (形态学中的顶帽/灰度拉伸)
3)边缘连接(膨胀)
4)二值化
5)利用findcontours函数找到边缘
6)裁剪图片,车牌图片存储
7) 对车牌预处理
8)方向矫正
9)车牌精确区域搜索
10) 字符分割
11) 字符识别
"""

import cv2
import copy
import numpy as np
import math
import os

def SingleChannel(img) :
    """
    用于车牌检测
    得到单通道图片,主要测试两种方式,灰度通道以及hsv中的v通道
    :param img: 输入图片
    :return:
    """
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    hue, saturation, value = cv2.split(hsv)
    cv2.imshow("SingleChannel", value)
    return value

def Contrast(img) :
    """
    用于车牌检测
    利用tophat,提高图片对比度,
    :param img: 输入图片
    :return:
    """
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
    # applying topHat/blackHat operations
    topHat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)
    cv2.imshow("tophat", topHat)
    blackHat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)
    cv2.imshow("blackhat", blackHat)
    add = cv2.add(img, topHat)
    subtract = cv2.subtract(add, blackHat)
    cv2.imshow('Constrast', subtract)
    return subtract

def threshold(img) :
    """
    用于车牌检测
    采用cv2.adaptiveThreshold方法,对图片二值化
    :param img: 输入图像
    :return:
    """
    thresh = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 19, 9)
    cv2.imshow("thresh", thresh)
    return thresh


global crop_num
crop_num = 0

def drawCoutrous(img_temp) :
    """
    对输入图像查找内边缘,设置阈值,去除一些面积较小的内边缘
    :param img_temp: 输入图像,经过预处理
    :return:
    """
    threshline = 2000
    imgCopy = copy.deepcopy(img_temp)
    contours, hierarchy = cv2.findContours(imgCopy, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
    # print(len(contours), contours[0].shape)
    # print(hierarchy.shape)
    maxarea = 0
    conid = 0
    img_zero = np.zeros(img.shape)
    # print("img_zero.shape is : ",img_zero.shape)
    num_contours = 0
    contoursList = []
    for i in range(len(contours)) :
        if hierarchy[0][i][3] >= 0 :
            temparea = math.fabs(cv2.contourArea(contours[i]))
            # print(math.fabs(cv2.contourArea(contours[i])))
            if temparea > maxarea :
                conid = i
                maxarea = temparea
            if temparea > threshline :
                num_contours += 1
                if num_contours % 7 == 0 :
                    cv2.drawContours(img_zero, contours, i, (0,0,255),1)
                if num_contours % 7 == 1 :
                    cv2.drawContours(img_zero, contours, i, (255,0,0),1)
                if num_contours % 7 == 2 :
                    cv2.drawContours(img_zero, contours, i, (0,255,0),1)
                if num_contours % 7 == 3 :
                    cv2.drawContours(img_zero, contours, i, (0,255,255),1)
                if num_contours % 7 == 4 :
                    cv2.drawContours(img_zero, contours, i, (255,0,255),1)
                if num_contours % 7 == 5 :
                    cv2.drawContours(img_zero, contours, i, (255,255,0),1)
                if num_contours % 7 == 6:
                    cv2.drawContours(img_zero, contours, i, (255, 255, 255), 1)
                # print(contours[i].shape)
                contoursList.append(contours[i])
    # print("maxarea: ",maxarea)
    # print("number of contours is ", num_contours)
    # cv2.drawContours(img_zero, contours, conid, (0, 0, 255), 1)
    cv2.imshow("with contours",img_zero)
    return contoursList

def DrawRectangle(img, img_temp, ConList) :
    """
    得到车牌边缘的的x,y坐标最小最大值,再原图上绘制bounding box,得到裁剪后的车牌图像
    :param img:      原图
    :param img_temp:    二值图像
    :param ConList:     图像的边缘轮廓
    :return:   null
    """
    length = len(ConList)
    rectanglePoint = np.zeros((length, 4, 1, 2), dtype = np.int32)
    img_zeros = np.zeros(img_temp.shape)
    img_copy = copy.deepcopy(img)
    img_copy_1 = copy.deepcopy(img)
    # print("img_zeros, length; ", img_zeros.shape, length)
    for i in range(length) :
        contours = ConList[i]
        minx, maxx, miny, maxy = 1e6, 0, 1e6, 0
        for index_num in range(contours.shape[0]) :
            if contours[index_num][0][0] < minx :
                minx = contours[index_num][0][0]
            if contours[index_num][0][0] > maxx :
                maxx = contours[index_num][0][0]
            if contours[index_num][0][1] < miny :
                miny = contours[index_num][0][1]
            if contours[index_num][0][1] > maxy :
                maxy = contours[index_num][0][1]
        # print(minx, maxx, miny, maxy)
        rectanglePoint[i][0][0][0], rectanglePoint[i][0][0][1] = minx, miny
        rectanglePoint[i][1][0][0], rectanglePoint[i][1][0][1] = minx, maxy
        rectanglePoint[i][2][0][0], rectanglePoint[i][2][0][1] = maxx, maxy
        rectanglePoint[i][3][0][0], rectanglePoint[i][3][0][1] = maxx, miny
        # rectanglePoint.dtype = np.int32
        # print(rectanglePoint[i].shape)
        crop_save(minx, maxx, miny, maxy, img_copy_1)
        # print("dx: ",maxx-minx,"dy: ",maxy-miny, "area: ", (maxx-minx)*(maxy-miny))
        cv2.polylines(img_copy, [rectanglePoint[i]], True, (0,0,255),2)
    cv2.imshow("img_zeros_haha", img_copy)

def crop_save(minx, maxx, miny, maxy, img_original) :
    """
    裁剪原图,根据minx,maxx,miny,maxy
    :param minx: x坐标最小值
    :param maxx: x坐标最大值
    :param miny: y坐标最小值
    :param maxy: y坐标最大值
    :param img_original: 由于需要将绘制结果再原图中显示,输入原图
    :return:
    """
    global crop_num
    epsx = 60
    epsy = 30
    dx = maxx - minx
    dy = maxy - miny
    if dx == dy :
        return
    if dx >= 600 - epsx :
        dx1, dx2, dx3, dx4 = minx, minx + 1 * int(dx / 3), minx + 2 * int(dx / 3), maxx
        save_pth = './crop40/cropimg_' + str(crop_num) + '.jpg'
        # cv2.imwrite(save_pth, img_original[dx1:dx2, miny:maxy,:])
        cv2.imwrite(save_pth, img_original[miny:maxy, dx1:dx2, :])
        crop_num += 1
        save_pth = './crop40/cropimg_' + str(crop_num) + '.jpg'
        cv2.imwrite(save_pth, img_original[miny:maxy, dx2:dx3, :])
        crop_num += 1
        save_pth = './crop40/cropimg_' + str(crop_num) + '.jpg'
        cv2.imwrite(save_pth, img_original[miny:maxy, dx3:dx4, :])
        crop_num += 1
    elif dx >= 400 - epsx :
        dx1, dx2, dx3 = minx, minx + 1 * int(dx / 2), maxx
        save_pth = './crop40/cropimg_' + str(crop_num) + '.jpg'
        cv2.imwrite(save_pth, img_original[miny:maxy, dx1:dx2, :])
        crop_num += 1
        save_pth = './crop40/cropimg_' + str(crop_num) + '.jpg'
        cv2.imwrite(save_pth, img_original[miny:maxy, dx2:dx3, :])
        crop_num += 1
    elif dy >= 240 - epsy :
        dy1, dy2, dy3, dy4 = miny, miny + 1 * int(dy / 3), miny + 2 * int(dy / 3), maxy
        save_pth = './crop40/cropimg_' + str(crop_num) + '.jpg'
        cv2.imwrite(save_pth, img_original[dy1: dy2, minx:maxx, :])
        crop_num += 1
        save_pth = './crop40/cropimg_' + str(crop_num) + '.jpg'
        cv2.imwrite(save_pth, img_original[dy2: dy3, minx:maxx, :])
        crop_num += 1
        save_pth = './crop40/cropimg_' + str(crop_num) + '.jpg'
        cv2.imwrite(save_pth, img_original[dy3: dy4, minx:maxx, :])
        crop_num += 1
    elif dy >= 160 - epsy :
        dy1, dy2, dy3 = miny, miny + 1 * int(dy / 2), maxy
        save_pth = './crop40/cropimg_' + str(crop_num) + '.jpg'
        cv2.imwrite(save_pth, img_original[dy1: dy2, minx:maxx, :])
        crop_num += 1
        save_pth = './crop40/cropimg_' + str(crop_num) + '.jpg'
        cv2.imwrite(save_pth, img_original[dy2: dy3, minx:maxx, :])
        crop_num += 1
    elif dx <= 200 + epsx :
        dx1, dx2 = minx, maxx
        save_pth = './crop40/cropimg_' + str(crop_num) + '.jpg'
        cv2.imwrite(save_pth, img_original[miny:maxy, dx1:dx2, :])
        crop_num += 1
    else :
        pass


if __name__ == '__main__' :
    pth = 'License_plates.jpg'
    img = cv2.imread(pth)
    img = cv2.resize(img, (292 * 4, 173 * 4))
    cv2.imshow("original",img)
    # 1)提取单通道图片,选项为 (灰度图片/HSV中的value分支)
    singlechannel_img = SingleChannel(img)
    # 2)提升对比度
    contrast_img = Contrast(singlechannel_img)
    # contrast_img = singlechannel_img
    # 3)边缘连接(膨胀)
    kernel = np.ones((2, 2), np.uint8)
    dilation_img = cv2.dilate(contrast_img, kernel, iterations=1)
    cv2.imshow("dilate", dilation_img)
    # dilation_img = contrast_img
    # 4) 二值化
    threshold_img = threshold(dilation_img)
    # 5)利用findcontours函数找到边缘
    contoursList = drawCoutrous(threshold_img)
    # 6) 裁剪图片,车牌图片存储
    DrawRectangle(img, threshold_img, contoursList)
    cv2.waitKey()
    cv2.destroyAllWindows()
相关推荐
岛屿旅人42 分钟前
智能时代网络空间认知安全新观察
网络·人工智能·安全·web安全·网络安全
玩AI的小胡子4 小时前
让文案生成更具灵活性/chatGPT新功能canvas画布编辑
人工智能·gpt·chatgpt·aigc
远洋录5 小时前
前端单元测试实战:从零开始构建可靠的测试体系
前端·人工智能·react
纪伊路上盛名在5 小时前
生成式AI、大模型、多模态技术开发与应用学习清单
服务器·人工智能·笔记·学习·知识图谱·学习方法
hongkid6 小时前
mac 安装CosyVoice (cpu版本)
人工智能·macos·cosyvoice
CodeCraft Studio6 小时前
什么是定性数据分析?有哪些定性数据分析技术及应用实践?
大数据·人工智能·数据分析
道友老李7 小时前
【深度学习进阶】CNN-VGG
人工智能·深度学习·神经网络·机器学习·cnn
z千鑫7 小时前
【Flask+OpenAI】利用Flask+OpenAI Key实现GPT4-智能AI对话接口demo - 从0到1手把手全教程(附源码)
人工智能·后端·python·chatgpt·flask·ai编程
AI完全体7 小时前
【AI日记】24.12.14 kaggle 比赛 2-4 EDA
人工智能·机器学习·kaggle 比赛