python编写的opencv车牌识别,使用pyqt5写界面,附工作流程

最近在捣鼓车牌识别的小项目,发现用Python+OpenCV搞图像处理配上PyQt5做界面,效果居然还不错。今儿就带大家看看这玩意怎么从零开始搭建,顺便扒拉点实战代码出来。

先看整个流程:摄像头抓图→图像预处理→车牌定位→字符分割→文字识别→结果显示。咱们先从最刺激的车牌定位开始,这里用OpenCV的灰度化+边缘检测三板斧:

def locate_plate(img):
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    blur = cv2.GaussianBlur(gray, (5,5), 0)
    
    # Sobel边缘检测
    sobel = cv2.Sobel(blur, cv2.CV_16S, 1, 0)
    sobel = cv2.convertScaleAbs(sobel)
    
    # 自适应阈值处理
    ret, binary = cv2.threshold(sobel, 0, 255, cv2.THRESH_OTSU)
    
    # 形态学闭操作
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (17,5))
    closed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)
    
    return closed

这段代码里有个坑要注意:morphologyEx用的结构核尺寸(17,5)是经验值,实际用的时候得根据摄像头分辨率调整。有次我偷懒直接用了默认值,结果把马路牙子当车牌框了...

定位到候选区域后,得用轮廓分析筛出真正的车牌。这里用到了轮廓面积和长宽比过滤:

contours, _ = cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

for cnt in contours:
    area = cv2.contourArea(cnt)
    if area < 2000: continue
    
    rect = cv2.minAreaRect(cnt)
    w, h = rect[1]
    ratio = max(w,h)/min(w,h) if min(w,h)>0 else 0
    
    if 2.5 < ratio < 5.5:  # 典型车牌长宽比
        box = cv2.boxPoints(rect)
        box = np.int0(box)
        cv2.drawContours(img, [box], 0, (0,255,0), 2)

这里有个骚操作——用minAreaRect获取旋转矩形,比直接用boundingRect更能适应倾斜车牌。实测在停车场斜着拍的车牌,用这法子框选准确率能提升40%左右。

python编写的opencv车牌识别,使用pyqt5写界面,附工作流程

字符分割这块儿最容易翻车,特别是碰到新能源车牌中间的小圆点。我的方案是垂直投影法+连通域分析:

def split_chars(plate_img):
    # 二值化反转(白底黑字)
    _, binary = cv2.threshold(plate_img, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
    
    # 垂直投影
    v_projection = np.sum(binary, axis=0)
    
    # 找波峰波谷
    char_ranges = []
    in_char = False
    for i, val in enumerate(v_projection):
        if val > 0 and not in_char:
            start = i
            in_char = True
        elif val == 0 and in_char:
            end = i
            in_char = False
            if end - start > 5:  # 过滤噪点
                char_ranges.append((start, end))
    
    return [binary[:,s:e] for s,e in char_ranges]

注意这里把图像反相处理了,白底黑字更符合OCR的输入要求。有个坑是新能源车的第八位字符比较窄,容易和后面的圆点粘连,得在投影分析后加个宽度过滤。

最后是PyQt5的界面设计,这里用QCamera做实时视频流显示:

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.camera = QCamera()
        self.viewfinder = QCameraViewfinder()
        self.camera.setViewfinder(self.viewfinder)
        
        # 自定义画面处理
        self.camera.imageCaptured.connect(self.process_frame)
        
        # 结果显示区域
        self.result_label = QLabel('识别结果')
        layout = QVBoxLayout()
        layout.addWidget(self.viewfinder)
        layout.addWidget(self.result_label)
        
        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)
        
    def process_frame(self, image):
        # 转换OpenCV格式
        ptr = image.bits()
        ptr.setsize(image.byteCount())
        arr = np.array(ptr).reshape(image.height(), image.width(), 4)
        frame = cv2.cvtColor(arr, cv2.COLOR_RGBA2BGR)
        
        # 调用识别管道
        result = pipeline(frame)
        self.result_label.setText(result)

这里有个性能优化点:直接操作图像内存比用QImage转格式快3倍不止。但要注意线程安全问题,图像处理部分最好放到单独的工作线程,否则界面会卡成PPT。

整套系统跑起来后,在i5-8265U上能达到每秒5帧的处理速度。实测发现最大的瓶颈在字符识别部分,后来把Tesseract换成轻量化的CNN模型后,速度直接翻倍。不过这事儿得另开一篇细说了,毕竟训练模型又是另一个深坑...

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