OpenCV实现车牌定位和字符分割

2023-05-16

前言:本案例的车牌图像来源于互联网,如有侵权请尽快联系我,立删。

文章目录

  • 一、概述
  • 二、车牌图像分析
  • 三、车牌定位
    • 1. 基本处理
    • 2. 图像降噪
    • 3. 灰度拉伸
    • 4. 图像差分
    • 5. 二值化
    • 6. 边缘检测
    • 7. 形态学处理
    • 8. 定位车牌
  • 四、字符分割
    • 1. 去除上下边缘
    • 2. 分割并保存字符
  • 五、测试其它图片
  • 六、总结
  • 七、附上完整代码

一、概述

在智能交通系统中,汽车牌照识别发挥了巨大的作用。其实现是将图像处理技术与计算机软件技术相连接在一起,以准确识别出车牌牌照的字符为目的,将识别出的数据传送至交通实时管理系统,以最终实现交通监管的功能。在车牌自动识别系统中,从汽车图像的获取到车牌字符处理是一个复杂的过程,主要分为四个阶段:图像获取、车牌定位、字符分割以及字符识别。本文主要通过OpenCV的各种图像处理方法实现车牌定位以及字符分割。(字符识别可参考我的另一篇博客车牌识别)

二、车牌图像分析

我国汽车牌照一般由七个字符和一个点组成(参考下图),车牌字符的高度和宽度是固定的,分别为90mm和45mm,七个字符之间的距离也是固定的12mm,中间分割符圆点的直径是10mm,但是真实车牌图像会因为透视原因造成字符间的距离变化。在民用车牌中,字符排列位置遵循以下规律:第一个字符通常是我国各省区的简称,共31个,用汉字表示;第二个字符通常是发证机关的代码号,最后五个字符由英文字母和数字组合而成,字母是24个大写字母(除去 I 和 O)的组合,数字用"0-9"之间的数字表示。

标准车牌
从图像处理角度看,汽车牌照具有以下几个特征:

  1. 车牌的几何特征,即车牌形状统一为高宽比固定的矩形;
  2. 车牌的灰度分布呈现出连续的波谷-波峰-波谷分布,这是因为我国车牌颜色单一,字符直线排列;
  3. 车牌直方图呈现出双峰状的特点,即车牌直方图中可以看到双个波峰;
  4. 车牌具有强边缘信息,这是因为车牌的字符相对集中在车牌的中心,而车牌边缘无字符,因此车牌的边缘信息感较强;
  5. 车牌的字符颜色和车牌背景颜色对比鲜明。目前,我国国内的车牌大致可分为蓝底白字和黄底黑字,特殊用车采用白底黑字或黑底白字,有时辅以红色字体等。为简化处理,本文只考虑蓝底白字的车牌。

三、车牌定位

1. 基本处理

调整尺寸和转灰度图
为了确保输入的车牌图像不能太大或太小,需要对图像进行尺寸调整,一般的照片高宽比是3:4,此次我们限制图像最大宽度为400像素,函数如下:

def resize_img(img):
    """ resize图像 """
    h, w = img.shape[:-1]
    scale = 400 / max(h, w)
    img_resized = cv.resize(img, None, fx=scale, fy=scale, 
                            interpolation=cv.INTER_CUBIC)
    # print(img_resized.shape)
    return img_resized

因图像后续处理输入要求,在此需将图像转为灰度图

img_gray = cv.cvtColor(img_resized, cv.COLOR_BGR2GRAY)
cv.imshow('Gray', img_gray)

效果展示:
在这里插入图片描述

2. 图像降噪

噪声是由一种或者多种原因造成的灰度值的随机变化,通常需要平滑技术(也常称为滤波或者降噪技术)进行抑制或者去除。图像降噪即通过滤波器增强图像中某个波段或频率并阻塞(或降低)其他频率波段。常见的图像滤波方式有均值滤波、高斯滤波、中值滤波、双边滤波等。一般采用高斯滤波来对图像进行降噪。(本案例车牌图像质量较好,此次没有进行此操作)

  img_gaussian = cv.GaussianBlur(img_gray, (3, 3), 0) 
  cv.imshow("Gaussian_Blur2",  img_gaussian)

在这里插入图片描述

3. 灰度拉伸

图像拉伸主要用来改善图像显示的对比度,道路提取流程中往往首先要对图像进行拉伸的预处理。图像拉伸主要有三种方式:灰度拉伸、直方图均衡化和直方图规定化,此次使用灰度拉伸,将灰度值拉伸到整个0-255的区间,那么其对比度显然是大幅增强的。可以用如下的公式来将某个像素的灰度值映射到更大的灰度空间:
在这里插入图片描述
其中Imin,Imax是原始图像的最小灰度值和最大灰度值,MIN和MAX是要拉伸到的灰度空间的灰度最小值和最大值。

