【图像处理】利用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+Iwhitetop_hatIblacktop_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} Iwhitetop_hat表示白顶帽处理后图像, I b l a c k − t o p _ h a t I_{black-top\_hat} Iblacktop_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()


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/936928.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

结构变量的占用多少个字节

1、在linux中&#xff0c;这种写法比较清晰 struct gpio_led_data { u8 can_sleep; //如果定义了结构变量&#xff0c;则该成员占用1个字节 u8 blinking; //如果定义了结构变量&#xff0c;则该成员占用1个字节 }; struct gpio_leds_priv { int num_leds; //如…

[COLM 2024] V-STaR: Training Verifiers for Self-Taught Reasoners

本文是对 STaR 的改进方法&#xff0c;COLM 是 Conference On Language Models&#xff0c;大模型领域新出的会议&#xff0c;在国际上很知名&#xff0c;不过目前还没有被列入 ccf list&#xff08;新会议一般不会列入&#xff09;&#xff1b;作者来自高校、微软研究院和 Goo…

uni-app Android平台上架要求的隐私政策提示配置方法【跨端开发系列】

文章目录 前言&#x1f4d6;一、前言二、DCloud 数据采集说明三、配置方式3.1 HBuilderX3.2.1及以上版本配置方式3.2 HBuilderX3.2.0及以下版本配置方法3.3 模板提示框3.4 无提示框 四、离线打包配置方式五、模板提示框六、二次确认提示框七、国际化八、隐私协议内容需要注意的…

Xcode

info.plist Appearance Light 关闭黑暗模式 Bundle display name 设置app名称&#xff0c;默认为工程名 Location When In Use Usage Description 定位权限一共有3个key 1.Privacy - Location When In Use Usage Description 2.Privacy - Location Always and When In U…

auto-gptq安装以及不适配软硬件环境可能出现的问题及解决方式

目录 1、auto-gptq是什么&#xff1f;2、auto-gptq安装3、auto-gptq不正确安装可能会出现的问题&#xff08;1&#xff09;爆出&#xff1a;CUDA extension not installed.&#xff08;2&#xff09;没有报错但是推理速度超级慢 1、auto-gptq是什么&#xff1f; Auto-GPTQ 是一…

惠普Laser Jet MFP M437nda复印机成像装置严重不足及更换传输卷故障解决方法

惠普Laser Jet MFP M437nda复印机成像装置严重不足及更换传输卷故障解决方法,记录维修那点事儿,普通维修工的日常维修点滴; 惠普Laser Jet MFP M437nda复印机成像装置严重不足维修方法 如果复印及打印的效果没有问题的情况下我们也可以不更换套鼓及显影剂,那么不更换套鼓及…

【2025最新版】Stable diffusion汉化版安装教程(附SD安装包),一键激活,永久免费!

如何安装并学习使用Stable Diffusion呢&#xff1f; 其实&#xff0c;安装SD的过程并不复杂&#xff0c;只需按照简单的步骤操作&#xff0c;几分钟内就能完成安装&#xff0c;不论是Windows系统还是Mac系统&#xff0c;都能轻松应对。

【Python网络爬虫笔记】11- Xpath精准定位元素

目录 一、Xpath 在 Python 网络爬虫中的作用&#xff08;一&#xff09;精准定位元素&#xff08;二&#xff09;应对动态网页&#xff08;三&#xff09;数据结构化提取 二、Xpath 的常用方法&#xff08;一&#xff09;节点选取&#xff08;二&#xff09;谓词筛选&#xff0…

RabbitMQ个人理解与基本使用

目录 一. 作用&#xff1a; 二. RabbitMQ的5中队列模式&#xff1a; 1. 简单模式 2. Work模式 3. 发布/订阅模式 4. 路由模式 5. 主题模式 三. 消息持久化&#xff1a; 消息过期时间 ACK应答 四. 同步接收和异步接收&#xff1a; 应用场景 五. 基本使用 &#xff…

网络工程师常用软件之配置对比软件

老王说网络&#xff1a;网络资源共享汇总 https://docs.qq.com/sheet/DWXZiSGxiaVhxYU1F ☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝ 我们经常在项目或者运维中对设备的config进行变更&am…

嵌入式驱动开发详解15(电容触摸屏gt9147)

