车牌识别系统一:Python-opencv进行车牌定位(HSV颜色空间、Sobel边缘算子)

车牌定位

开题

作为一个刚接触Python以及图像处理的小白,正好这次毕设题目是车牌识别,虽然毕设最终还是只能简单的实现部分车牌的识别,但还是希望能够将其记录下来,既是给自己做做笔记,也是希望能够给其他人一些帮助(因此在文章中可能会出现一些问题,希望大家指正并谅解)。说说正题——车牌识别主要分为三个模块:车牌定位、字符分割和字符识别。本篇文章主要是第一部分车牌定位部分的代码,部分代码参考自下面这位博主(非常感谢博主的分享):
链接: 车牌定位.

流程图

Alt

颜色空间模型采用HSV模型,边缘算子采用Sobel算子。首先,对输入的车牌图像进行高斯滤波、归一化等预处理,然后进行HSV变换,再进行灰度化、二值化处理;随后再对输入图像进行灰度化及二值化处理,并进行Sobel边缘算子计算,然后对得到的颜色检测图像与边缘检测图像进行互相筛选,从而得到最后的消除大部分噪声的车辆图像,然后即可根据我国车牌的特征,车牌长宽为440mm*140mm,其长宽比约为3:1,筛选出车牌图块位置,最后输出轮廓比最接近3:1的图块,即为定位后的车牌。

代码

# coding=gbk
"""
车牌定位模块,尝试将Sobel边缘和基于颜色HSV的两种定位方法互相筛选,实现车牌定位
__author__ = 'kuang'
2019.04.20   7号宿舍楼
"""

import os
import time
import cv2 as cv
import numpy as np

#找出最有可能是车牌的位置
def getSatifyestBox(list_rate):
    for index, key in enumerate(list_rate):
        list_rate[index] = abs(key - 3)
    index = list_rate.index(min(list_rate))   #index函数作用是:若list_rate中存在index括号中的内容,则返回括号内字符串的索引值
    return index