def stretching(img):
    """ 灰度拉伸 """
    maxi = float(img.max())
    mini = float(img.min())

    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)
    img_stretched = img
    return img_stretched

效果展示:
在这里插入图片描述

4. 图像差分

图像差分,就是把两幅图像的对应像素值相减,以削弱图像的相似部分,突出显示图像的变化部分。在进行差分前,需要对图像进行开运算,即先腐蚀后膨胀。

# 进行开运算,去除噪声
r = 14
h = w = r * 2 + 1
kernel = np.zeros((h, w), np.uint8)
cv.circle(kernel, (r, r), r, 1, -1)
# 开运算
img_opening = cv.morphologyEx(img, cv.MORPH_OPEN, kernel)

效果展示:
在这里插入图片描述
再对开运算前后图像进行差分,使用cv.absdiff函数
效果展示:
在这里插入图片描述

5. 二值化

图像二值化处理就是将图像上点的灰度置为0或255,即整个图像呈现出明显的黑白效果。将256个亮度等级的灰度图像通过适当的阀值选取而获得仍然可以反映图像整体和局部特征的二值化图像。

def binarization(img):
    """ 二值化处理函数 """
    maxi = float(img.max())
    mini = float(img.min())
    x = maxi - ((maxi - mini) / 2)
    # 二值化, 返回阈值ret和二值化操作后的图像img_binary
    ret, img_binary = cv.threshold(img, x, 255, cv.THRESH_BINARY)
    # img_binary = cv.adaptiveThreshold(img, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 5, 2)
    return img_binary

二值化后可以明显看到车牌区域,效果展示:
在这里插入图片描述

6. 边缘检测

边缘检测的目的是找到图像中亮度变化剧烈的像素点构成的集合,表现出来往往是轮廓。边缘检测有很多检测器,其中最常用的是canny边检测器,不容易受到噪声的影响。

def canny(img):
    """ canny边缘检测 """
    img_canny = cv.Canny(img, img.shape[0], img.shape[1])
    return img_canny

效果展示:
在这里插入图片描述

7. 形态学处理

开运算和闭运算是形态学常用的图像处理方式,开运算可以消除亮度较高的细小区域,在纤细点处分离物体,对于较大物体,可以在不明显改变其面积的情况下平滑其边界。闭运算具有填充白色物体内细小黑色空洞的区域、连接临近物体、平滑边界等作用。

def opening_closing(img):
    """ 开闭运算,保留车牌区域,消除其他区域,从而定位车牌 """
    # 进行闭运算
    kernel = np.ones((5, 23), np.uint8)
    img_closing = cv.morphologyEx(img, cv.MORPH_CLOSE, kernel)
    cv.imshow("Closing", img_closing)

    # 进行开运算
    img_opening1 = cv.morphologyEx(img_closing, cv.MORPH_OPEN, kernel)
    cv.imshow("Opening_1", img_opening1)

    # 再次进行开运算
    kernel = np.ones((11, 6), np.uint8)
    img_opening2 = cv.morphologyEx(img_opening1, cv.MORPH_OPEN, kernel)
    return img_opening2

三次运算效果展示:
在这里插入图片描述在这里插入图片描述在这里插入图片描述

8. 定位车牌

先对上一步的图像 ‘img_opening2’ 检测轮廓,使用的是cv.findContours,该函数会返回图像的轮廓信息,然后对轮廓信息进行大小,高宽比,颜色筛选出最符合车牌的矩形轮廓,从而定位车牌区域。

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(original, img):
    """ 定位车牌号 """
    _, contours, hierarchy = cv.findContours(img, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
    img_cont = original.copy()
    img_cont = cv.drawContours(img_cont, contours, -1, (255, 0, 0), 6)
    cv.imshow("Contours", img_cont)
    # 计算轮廓面积及高宽比
    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])[-5:]

    # 使用颜色识别判断找出最像车牌的区域
    maxweight, maxindex=0, -1
    for i in range(len(block)):
        # print('block', block[i])
        if 2 <= block[i][2] <=4 and 1000 <= block[i][1] <= 20000:    # 对矩形区域高宽比及面积进行限制 
            b = original[block[i][0][1]: block[i][0][3], block[i][0][0]: block[i][0][2]]
            # BGR转HSV
            hsv = cv.cvtColor(b, cv.COLOR_BGR2HSV)
            lower = np.array([100, 50, 50])
            upper = np.array([140, 255, 255])
            # 根据阈值构建掩膜
            mask = cv.inRange(hsv, lower, upper)
            # 统计权值
            w1 = 0
            for m in mask:
                w1 += m / 255
                print(w1)

            w2 = 0
            for n in w1:
                w2 += n

            # 选出最大权值的区域
            if w2 > maxweight:
                maxindex = i
                maxweight = w2

    rect = block[maxindex][0]
    return rect