文章目录 前言电容触摸屏特点MT触摸消息电容触摸屏协议电容屏触摸时序Type A 触摸点信息上报时序Type B 触摸点信息上报时序 多点触摸所使用到的API函数 驱动部分驱动框图设备树节点修改设备树引脚配置设备节点配置 具体驱动开发I2C驱动框架I2C框架内部实现 参考文献 前言 随着…

antdv-<a-button>中属性的使用

UI组件库&#xff08;User Interface Component Library&#xff09;是一种预先构建好的、可重用的用户界面元素集合&#xff0c;旨在帮助开发者更快速、更简便地构建用户界面。这些组件通常包括按钮、表单、导航栏、模态框等&#xff0c;能够提供一致的外观和交互风格&#xf…

win服务器的架设、windows server 2012 R2 系统的下载与安装使用

文章目录 windows server 2012 R2 系统的下载与安装使用1 windows server 2012 的下载2 打开 VMware 虚拟机软件&#xff08;1&#xff09;新建虚拟机&#xff08;2&#xff09;设置虚拟机&#xff08;3&#xff09;打开虚拟机 windows server 2012&#xff08;4&#xff09;进…

【ArcGIS微课1000例】0135:自动生成标识码(长度不变,前面自动加0)

文章目录 一、加载实验数据二、BSM计算方法一、加载实验数据 加载专栏《ArcGIS微课实验1000例(附数据)》配套数据中0135.rar中的建筑物数据,如下图所示: 打开属性表,BSM为数据库中要求的字段:以TD_T 1066-2021《不动产登记数据库标准》为例: 计算出来的BSM如下图: 二、B…

康谋方案 | 多源相机数据采集与算法集成测试方案

目录 一、相机组成 二、多源相机采集与测试方案 三、应用案例分享 四、结语 在智能化技术快速发展当下&#xff0c;图像数据的采集与处理逐渐成为自动驾驶、工业等领域的一项关键技术。高质量的图像数据采集与算法集成测试都是确保系统性能和可靠性的关键。随着技术的不断进…

陪玩系统小程序源码/游戏陪玩APP系统用户端有哪些功能?游戏陪玩小程序APP源码开发

多客陪玩系统-游戏陪玩线下预约上门服务等陪玩圈子陪玩社区系统源码 陪玩系统源码&#xff0c;高质量的陪玩系统源码&#xff0c;游戏陪玩APP源码开发&#xff0c;语音陪玩源码搭建: 线上陪玩活动组局与线下家政服务系统的部署需要综合考虑技术选型、开发流程、部署流程、功能实…

运维实战:K8s 上的 Doris 高可用集群最佳实践

今天我们将深入探讨&#xff1a;&#xff1a;如何在 K8s 集群上部署 Compute storage coupled&#xff08;存算耦合&#xff09; 模式的 Doris 高可用集群&#xff1f; 本文&#xff0c;我将为您提供一份全面的实战指南&#xff0c;逐步引导您完成以下关键任务&#xff1a; 配…

从零用java实现 小红书 springboot vue uniapp (2)主页优化

前言 移动端演示 http://8.146.211.120:8081/#/ 前面的文章我们基本完成了主页的布局 今天我们具体的去进行实现 并且分享我开发时遇到的问题 首先先看效果 java仿小红书主页 实现效果为 1.顶端全屏切换 2.上划加载更多 3.下拉当前页整体刷新 顶端全屏切换我们选择 gui-switch…

动手学深度学习-线性神经网络-7softmax回归的简洁实现

目录 初始化模型参数 重新审视Softmax的实现 优化算法 训练 小结 在 线性回归的实现中&#xff0c; 我们发现通过深度学习框架的高级API能够使实现 线性回归变得更加容易。 同样&#xff0c;通过深度学习框架的高级API也能更方便地实现softmax回归模型。 本节如在上一节…

人工智能原理实验四:智能算法与机器学习

一、实验目的 本实验课程是计算机、智能、物联网等专业学生的一门专业课程&#xff0c;通过实验&#xff0c;帮助学生更好地掌握人工智能相关概念、技术、原理、应用等&#xff1b;通过实验提高学生编写实验报告、总结实验结果的能力&#xff1b;使学生对智能程序、智能算法等…