def location(img):
    #读取图片并统一尺寸
    img_resize = cv.resize(img,(640,480),)
    #高斯模糊+中值滤波
    img_gaus = cv.GaussianBlur(img_resize,(5,5),0)          #高斯模糊
    img_med = cv.medianBlur(img_gaus,5)                     #中值滤波

    #HSV模型处理,直至二值化
    #转换为HSV模型
    img_hsv = cv.cvtColor(img_med,cv.COLOR_BGR2HSV)        #hsv模型
    lower_blue = np.array([100,40,50])
    higher_blue = np.array([140,255,255])
    mask = cv.inRange(img_hsv,lower_blue,higher_blue)    #掩膜操作
    img_res = cv.bitwise_and(img_med,img_med,mask=mask)

    #灰度化+二值化
    img_gray_h = cv.cvtColor(img_res,cv.COLOR_BGR2GRAY)    #转换了灰度化
    ret1,img_thre_h = cv.threshold(img_gray_h,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)

    #进行Sobel算子运算,直至二值化
    img_gray_s = cv.cvtColor(img_med,cv.COLOR_BGR2GRAY) 

    #sobel算子运算
    img_sobel_x = cv.Sobel(img_gray_s,cv.CV_32F,1,0,ksize=3)    #x轴Sobel运算
    img_sobel_y = cv.Sobel(img_gray_s,cv.CV_32F,0,1,ksize=3)
    img_ab_y = np.uint8(np.absolute(img_sobel_y))
    img_ab_x = np.uint8(np.absolute(img_sobel_x))               #像素点取绝对值
    img_ab = cv.addWeighted(img_ab_x, 0.5, img_ab_y, 0.5,0)   #将两幅图像叠加在一起(按一定权值)
    #考虑再加一次高斯去噪
    img_gaus_1 = cv.GaussianBlur(img_ab,(5,5),0)          #高斯模糊

    #二值化操作
    ret2,img_thre_s = cv.threshold(img_gaus_1,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)     #正二值化
	
	#颜色空间与边缘算子的图像互相筛选
    #同时遍历两幅二值图片,若两者均为255,则置255
    img_1 = np.zeros(img_thre_h.shape,np.uint8)  #重新拷贝图片
    height = img_resize.shape[0]    #行数
    width = img_resize.shape[1]     #列数
    for i in range(height):
        for j in range(width):
            h = img_thre_h[i][j]
            s = img_thre_s[i][j]
            if h ==255 and s ==255 :
                img_1[i][j] = 255
            else:
                img_1[i][j] = 0
    # cv.imshow('threshold',img_1)
    # cv.waitKey(0)

    #二值化后的图像进行闭操作
    kernel = np.ones((14,18),np.uint8)                           
    img_close = cv.morphologyEx(img_1,cv.MORPH_CLOSE,kernel)    #闭操作
    img_med_2 = cv.medianBlur(img_close,5)
    # cv.imshow('close',img_med_2)
    # cv.waitKey(0)

    #查找轮廓
    regions = []         #区域
    list_rate = []
    img_input = img_med_2.copy()
    contours,hierarchy = cv.findContours(img_input,cv.RETR_TREE,cv.CHAIN_APPROX_SIMPLE)
    #   筛选面积最小的
    for contour in contours:
        #计算该轮廓的面积
        area = cv.contourArea(contour)
        #面积小的都筛选掉
        if area < 2000:
            continue
        #轮廓近似,epsilon,是从轮廓到近似轮廓的最大距离。是一个准确率参数,好的epsilon的选择可以得到正确的输出。True决定曲线是否闭合。
        epslion = 1e-3 * cv.arcLength(contour,True)
        approx = cv.approxPolyDP(contour,epslion,True)            #曲线折线化
        #找到最小的矩形,该矩形可能有方向
        rect = cv.minAreaRect(contour)
        #box是四个点的坐标
        box = cv.boxPoints(rect)
        box = np.int0(box)
        #计算高和宽
        height = abs(box[0][1] - box[2][1])
        width = abs(box[0][0] - box[2][0])
        #车牌正常情况下长高比为2-5之间(精确一点可为(2.2,3.6))
        ratio = float(width) / float(height)
        if ratio > 2 and ratio < 5:
            regions.append(box)
            list_rate.append(ratio)
    #输出车牌的轮廓
    print('[INF0]:Detect %d license plates' % len(regions))    #输出疑似车牌图块的数量
    index = getSatifyestBox(list_rate)
    region = regions[index]
    #用绿线画出这些找到的轮廓
    #重新申请空间拷贝,因为drawcontours会改变原图片
    img_2 = np.zeros(img_resize.shape,np.uint8)
    img_2 = img_resize.copy()
    cv.drawContours(img_2, [region], 0, (0, 255, 0), 2)
    # cv.imshow('result',img_2)
    # cv.waitKey(0)

    #定位后需对车牌图像做后面的字符分割等处理,因此需要将车牌图块单独截取出来,截取轮廓
    Xs = [i[0] for i in region]
    YS = [i[1] for i in region]
    x1 = min(Xs)
    x2 = max(Xs)
    y1 = min(YS)
    y2 = max(YS)
    height_1 = y2 - y1
    width_1 = x2 - x1
    img_crop = img_resize[y1:y1+height_1,x1:x1+width_1]
    # cv.imshow('resultcut',img_crop)
    # cv.waitKey(0)
	
	
	
	#后面是自己的一些想法,希望能够对截取到的车牌图块再细致处理一下,使其仅保留车牌部分,但作用貌似也不大(苦笑)
    #假设再进行一次HSV
    img_hsv_1 = cv.cvtColor(img_crop,cv.COLOR_BGR2HSV)        #hsv模型
    lower_blue_1 = np.array([100,90,90])
    higher_blue_1 = np.array([140,255,255])
    mask_1 = cv.inRange(img_hsv_1,lower_blue_1,higher_blue_1)    #掩膜操作
    img_res_1 = cv.bitwise_and(img_crop,img_crop,mask=mask_1)
    
    #灰度化+二值化
    img_gray_1 = cv.cvtColor(img_res_1,cv.COLOR_BGR2GRAY)    #转换了灰度化
    ret3,img_thre_1 = cv.threshold(img_gray_1,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
    
    height_2 = img_thre_1.shape[0]      #获得行数
    width_2 = img_thre_1.shape[1]       #获得列数
    white_min = []
    white_max = []
    a = 0
    b = 0
    #从每行开始遍历,记录每行第一个及最后一个白色像素点的列数
    for i in range(height_2):
        for j in range(width_2):
            h = img_thre_1[i,j]
            if h == 255:
                a = j
                white_min.append(a)
                break
    a = min(white_min)
    for q in range(height_2-1,-1,-1):
        for w in range(width_2-1,-1,-1):
            ps = img_thre_1[q,w]
            if ps == 255:
                b = w
                white_max.append(b)
                break
    b = max(white_max)
    white_min1 = []
    white_max1 = []
    c = 0
    d = 0
    #从每一列开始遍历,记录每一行第一个白色像素点及最后一个像素点的行数
    for i in range(width_2):
        for j in range(height_2):
            h = img_thre_1[j,i]
            if h == 255:
                c = j
                white_max1.append(c)
                break
    c = min(white_max1)
    for q in range(width_2-1,-1,-1):
        for w in range(height_2-1,-1,-1):
            ps = img_thre_1[w,q]
            if ps == 255:
                d = w
                white_min1.append(d)
                break
    d = max(white_min1)
    img_cut = img_crop[c:d,a:b]
    # cv.imshow('final',img_cut)
    # cv.waitKey(0)
    return img_cut

if __name__ == "__main__":
    img = cv.imread("XXX.jpg")     #输入自己的图片
    cv.imshow("plate",img)
    cv.waitKey(0)
    img_p = location(img)
    cv.imshow("plate",img_p)
    cv.waitKey(0)

采用颜色边缘对车牌进行定位,在复杂环境下效果不是很好,只能是初学者用来练练手,如果诸位在现实应用中涉及到车牌识别,可以去参考这位博主分享的第三方库HyperLPR的使用,HyperLPR的使用仅几行代码,且效果更佳:
链接: HyperLPR的使用.

实现效果

原图像
定位后图像

存在的问题

在代码中并未实现车牌的垂直和水平倾斜校正(因为在定位后的车牌部分存在边框,因此可以使用霍夫变换进行校正,但是有些定位的车牌并不包含边框,此时使用霍夫变换就会出现误校正了),希望各位大佬能够给小弟指点指点迷津(感谢感谢!!!)

  • 4
    点赞
  • 78
    收藏
    觉得还不错? 一键收藏
  • 15
    评论
好的,我们可以使用Python的Tkinter模块来实现GUI界面,使用OpenCV库来处理图片。以下是一个简单的示例代码,可以根据需要进行修改和扩展。 ```python import cv2 import numpy as np import tkinter as tk from tkinter import filedialog class ImageProcessor: def __init__(self): self.img = None self.processed_img = None def open_file(self): filepath = filedialog.askopenfilename(filetypes=[("Image files", "*.jpg;*.jpeg;*.png")]) if filepath: self.img = cv2.imread(filepath) self.processed_img = self.img.copy() self.show_img(self.processed_img) def save_file(self): if self.processed_img is not None: filepath = filedialog.asksaveasfilename(defaultextension=".png", filetypes=[("PNG file", "*.png")]) if filepath: cv2.imwrite(filepath, self.processed_img) def show_img(self, img): cv2.imshow("Image", img) cv2.waitKey(0) cv2.destroyAllWindows() def gray(self): if self.img is not None: self.processed_img = cv2.cvtColor(self.img, cv2.COLOR_BGR2GRAY) self.show_img(self.processed_img) def brightness(self, value): if self.img is not None: hsv = cv2.cvtColor(self.img, cv2.COLOR_BGR2HSV) h, s, v = cv2.split(hsv) v = np.clip(v + value, 0, 255) hsv = cv2.merge([h, s, v]) self.processed_img = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR) self.show_img(self.processed_img) def equalize_hist(self): if self.img is not None: gray = cv2.cvtColor(self.img, cv2.COLOR_BGR2GRAY) self.processed_img = cv2.equalizeHist(gray) self.show_img(self.processed_img) def translate(self, x, y): if self.img is not None: rows, cols = self.img.shape[:2] M = np.float32([[1, 0, x], [0, 1, y]]) self.processed_img = cv2.warpAffine(self.img, M, (cols, rows)) self.show_img(self.processed_img) def rotate(self, angle): if self.img is not None: rows, cols = self.img.shape[:2] M = cv2.getRotationMatrix2D((cols/2, rows/2), angle, 1) self.processed_img = cv2.warpAffine(self.img, M, (cols, rows)) self.show_img(self.processed_img) def affine(self, pts1, pts2): if self.img is not None: rows, cols = self.img.shape[:2] M = cv2.getAffineTransform(pts1, pts2) self.processed_img = cv2.warpAffine(self.img, M, (cols, rows)) self.show_img(self.processed_img) def resize(self, width, height): if self.img is not None: self.processed_img = cv2.resize(self.img, (width, height), interpolation=cv2.INTER_LINEAR) self.show_img(self.processed_img) def perspective(self, pts1, pts2): if self.img is not None: rows, cols = self.img.shape[:2] M = cv2.getPerspectiveTransform(pts1, pts2) self.processed_img = cv2.warpPerspective(self.img, M, (cols, rows)) self.show_img(self.processed_img) def add_noise(self): if self.img is not None: rows, cols = self.img.shape[:2] noise = np.random.normal(0, 50, (rows, cols)) self.processed_img = cv2.add(self.img, noise.astype(np.uint8)) self.show_img(self.processed_img) def remove_noise(self): if self.img is not None: gray = cv2.cvtColor(self.img, cv2.COLOR_BGR2GRAY) self.processed_img = cv2.medianBlur(gray, 5) self.show_img(self.processed_img) def sobel(self): if self.img is not None: gray = cv2.cvtColor(self.img, cv2.COLOR_BGR2GRAY) sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=5) sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=5) self.processed_img = cv2.magnitude(sobelx, sobely) self.show_img(self.processed_img) class App: def __init__(self): self.root = tk.Tk() self.root.title("Image Processor") self.processor = ImageProcessor() self.create_menu() self.create_toolbar() self.create_canvas() self.create_buttons() self.root.mainloop() def create_menu(self): menubar = tk.Menu(self.root) filemenu = tk.Menu(menubar, tearoff=0) filemenu.add_command(label="Open", command=self.processor.open_file) filemenu.add_command(label="Save", command=self.processor.save_file) filemenu.add_separator() filemenu.add_command(label="Exit", command=self.root.quit) menubar.add_cascade(label="File", menu=filemenu) self.root.config(menu=menubar) def create_toolbar(self): toolbar = tk.Frame(self.root) toolbar.pack(side="top", fill="x") gray_button = tk.Button(toolbar, text="Gray", command=self.processor.gray) gray_button.pack(side="left") brightness_scale = tk.Scale(toolbar, from_=-255, to=255, orient="horizontal") brightness_scale.pack(side="left") brightness_button = tk.Button(toolbar, text="Brightness", command=lambda: self.processor.brightness(brightness_scale.get())) brightness_button.pack(side="left") equalize_hist_button = tk.Button(toolbar, text="Equalize Hist", command=self.processor.equalize_hist) equalize_hist_button.pack(side="left") toolbar.add_separator() translate_x_scale = tk.Scale(toolbar, from_=-100, to=100, orient="horizontal") translate_x_scale.pack(side="left") translate_y_scale = tk.Scale(toolbar, from_=-100, to=100, orient="horizontal") translate_y_scale.pack(side="left") translate_button = tk.Button(toolbar, text="Translate", command=lambda: self.processor.translate(translate_x_scale.get(), translate_y_scale.get())) translate_button.pack(side="left") rotate_scale = tk.Scale(toolbar, from_=-180, to=180, orient="horizontal") rotate_scale.pack(side="left") rotate_button = tk.Button(toolbar, text="Rotate", command=lambda: self.processor.rotate(rotate_scale.get())) rotate_button.pack(side="left") affine_button = tk.Button(toolbar, text="Affine", command=lambda: self.processor.affine(pts1, pts2)) affine_button.pack(side="left") toolbar.add_separator() resize_width_scale = tk.Scale(toolbar, from_=50, to=500, orient="horizontal") resize_width_scale.pack(side="left") resize_height_scale = tk.Scale(toolbar, from_=50, to=500, orient="horizontal") resize_height_scale.pack(side="left") resize_button = tk.Button(toolbar, text="Resize", command=lambda: self.processor.resize(resize_width_scale.get(), resize_height_scale.get())) resize_button.pack(side="left") perspective_button = tk.Button(toolbar, text="Perspective", command=lambda: self.processor.perspective(pts1, pts2)) perspective_button.pack(side="left") toolbar.add_separator() add_noise_button = tk.Button(toolbar, text="Add Noise", command=self.processor.add_noise) add_noise_button.pack(side="left") remove_noise_button = tk.Button(toolbar, text="Remove Noise", command=self.processor.remove_noise) remove_noise_button.pack(side="left") sobel_button = tk.Button(toolbar, text="Sobel", command=self.processor.sobel) sobel_button.pack(side="left") def create_canvas(self): self.canvas = tk.Canvas(self.root, width=800, height=600) self.canvas.pack(side="left", fill="both", expand=True) self.canvas.bind("<Button-1>", self.on_canvas_click) self.canvas.bind("<B1-Motion>", self.on_canvas_drag) self.canvas.bind("<ButtonRelease-1>", self.on_canvas_release) self.canvas.bind("<Configure>", self.on_canvas_resize) def create_buttons(self): self.reset_button = tk.Button(self.root, text="Reset", command=self.reset_canvas) self.reset_button.pack(side="bottom") def reset_canvas(self): self.canvas.delete("all") self.pts1 = [] self.pts2 = [] def on_canvas_click(self, event): x, y = self.canvas.canvasx(event.x), self.canvas.canvasy(event.y) self.canvas.create_oval(x-5, y-5, x+5, y+5, fill="red") if len(self.pts1) < 4: self.pts1.append((x, y)) elif len(self.pts2) < 4: self.pts2.append((x, y)) def on_canvas_drag(self, event): if len(self.pts1) < 4 and len(self.pts2) < 4: x, y = self.canvas.canvasx(event.x), self.canvas.canvasy(event.y) self.canvas.create_oval(x-5, y-5, x+5, y+5, fill="red") if len(self.pts1) < 4: self.pts1.append((x, y)) elif len(self.pts2) < 4: self.pts2.append((x, y)) def on_canvas_release(self, event): if len(self.pts1) == 4 and len(self.pts2) == 4: self.canvas.create_polygon(self.pts1, outline="green", fill="", width=2) self.canvas.create_polygon(self.pts2, outline="blue", fill="", width=2) def on_canvas_resize(self, event): self.canvas.config(scrollregion=self.canvas.bbox("all")) if __name__ == "__main__": app = App() ``` 在代码中,我们定义了一个ImageProcessor类来处理图像,包括打开文件、保存文件、灰度化、调亮度、直方图均衡化、图像平移、旋转、仿射、插值缩放、透视、加噪、去噪点、Sobel边缘检测等功能。然后我们定义了一个App类来实现GUI界面,包括菜单、工具栏、画布和按钮等组件,并且将ImageProcessor类的方法与这些组件进行绑定。在画布上,我们可以用鼠标左键来选择四个点,然后点击Affine或Perspective按钮来进行仿射或透视变换。 注意:在Windows系统中,使用OpenCV库显示图像时需要调用cv2.waitKey(0)函数来等待用户按下键盘,否则图像会无法显示。在其他系统中,可能需要调用cv2.imshow()函数来显示图像。
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值