在原图中框出车牌,效果展示:
在这里插入图片描述在这里插入图片描述

四、字符分割

1. 去除上下边缘

将车牌区域从图像中裁剪出来,如下图:

在这里插入图片描述
车牌的上下边界通常都是不规范的,其中拉铆螺母的位置也会干扰字符分割,我们需要去除边缘没用的部分。

def find_waves(threshold, histogram):
    """ 根据设定的阈值和图片直方图,找出波峰,用于分隔字符 """
    up_point = -1    # 上升点
    is_peak = False
    if histogram[0] > threshold:
        up_point = 0
        is_peak = True
    wave_peaks = []
    for i, x in enumerate(histogram):
        if is_peak and x < threshold:
            if i - up_point > 2:
                is_peak = False
                wave_peaks.append((up_point, i))
        elif not is_peak and x >= threshold:
            is_peak = True
            up_point = i
    if is_peak and up_point != -1 and i - up_point > 4:
        wave_peaks.append((up_point, i))
    return wave_peaks
 
 
def remove_upanddown_border(img):
    """ 去除车牌上下无用的边缘部分,确定上下边界 """
    plate_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
    ret, plate_binary_img = cv.threshold(plate_gray, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)
    row_histogram = np.sum(plate_binary_img, axis=1)    # 数组的每一行求和
    row_min = np.min(row_histogram)
    row_average = np.sum(row_histogram) / plate_binary_img.shape[0]
    row_threshold = (row_min + row_average) / 2
    wave_peaks = find_waves(row_threshold, row_histogram)
    # 挑选跨度最大的波峰
    wave_span = 0.0
    for wave_peak in wave_peaks:
        span = wave_peak[1] - wave_peak[0]
        if span > wave_span:
            wave_span = span
            selected_wave = wave_peak
    plate_binary_img = plate_binary_img[selected_wave[0]:selected_wave[1], :]
    #cv.imshow("plate_binary_img", plate_binary_img)
    return  plate_binary_img

效果展示:
在这里插入图片描述

2. 分割并保存字符

从左往右开始检测匹配字符,若宽度(end - start)大于5则认为是字符,将其裁剪并保存下来。

def find_end(start, arg, black, white, width, black_max, white_max):
    end = start + 1
    for m in range(start + 1, width - 1):
        if (black[m] if arg else white[m]) > (0.95*black_max if arg else 0.95*white_max):
            end = m
            break
    return end


def char_segmentation(thresh):
    """ 分割字符 """
    white, black = [], []    # list记录每一列的黑/白色像素总和
    height, width = thresh.shape
    white_max = 0    # 仅保存每列,取列中白色最多的像素总数
    black_max = 0    # 仅保存每列,取列中黑色最多的像素总数
    # 计算每一列的黑白像素总和
    for i in range(width):
        line_white = 0    # 这一列白色总数
        line_black = 0    # 这一列黑色总数
        for j in range(height):
            if thresh[j][i] == 255:
                line_white += 1
            if thresh[j][i] == 0:
                line_black += 1
        white_max = max(white_max, line_white)
        black_max = max(black_max, line_black)
        white.append(line_white)
        black.append(line_black)
        # print('white_max', white_max)
        # print('black_max', black_max)
    # arg为true表示黑底白字,False为白底黑字
    arg = True
    if black_max < white_max:
        arg = False

    # 分割车牌字符
    n = 1
    while n < width - 2:
        n += 1
        # 判断是白底黑字还是黑底白字  0.05参数对应上面的0.95 可作调整
        if (white[n] if arg else black[n]) > (0.05 * white_max if arg else 0.05 * black_max):  # 这点没有理解透彻
            start = n
            end = find_end(start, arg, black, white, width, black_max, white_max)
            n = end
            if end - start > 5 or end > (width * 3 / 7):
                cropImg = thresh[0:height, start-1:end+1]
                # 对分割出的数字、字母进行resize并保存
                cropImg = cv.resize(cropImg, (34, 56))
                cv.imwrite(save_path + '\\{}.bmp'.format(n), cropImg)
                cv.imshow('Char_{}'.format(n), cropImg)

最终分割的字符保存至文件夹中,效果展示:
在这里插入图片描述

五、测试其它图片

测试另外两张汽车正视图,字符分割效果较好,展示如下:

在这里插入图片描述在这里插入图片描述
在这里插入图片描述在这里插入图片描述

六、总结

案例思路总结起来是先对图像做预处理,包含调整图像尺寸、转换灰度图、图像降噪、灰度拉伸、图像差分、图像二值化、canny边缘检测、形态学开闭运算,从而定位车牌区域,然后裁剪车牌,去除上下无用边缘部分,最后进行字符分割并将其保存至特定文件夹。

本案例对汽车正视图的车牌定位以及字符分割的效果较为成功,如果图像中车牌有一定的倾斜度以及透视变形,则其中还需对车牌进行倾斜矫正以及透视变换,图像处理也将更为复杂。后续的字符识别只要拥有足够的数据集训练,其过程也与手写数字识别案例一样简单。

七、附上完整代码

import cv2 as cv
import numpy as np

img_path = 'data\\img\\test_005.jpg'
save_path = 'Chars\\test'


def resize_img(img, max_size):
    """ resize图像 """
    h, w = img.shape[0:2]
    scale = max_size / max(h, w)
    img_resized = cv.resize(img, None, fx=scale, fy=scale, 
                            interpolation=cv.INTER_CUBIC)
    # print(img_resized.shape)
    return img_resized


def stretching(img):
    """ 图像拉伸 """
    maxi = float(img.max())
    mini = float(img.min())

    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)
    img_stretched = img
    return img_stretched


def absdiff(img):
    """ 对开运算前后图像做差分 """
    # 进行开运算,用来去除噪声
    r = 15
    h = w = r * 2 + 1
    kernel = np.zeros((h, w), np.uint8)
    cv.circle(kernel, (r, r), r, 1, -1)
    # 开运算
    img_opening = cv.morphologyEx(img, cv.MORPH_OPEN, kernel)
    # 获取差分图
    img_absdiff = cv.absdiff(img, img_opening)
    cv.imshow("Opening", img_opening)
    return img_absdiff  


def binarization(img):
    """ 二值化处理函数 """
    maxi = float(img.max())
    mini = float(img.min())
    x = maxi - ((maxi - mini) / 2)
    # 二值化, 返回阈值ret和二值化操作后的图像img_binary
    ret, img_binary = cv.threshold(img, x, 255, cv.THRESH_BINARY)
    # img_binary = cv.adaptiveThreshold(img, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 5, 2)
    # 返回二值化后的黑白图像
    return img_binary


def canny(img):
    """ canny边缘检测 """
    img_canny = cv.Canny(img, img.shape[0], img.shape[1])
    return img_canny


def opening_closing(img):
    """ 开闭运算,保留车牌区域,消除其他区域,从而定位车牌 """
    # 进行闭运算
    kernel = np.ones((5, 23), np.uint8)
    img_closing = cv.morphologyEx(img, cv.MORPH_CLOSE, kernel)
    cv.imshow("Closing", img_closing)

    # 进行开运算
    img_opening1 = cv.morphologyEx(img_closing, cv.MORPH_OPEN, kernel)
    cv.imshow("Opening_1", img_opening1)

    # 再次进行开运算
    kernel = np.ones((11, 6), np.uint8)
    img_opening2 = cv.morphologyEx(img_opening1, cv.MORPH_OPEN, kernel)
    return img_opening2


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(original, img):
    """ 定位车牌号 """
    _, contours, hierarchy = cv.findContours(img, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
    img_cont = original.copy()
    img_cont = cv.drawContours(img_cont, contours, -1, (255, 0, 0), 6)
    cv.imshow("Contours", img_cont)
    # 计算轮廓面积及高宽比
    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 bl: bl[1])[-5:]

    # 使用颜色识别判断找出最像车牌的区域
    maxweight, maxindex=0, -1
    for i in range(len(block)):
        # print('block', block[i])
        if 2 <= block[i][2] <=4 and 1000 <= block[i][1] <= 20000:    # 对矩形区域高宽比及面积进行限制 
            b = original[block[i][0][1]: block[i][0][3], block[i][0][0]: block[i][0][2]]
            # BGR转HSV
            hsv = cv.cvtColor(b, cv.COLOR_BGR2HSV)
            lower = np.array([100, 50, 50])
            upper = np.array([140, 255, 255])
            # 根据阈值构建掩膜
            mask = cv.inRange(hsv, lower, upper)
            # 统计权值
            w1 = 0
            for m in mask:
                w1 += m / 255

            w2 = 0
            for n in w1:
                w2 += n

            # 选出最大权值的区域
            if w2 > maxweight:
                maxindex = i
                maxweight = w2

    rect = block[maxindex][0]
    return rect


def preprocessing(img):
    # resize图像至300 * 400
    img_resized = resize_img(img, 400)
    cv.imshow('Original', img_resized)
    # 转灰度图
    img_gray = cv.cvtColor(img_resized, cv.COLOR_BGR2GRAY)
    cv.imshow('Gray', img_gray)
    # 高斯滤波
    # img_gaussian = cv.GaussianBlur(img_gray, (3,3), 0)
    # cv.imshow("Gaussian_Blur", img_gaussian)
    # 灰度拉伸,提升图像对比度
    img_stretched = stretching(img_gray)
    cv.imshow('Stretching', img_stretched)
    # 差分开运算前后图像
    img_absdiff = absdiff(img_stretched)
    cv.imshow("Absdiff", img_absdiff)
    # 图像二值化
    img_binary = binarization(img_absdiff)
    cv.imshow('Binarization', img_binary)
    # 边缘检测
    img_canny = canny(img_binary)
    cv.imshow("Canny", img_canny)
    # 开闭运算,保留车牌区域,消除其他区域
    img_opening2 = opening_closing(img_canny)
    cv.imshow("Opening_2", img_opening2)
    # 定位车牌号所在矩形区域
    rect = locate_license(img_resized, img_opening2)
    print("rect:", rect)  
    # 框出并显示车牌
    img_copy = img_resized.copy()
    cv.rectangle(img_copy, (rect[0], rect[1]), (rect[2], rect[3]), (0, 255, 0), 2)
    cv.imshow('License', img_copy)
    return rect, img_resized


def cut_license(original, rect):
    """ 裁剪车牌 """
    license_img = original[rect[1]:rect[3], rect[0]:rect[2]]
    return license_img


def find_waves(threshold, histogram):
    """ 根据设定的阈值和图片直方图,找出波峰,用于分隔字符 """
    up_point = -1    # 上升点
    is_peak = False
    if histogram[0] > threshold:
        up_point = 0
        is_peak = True
    wave_peaks = []
    for i, x in enumerate(histogram):
        if is_peak and x < threshold:
            if i - up_point > 2:
                is_peak = False
                wave_peaks.append((up_point, i))
        elif not is_peak and x >= threshold:
            is_peak = True
            up_point = i
    if is_peak and up_point != -1 and i - up_point > 4:
        wave_peaks.append((up_point, i))
    return wave_peaks
 
 
def remove_upanddown_border(img):
    """ 去除车牌上下无用的边缘部分,确定上下边界 """
    plate_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
    ret, plate_binary_img = cv.threshold(plate_gray, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)
    row_histogram = np.sum(plate_binary_img, axis=1)    # 数组的每一行求和
    row_min = np.min(row_histogram)
    row_average = np.sum(row_histogram) / plate_binary_img.shape[0]
    row_threshold = (row_min + row_average) / 2
    wave_peaks = find_waves(row_threshold, row_histogram)
    # 挑选跨度最大的波峰
    wave_span = 0.0
    selected_wave = []
    for wave_peak in wave_peaks:
        span = wave_peak[1] - wave_peak[0]
        if span > wave_span:
            wave_span = span
            selected_wave = wave_peak
    plate_binary_img = plate_binary_img[selected_wave[0]:selected_wave[1], :]
    return  plate_binary_img


def find_end(start, arg, black, white, width, black_max, white_max):
    end = start + 1
    for m in range(start + 1, width - 1):
        if (black[m] if arg else white[m]) > (0.95*black_max if arg else 0.95*white_max):
            end = m
            break
    return end


def char_segmentation(thresh):
    """ 分割字符 """
    white, black = [], []    # list记录每一列的黑/白色像素总和
    height, width = thresh.shape
    white_max = 0    # 仅保存每列,取列中白色最多的像素总数
    black_max = 0    # 仅保存每列,取列中黑色最多的像素总数
    # 计算每一列的黑白像素总和
    for i in range(width):
        line_white = 0    # 这一列白色总数
        line_black = 0    # 这一列黑色总数
        for j in range(height):
            if thresh[j][i] == 255:
                line_white += 1
            if thresh[j][i] == 0:
                line_black += 1
        white_max = max(white_max, line_white)
        black_max = max(black_max, line_black)
        white.append(line_white)
        black.append(line_black)
        # print('white_max', white_max)
        # print('black_max', black_max)
    # arg为true表示黑底白字,False为白底黑字
    arg = True
    if black_max < white_max:
        arg = False

    # 分割车牌字符
    n = 1
    while n < width - 2:
        n += 1
        # 判断是白底黑字还是黑底白字  0.05参数对应上面的0.95 可作调整
        if (white[n] if arg else black[n]) > (0.05 * white_max if arg else 0.05 * black_max):  # 这点没有理解透彻
            start = n
            end = find_end(start, arg, black, white, width, black_max, white_max)
            n = end
            if end - start > 5 or end > (width * 3 / 7):
                cropImg = thresh[0:height, start-1:end+1]
                # 对分割出的数字、字母进行resize并保存
                cropImg = cv.resize(cropImg, (34, 56))
                cv.imwrite(save_path + '\\{}.bmp'.format(n), cropImg)
                cv.imshow('Char_{}'.format(n), cropImg)


def main():
    # 读取图像
    image = cv.imread(img_path)
    # 图像预处理,返回img_resized和定位的车牌矩形区域
    rect, img_resized = preprocessing(image)
    # 裁剪出车牌
    license_img = cut_license(img_resized, rect)
    cv.imshow('License', license_img)
    # 去除车牌上下无用边缘
    plate_b_img = remove_upanddown_border(license_img)
    cv.imshow('plate_binary', plate_b_img)
    # 字符分割,保存至文件夹
    char_segmentation(plate_b_img)
    cv.waitKey(0)
    cv.destroyAllWindows()


if __name__ == '__main__':
    main()
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

OpenCV实现车牌定位和字符分割 的相关文章

  • CICD中clang-tidy静态语义检查

    教程 xff1b https hokein github io clang tools tutorial 要用clang tidy首先要在电脑上安装clang tools Linux Ubuntu系统 span class token fu
  • Vscode 设置clang-format

    用户设置与工作空间设置 VS Code提供了两种设置方式 xff1a 用户设置 xff1a 这种方式进行的设置 xff0c 会应用于该用户打开的所有工程 xff1b 工作空间设置 xff1a 工作空间是指使用VS Code打开的某个文件夹
  • 同步异步电机ADRC控制系统仿真

    之前一直使用PI控制器做异步电机矢量控制 xff0c 最近想把ADRC控制也放到异步电机矢量控制上去 xff0c 所以对其进了仿真 xff0c 可遇到了一个一直没有解决掉的问题 xff0c 现记录下来 xff0c 请各位先辈进行指教以及为遇
  • 大疆A型板使用经验分享(八)——FreeRTOS操作系统的使用

    一 freeRTOS操作系统 操作系统 operating system 本质上是一个帮助用户进行功能管理的软件 操作系统运行在硬件之上 为其他工作的软件执行资源分配等管理工作 一般称呼不使用操作系统的单片机开发方式为 裸机开发 当进行裸机
  • MySQL锁篇

    文章目录 说明 xff1a 锁篇一 MySQL有那些锁 xff1f 二 MySQL 是怎么加锁的 xff1f 三 update 没加索引会锁全表 xff1f 四 MySQL 记录锁 43 间隙锁可以防止删除操作而导致的幻读吗 xff1f 五
  • C++学习笔记

    文章目录 一 基础入门1 常量2 关键字3 数据类型3 1 整型3 2 浮点型3 3 字符型3 4 字符串类型3 5 布尔类型 4 数据的输入与输出5 运算符6 数组6 1 一维数组6 2 二维数组 7 函数8 指针9 结构体 二 核心编程
  • JS实现继承的几种方式

    JS继承的实现方式 堪称最全最详细 前沿看js继承这块时我看的几个教程都是说的很简单或者是没有说全就自行百度看了好多总结了下有 xff1a 1 构造函数继承 2 原型链继承 3 组合继承 4 class继承 5 实例继承 6 拷贝继承 7
  • 51单片机入门之点亮发光二极管

    1 任务书 用51单片机控制一个发光二极管 xff0c 打开单片机后LED亮 2 分析 首先是考虑电路连接问题 单片机所有1 O 口都可以驱动发光二极管 设选用P2 0 来接发光二极管 xff0c 所谓 闪亮 xff0c 就是点亮发光管后
  • 51单片机入门之开关控制

    1 任务书 使用拨扭开关控制led xff0c 用微动开关控制led 2 分析 拨钮开关无非就是 打开 跟 关闭 两个状态 xff0c 也就是0跟1 xff0c 打开无非就是接通关闭无非就是断开 那我们应该怎么接入单片机了 xff0c 其实
  • 51单片机静态动态数码管显示

    51单片机静态动态数码管显示 通过此实训了解动态数码管的显示原理 xff0c 掌握编码方法 共阴极和其阴极数码管的不同之处及常用设计方法 实训设备 这里使用的377锁存器模块控制的数码管下面就是电路图 显示内容 在显示模块的八位 LED 数
  • 51单片机矩阵键盘控制数码管

    51单片机矩阵键盘控制数码管 我们先了解矩阵键盘的工作原理 xff0c 掌握编码方法并能够编写出扫描程序 xff0c 使用矩阵键盘控制数码管输出矩阵键值 显示内容 在显示模块的八位 LED 数码的个位显示当前使用矩阵键盘所按下的值 键阵键盘
  • 51单片机继电器控制直流电机正反转

    51单片机继电器控制直流电机正反转 用继电器控制 24V 直流电机的转动与停止 继电器是常用的电气隔离器件 简单的驱动电路是用三极管直接驱动 该电路驱动简单 成本低廉 当控制电路为高电平时 xff0c NPN 型三极管就会饱和导通 xff0
  • 51单片机定时器流水灯控制

    51单片机定时器控制led流水灯数码管进行计数 51单片机根据不同的型号有不同数量的定时器的 xff0c 而这些定时器的大概用法是差不多的我们今天就使用定时器来控制led流水灯 实训要求 使用单片机定时器对led灯进行控制 xff0c 数码
  • esp8266单片机使用MAX7219芯片驱动点阵屏幕

    esp8266单片机使用MAX7219芯片驱动点阵屏幕 我们这里使用的单片机是一块esp8266 xff0c 点阵屏幕的话就是买的普通16脚红色的 xff0c 驱动芯片就是MAX7219芯片 xff0c 只需要依次把芯片的clk xff0c
  • 使用自己开发的app远程控制MAX7219点阵屏幕

    使用自己开发的app远程控制MAX7219点阵屏幕 一 功能介绍 xff1a 二 芯片介绍 xff1a 三 实现原理 xff1a 四 代码部分 xff1a 其它资料 xff1a 一 功能介绍 xff1a 1 可以固定显示想显示的内容 2 点
  • go发送http请求

    说明 xff1a 写项目时候用到的 xff0c go发送http请求用到的一个方法 span class token keyword func span 函数名 span class token punctuation span body
  • js事件流

    事件流指的是事件完整执行过程中的流动路径 事件流分为捕获阶段和冒泡阶段 捕获阶段是从父到子 xff1b 冒泡阶段是从子到父 事件冒泡 xff1a 事件冒泡概念 xff1a 当一个元素的事件被触发的时候 xff0c 同样的事件将会在该元素的祖
  • 树莓派安装python3.7.3

    一 安装依赖包 sudo apt get install y make build essential libssl dev zlib1g dev sudo apt get install y libbz2 dev libreadline
  • vscode中调试webpack构建的项目

    在webpack的配置中 xff1a devtool span class token punctuation span span class token string 39 source map 39 span span class to
  • mac下proxychains4的配置文件位置

    mac下proxychains4的配置文件位置 xff1a usr local etc proxychains conf span class token function vim span usr local etc proxychain

随机推荐

  • 1. 驱动开发--基础知识

    文章目录 1 驱动的概念2 linux体系架构3 模块化设计3 1 微内核和宏内核 4 linux设备驱动分类4 1 驱动分类4 2 三类驱动程序详细对比分析4 3 为什么字符设备驱动最重要 5 驱动程序的安全性要求5 1 驱动是内核的一部
  • 【论文笔记】Ensemble Augmented-Shot Y-shaped Learning

    论文笔记 EASY Ensemble Augmented Shot Y shaped Learning State Of The Art Few Shot Classification with Simple Ingredients Int
  • Ubuntu下的文件保存及退出

    这篇文章是写给我自己的 xff0c 怕自己以后忘了 我很多时候会在ubuntu下发现键盘并不那么好使 输入 vim test cpp 然后输入i o a xff0c 输入以上三种 xff0c 进入编辑状态 输入完成 xff0c 按esc退出
  • 机会总是留给有准备的人

    qqq
  • 1.karto-slam涉及的类-雷达以及雷达数据相关

    首先是最简单的 1 sensor msgs LaserScan 主要包括header 还有激光参数 xff08 扫射范围距离 xff0c 步长 xff0c 时间等 xff0c 不包含位姿信息 xff0c header里面含有frame id
  • catkin build 和 catkin_make

    首先安装 xff1a sudo apt get install python catkin tools 编译过程中你可能会遇到以下错误 xff0c 那是因为以前使用了catkin make进行编译 xff0c 需要把build和devel删
  • 使用Haar特征进行人脸识别

    这篇博客对2001年那篇划时代的paper xff1a Rapid Objection Using a Boosted Cascade of Simple Features进行一个简要的解析 这篇文章之后人脸识别的效果有了很大的提升 后来还
  • MySQL基础课程三件套,年前轻松带你带你入门数据库管理系统~

    今天已经2022年1月11日了 xff0c 相信大部分的宝子们已经进入快乐的寒假了 xff0c 今天给对数据库感兴趣的童鞋们推荐B站上的一系列数据库管理入门课 该系列课程分为三个部分 xff0c 第一部分为MySQL新手入门教程详解 xff
  • 【kazam】linux下截屏、录屏软件kazam的简单使用

    安装 xff1a sudo apt get install kazam 或者使用 ppa 安装 sudo add apt repository ppa kazam stable series sudo apt get update sudo
  • LCD24064显示程序,此工程直接运行。

    T6963C C51 Source Code240X64MCU W78E516D 12MHZLCM Controller T6963C RA6963 24064A B 1 FG GND 2 GND GND
  • 四旋翼无人机飞行器基本知识(四旋翼无人机结构和原理+四轴飞行diy全套入门教程)

    第一篇 四旋翼飞行器结构和原理 第二篇 四旋翼飞行diy全套入门教程 四旋翼飞行器结构和原理 1 结构形式 旋翼对称分布在机体的前后 左右四个方向 xff0c 四个旋翼处于同一高度平面 xff0c 且四个旋翼的结构和半径都相同 xff0c
  • 四旋翼飞控原理

    以前 xff0c 搞无人机的十个人有八个是航空 气动 机械出身 xff0c 更多考虑的是如何让飞机稳定飞起来 飞得更快 飞得更高 如今 xff0c 随着芯片 人工智能 大数据技术的发展 xff0c 无人机开始了智能化 终端化 集群化的趋势
  • 四旋翼飞控原理

    以前 xff0c 搞无人机的十个人有八个是航空 气动 机械出身 xff0c 更多考虑的是如何让飞机稳定飞起来 飞得更快 飞得更高 如今 xff0c 随着芯片 人工智能 大数据技术的发展 xff0c 无人机开始了智能化 终端化 集群化的趋势
  • 四旋翼飞行器控制原理与设计

    一 相关理论知识 1 坐标系与欧拉角 进行动力学建模之前首先建立坐标系 xff0c 在此建立地球坐标系和机体坐标系 xff0c 如图所示 xff0c 这里地球系z轴方向向下指向地心 xff0c 机体系x轴为机头方向 当描述一个三维空间内的刚
  • kalman 滤波

    卡尔曼 Kalman 滤波算法原理 C语言实现及实际应用 文章目录 卡尔曼滤波 一 滤波效果展示 二 简介 三 组成 预测状态方程 xff08 1 xff09 目的 xff1a xff08 2 xff09 方程 xff1a xff08 3
  • 软件项目管理 7.4.3.进度计划编排-时间压缩法

    公众号 64 项目管理研究所 将会第一时间更新文章并分享 行业分析报告 归档于软件项目管理初级学习路线 第七章 软件项目进度计划 该文章图片解析有问题 xff0c 点击此处查看 xff01 这里 xff01 前言 大家好 xff0c 这节我
  • maven解析依赖报错:Cannot resolve com.baomidou:mybatis-plus-generator:3.4.2

    不能解析依赖 xff1a span class token tag span class token tag span class token punctuation lt span dependency span span class t
  • 客户要求压缩进度,项目经理怎么办?

    几乎每个项目经理都会遇到这样的客户 xff1a 客户 xff1a 王经理 xff0c 我们现在这个项目 xff0c 上头领导说了 xff0c 原定在11月中旬上线的日期 xff0c 需要提前到十一国庆节前上线 xff0c 业务部门需要这个系
  • 树莓派关机重启命令

    关机方法任选一行即可 1 2 3 4 sudo shutdown h now sudo halt sudo poweroff sudo init 0 重启方
  • OpenCV实现车牌定位和字符分割

    前言 xff1a 本案例的车牌图像来源于互联网 xff0c 如有侵权请尽快联系我 xff0c 立删 文章目录 一 概述二 车牌图像分析三 车牌定位1 基本处理2 图像降噪3 灰度拉伸4 图像差分5 二值化6 边缘检测7 形态学处理8 定位车