20190706
|
@ -0,0 +1,52 @@
|
|||
# 车牌检测和识别的Python应用软件实现
|
||||
|
||||
徐静
|
||||
|
||||
## 1.车牌检测和识别项目介绍
|
||||
|
||||
![](./pic/p2.jpg)
|
||||
*图片来源:https://www.cnblogs.com/polly333/p/7367479.html*
|
||||
|
||||
车牌的检测和识别的应用非常广泛,比如交通违章车牌追踪,小区或地下车库门禁。在对车牌识别和检测的过程中,因为车牌往往是规整的矩形,长宽比相对固定,色调纹理相对固定,常用的方法有:基于形状、基于色调、基于纹理、基于文字特征等方法,近年来随着深度学习的发展也会使用目标检测的一些深度学习方法。该项目主要的流程如下图所示:
|
||||
|
||||
![](./pic/p1.png)
|
||||
|
||||
1.输入原始图片,通过二值化,边缘检测,和基于色调的颜色微调等办法检测出原图中的车牌号的位置;
|
||||
2.把检测到的车牌(ROI)裁剪,为车牌号的识别做准备;
|
||||
3.基于裁剪的车牌号,使用直方图的波峰波谷分割裁剪的车牌号(如上图中的第3步)
|
||||
4.训练机器学习模型做车牌识别,这里训练了2个SVM,一个SVM用来识别省份简称(如 鲁),另一个SVM用来识别字母和数字。
|
||||
5.通过PyQt5把整个算法封装成GUI程序,并打包发布安装软件。
|
||||
|
||||
|
||||
## 2.项目代码解析
|
||||
下图描述了整个项目的代码结构,可以访问https://github.com/DataXujing/vehicle-license-plate-recognition 查看,其结构如下:
|
||||
![](./pic/file_struct.png)
|
||||
|
||||
|
||||
|
||||
## 3.项目演示
|
||||
|
||||
可以通过访问项目地址 ( https://github.com/DataXujing/vehicle-license-plate-recognition )查看整个应用,或者访问安装程序下载地址 (https://pan.baidu.com/s/1IazbGFLlQkb8BQmK_EAeRA 提取码:v103 )安装安装程序进行测试,这里展示一些识别结果和测试视频:
|
||||
|
||||
![](./pic/test1.png)
|
||||
|
||||
![](./pic/test2.png)
|
||||
|
||||
![](./pic/test3.png)
|
||||
|
||||
|
||||
<video src="./pic/demo.mp4" controls="controls" ></video>
|
||||
|
||||
|
||||
## 4.TODO
|
||||
|
||||
目前识别的效果针对于某些场景下仍然很不理想,技术层面上的主要原因有两个,一个是车牌检测算法并没有检测到车牌(这主要是检测算法的问题),可以尝试一些目标检测的算法,比如Faster R-CNN(速度可能慢一些),YOLO系列, SSD系列等的经典的目标检测算法,然后做矫正或进一步的区域筛选;另一个原因是是在识别算法上,本次我们仅是基于少量的训练数据训练了SVM,可以尝试增加训练集并把模型替换成一些更复杂的机器学习模型如XGBoost,LightGBM,CatBoost等模型或使用CNN训练一个多分类的深度学习模型, 亦或者是直接考虑一些基于Attention的CNN-RNN架构的OCR识别模型。
|
||||
|
||||
|
||||
## Reference
|
||||
|
||||
1.[OpenCV图像识别:车牌定位算法源码,Python语言实现](https://blog.csdn.net/sumkee911/article/details/79435983)
|
||||
2.[车牌号识别 python + opencv](https://blog.csdn.net/wzh191920/article/details/79589506)
|
||||
3 [License-Plate-Recognition](https://github.com/wzh191920/License-Plate-Recognition)
|
||||
4.[车牌识别(一)-车牌定位](https://www.cnblogs.com/polly333/p/7367479.html)
|
||||
5.[在PyQt5中美化和装扮图形界面](https://zmister.com/archives/477.html)
|
|
@ -0,0 +1,279 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file
|
||||
# 'C:\Users\Administrator\Desktop\car_id_detect_reg\card_soft\my_main_ui.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.11.3
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
# __author__ = xujing
|
||||
# __date__ = 2019-07-05
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
from PyQt5.QtWidgets import QToolTip
|
||||
from PyQt5.QtGui import QFont,QIcon
|
||||
import sys
|
||||
import qtawesome
|
||||
|
||||
class Ui_MainWindow(object):
|
||||
def setupUi(self, MainWindow):
|
||||
MainWindow.setObjectName("MainWindow")
|
||||
MainWindow.resize(906, 600)
|
||||
icon = QtGui.QIcon()
|
||||
icon.addPixmap(QtGui.QPixmap(":/pic/pic/logo.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
MainWindow.setWindowIcon(icon)
|
||||
|
||||
# 将按钮边框去掉,文字设置为白色,背景灰色
|
||||
qss1 = '''
|
||||
QPushButton{border:none;color:cyan;}
|
||||
QPushButton#pushButton_3{
|
||||
border:none;
|
||||
border-bottom:1px solid cyan;
|
||||
font-size:22px;
|
||||
font-weight:700;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
QPushButton#pushButton_4{
|
||||
border:none;
|
||||
border-bottom:1px solid cyan;
|
||||
font-size:22px;
|
||||
font-weight:700;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
QPushButton#pushButton_5{
|
||||
border:none;
|
||||
border-bottom:1px solid cyan;
|
||||
font-size:22px;
|
||||
font-weight:700;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
QPushButton#pushButton_8{
|
||||
border:none;
|
||||
border-bottom:1px solid cyan;
|
||||
font-size:15px;
|
||||
font-weight:900;
|
||||
}
|
||||
QPushButton#pushButton_3:hover{border-left:4px solid red;font-weight:700;}
|
||||
QPushButton#pushButton_4:hover{border-left:4px solid red;font-weight:700;}
|
||||
QPushButton#pushButton_5:hover{border-left:4px solid red;font-weight:700;}
|
||||
QPushButton#pushButton_8:hover{border-left:4px solid red;font-weight:700;}
|
||||
backfround:black;
|
||||
|
||||
|
||||
'''
|
||||
|
||||
# 圆角窗口
|
||||
qss2 = '''#MainWindow{
|
||||
background:black;
|
||||
border-radius:30px
|
||||
}
|
||||
|
||||
'''
|
||||
# 下拉列表样式
|
||||
qss3 = '''QComboBox{
|
||||
border: 1px solid cyan;
|
||||
border-radius:2px;
|
||||
color: white;
|
||||
select-background-color:cyan;
|
||||
background-color:black;
|
||||
selection-color:black;
|
||||
}
|
||||
'''
|
||||
|
||||
# table qss
|
||||
qss4 = '''
|
||||
#tableWidget{
|
||||
border:none;
|
||||
border-color:cyan;
|
||||
background:black;
|
||||
color: cyan;
|
||||
font-size: 14px;
|
||||
font-weight:800;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
'''
|
||||
|
||||
|
||||
# graph qss
|
||||
qss5 = '''
|
||||
#graphicsView{
|
||||
border:none;
|
||||
border-color:cyan;
|
||||
background:black;
|
||||
}
|
||||
'''
|
||||
# button 6,7
|
||||
qss6 = '''
|
||||
QPushButton
|
||||
{
|
||||
font-size:16px;
|
||||
font-weight:900;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
/* 前景色 */
|
||||
color:rgb(248, 248, 255);
|
||||
|
||||
/* 背景色 */
|
||||
background-color:rgb(0,229,238);
|
||||
|
||||
/* 边框风格 */
|
||||
border-style:outset;
|
||||
|
||||
/* 边框宽度 */
|
||||
border-width:2px;
|
||||
|
||||
/* 边框颜色 */
|
||||
border-color:rgb(255,0,0);
|
||||
|
||||
/* 边框倒角 */
|
||||
border-radius:10px;
|
||||
|
||||
/* 控件最小宽度 */
|
||||
min-width:50px;
|
||||
|
||||
/* 控件最小高度 */
|
||||
min-height:20px;
|
||||
|
||||
/* 内边距 */
|
||||
padding:4px;
|
||||
}
|
||||
|
||||
/* 鼠标按下时的效果 */
|
||||
QPushButton#pushButton:pressed
|
||||
{
|
||||
/* 改变背景色 */
|
||||
background-color:rgb(0,255,127);
|
||||
|
||||
/* 改变边框风格 */
|
||||
border-style:inset;
|
||||
|
||||
/* 使文字有一点移动 */
|
||||
padding-left:3px;
|
||||
padding-top:3px;
|
||||
}
|
||||
|
||||
/* 按钮样式 */
|
||||
QPushButton:flat
|
||||
{
|
||||
border:2px solid red;
|
||||
}
|
||||
|
||||
/*鼠标悬浮时的效果*/
|
||||
QPushButton:hover
|
||||
{
|
||||
color:rgb(255, 222, 173);
|
||||
background-color:rgb(0,255,127); /*改变背景色*/
|
||||
border-style:inset;/*改变边框风格*/
|
||||
padding-left:3px;
|
||||
padding-top:3px;
|
||||
}
|
||||
|
||||
'''
|
||||
MainWindow.setStyleSheet(qss2)
|
||||
|
||||
MainWindow.setWindowFlag(QtCore.Qt.FramelessWindowHint) # 隐藏边框
|
||||
MainWindow.setWindowOpacity(0.95) # 设置窗口透明度
|
||||
|
||||
self.centralWidget = QtWidgets.QWidget(MainWindow)
|
||||
self.centralWidget.setObjectName("centralWidget")
|
||||
self.centralWidget.setStyleSheet(qss3)
|
||||
self.label = QtWidgets.QLabel(self.centralWidget)
|
||||
self.label.setGeometry(QtCore.QRect(30, 50, 611, 501))
|
||||
self.label.setText("")
|
||||
self.label.setObjectName("label")
|
||||
self.pushButton = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.pushButton.setGeometry(QtCore.QRect(720, 20, 75, 23))
|
||||
self.pushButton.setObjectName("pushButton")
|
||||
QToolTip.setFont(QFont("SansSerif",6))
|
||||
self.pushButton.setToolTip("<b>最小化</b>")
|
||||
self.pushButton.setStyleSheet('''QPushButton{background:#F76677;border-radius:5px;}QPushButton:hover{background:red;}''')
|
||||
self.pushButton_2 = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.pushButton_2.setGeometry(QtCore.QRect(810, 20, 75, 23))
|
||||
self.pushButton_2.setObjectName("pushButton_2")
|
||||
self.pushButton_2.setToolTip("<b>关闭</b>")
|
||||
self.pushButton_2.setStyleSheet('''QPushButton{background:#6DDF6D;border-radius:5px;}QPushButton:hover{background:green;}''')
|
||||
self.pushButton_3 = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.pushButton_3.setGeometry(QtCore.QRect(690, 90, 191, 23))
|
||||
icon1 = QtGui.QIcon()
|
||||
icon1.addPixmap(QtGui.QPixmap(":/pic/pic/cut.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.pushButton_3.setIcon(icon1)
|
||||
self.pushButton_3.setObjectName("pushButton_3")
|
||||
self.pushButton_3.setStyleSheet(qss1)
|
||||
self.label_2 = QtWidgets.QLabel(self.centralWidget)
|
||||
self.label_2.setGeometry(QtCore.QRect(700, 140, 181, 61))
|
||||
self.label_2.setText("")
|
||||
self.label_2.setObjectName("label_2")
|
||||
self.pushButton_4 = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.pushButton_4.setGeometry(QtCore.QRect(690, 230, 191, 23))
|
||||
icon2 = QtGui.QIcon()
|
||||
icon2.addPixmap(QtGui.QPixmap(":/pic/pic/shibie.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.pushButton_4.setIcon(icon2)
|
||||
self.pushButton_4.setObjectName("pushButton_4")
|
||||
self.pushButton_4.setStyleSheet(qss1)
|
||||
self.label_3 = QtWidgets.QLabel(self.centralWidget)
|
||||
self.label_3.setGeometry(QtCore.QRect(700, 290, 171, 41))
|
||||
self.label_3.setText("")
|
||||
self.label_3.setObjectName("label_3")
|
||||
self.label_3.setStyleSheet("color: rgb(255, 255, 255);font-size: 24px;font-weight:1000;")
|
||||
self.pushButton_5 = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.pushButton_5.setGeometry(QtCore.QRect(700, 360, 181, 23))
|
||||
icon3 = QtGui.QIcon()
|
||||
icon3.addPixmap(QtGui.QPixmap(":/pic/pic/color.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.pushButton_5.setIcon(icon3)
|
||||
self.pushButton_5.setObjectName("pushButton_5")
|
||||
self.pushButton_5.setStyleSheet(qss1)
|
||||
self.label_4 = QtWidgets.QLabel(self.centralWidget)
|
||||
self.label_4.setGeometry(QtCore.QRect(700, 400, 181, 21))
|
||||
self.label_4.setStyleSheet("color: rgb(255, 255, 255);")
|
||||
self.label_4.setText("")
|
||||
self.label_4.setObjectName("label_4")
|
||||
self.pushButton_6 = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.pushButton_6.setGeometry(QtCore.QRect(710, 492, 75, 41))
|
||||
icon4 = QtGui.QIcon()
|
||||
icon4.addPixmap(QtGui.QPixmap(":/pic/pic/image.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.pushButton_6.setIcon(icon4)
|
||||
self.pushButton_6.setObjectName("pushButton_6")
|
||||
self.pushButton_6.setStyleSheet(qss6)
|
||||
self.pushButton_7 = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.pushButton_7.setGeometry(QtCore.QRect(810, 490, 71, 41))
|
||||
icon5 = QtGui.QIcon()
|
||||
icon5.addPixmap(QtGui.QPixmap(":/pic/pic/video.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.pushButton_7.setIcon(icon5)
|
||||
self.pushButton_7.setObjectName("pushButton_7")
|
||||
self.pushButton_7.setStyleSheet(qss6)
|
||||
self.pushButton_8 = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.pushButton_8.setGeometry(QtCore.QRect(30, 10, 231, 31))
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("Meiryo UI")
|
||||
self.pushButton_8.setFont(font)
|
||||
self.pushButton_8.setIcon(icon)
|
||||
self.pushButton_8.setObjectName("pushButton_8")
|
||||
self.pushButton_8.setStyleSheet(qss1)
|
||||
MainWindow.setCentralWidget(self.centralWidget)
|
||||
|
||||
self.retranslateUi(MainWindow)
|
||||
QtCore.QMetaObject.connectSlotsByName(MainWindow)
|
||||
|
||||
def retranslateUi(self, MainWindow):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
MainWindow.setWindowTitle(_translate("MainWindow", "交通门禁车牌检测和识别系统"))
|
||||
self.pushButton.setText(_translate("MainWindow", "-"))
|
||||
self.pushButton_2.setText(_translate("MainWindow", "✘"))
|
||||
self.pushButton_3.setText(_translate("MainWindow", "车牌检测"))
|
||||
self.pushButton_4.setText(_translate("MainWindow", "车牌识别"))
|
||||
self.pushButton_5.setText(_translate("MainWindow", "车牌颜色"))
|
||||
self.pushButton_6.setText(_translate("MainWindow", "图像"))
|
||||
self.pushButton_7.setText(_translate("MainWindow", "视频"))
|
||||
self.pushButton_8.setText(_translate("MainWindow", "交通门禁车牌检测和识别系统"))
|
||||
|
||||
import my_pic_rc
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
MainWindow = QtWidgets.QMainWindow()
|
||||
ui = Ui_MainWindow()
|
||||
ui.setupUi(MainWindow)
|
||||
MainWindow.show()
|
||||
sys.exit(app.exec_())
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE UserProject SYSTEM "UserProject-4.0.dtd">
|
||||
<!-- eric6 user project file for project car -->
|
||||
<!-- Saved: 2019-07-05, 16:11:31 -->
|
||||
<!-- Copyright (C) 2019 DataXujing, 274762204@qq.com -->
|
||||
<UserProject version="4.0"/>
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE Tasks SYSTEM "Tasks-6.0.dtd">
|
||||
<!-- eric6 tasks file for project car -->
|
||||
<!-- Saved: 2019-07-05, 16:11:31 -->
|
||||
<Tasks version="6.0">
|
||||
<ProjectScanFilter></ProjectScanFilter>
|
||||
</Tasks>
|
|
@ -0,0 +1,50 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE Project SYSTEM "Project-5.1.dtd">
|
||||
<!-- eric project file for project car -->
|
||||
<!-- Saved: 2019-07-05, 16:11:30 -->
|
||||
<!-- Copyright (C) 2019 DataXujing, 274762204@qq.com -->
|
||||
<Project version="5.1">
|
||||
<Language>en_US</Language>
|
||||
<Hash>7b44b6b179c6221a079f95a039d33daff1089ecc</Hash>
|
||||
<ProgLanguage mixed="0">Python3</ProgLanguage>
|
||||
<ProjectType>PyQt5</ProjectType>
|
||||
<Description>car id detection.</Description>
|
||||
<Version>0.1</Version>
|
||||
<Author>DataXujing</Author>
|
||||
<Email>274762204@qq.com</Email>
|
||||
<Eol index="0"/>
|
||||
<Sources>
|
||||
<Source>Ui_my_main_ui.py</Source>
|
||||
<Source>my_main_ui.py</Source>
|
||||
<Source>my_pic_rc.py</Source>
|
||||
</Sources>
|
||||
<Forms>
|
||||
<Form>my_main_ui.ui</Form>
|
||||
</Forms>
|
||||
<Translations/>
|
||||
<Resources>
|
||||
<Resource>my_pic.qrc</Resource>
|
||||
</Resources>
|
||||
<Interfaces/>
|
||||
<Others/>
|
||||
<Vcs>
|
||||
<VcsType>None</VcsType>
|
||||
</Vcs>
|
||||
<FiletypeAssociations>
|
||||
<FiletypeAssociation pattern="*.e4p" type="OTHERS"/>
|
||||
<FiletypeAssociation pattern="*.idl" type="INTERFACES"/>
|
||||
<FiletypeAssociation pattern="*.md" type="OTHERS"/>
|
||||
<FiletypeAssociation pattern="*.py" type="SOURCES"/>
|
||||
<FiletypeAssociation pattern="*.py3" type="SOURCES"/>
|
||||
<FiletypeAssociation pattern="*.pyw" type="SOURCES"/>
|
||||
<FiletypeAssociation pattern="*.pyw3" type="SOURCES"/>
|
||||
<FiletypeAssociation pattern="*.qm" type="TRANSLATIONS"/>
|
||||
<FiletypeAssociation pattern="*.qrc" type="RESOURCES"/>
|
||||
<FiletypeAssociation pattern="*.rst" type="OTHERS"/>
|
||||
<FiletypeAssociation pattern="*.ts" type="TRANSLATIONS"/>
|
||||
<FiletypeAssociation pattern="*.txt" type="OTHERS"/>
|
||||
<FiletypeAssociation pattern="*.ui" type="FORMS"/>
|
||||
<FiletypeAssociation pattern="README" type="OTHERS"/>
|
||||
<FiletypeAssociation pattern="README.*" type="OTHERS"/>
|
||||
</FiletypeAssociations>
|
||||
</Project>
|
|
@ -0,0 +1,294 @@
|
|||
'''
|
||||
基于形状和色调的检测车牌号并提取车牌号图片
|
||||
'''
|
||||
import cv2
|
||||
import numpy as np
|
||||
from numpy.linalg import norm
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
SZ = 20 # 训练图片长宽
|
||||
MAX_WIDTH = 1000 # 原始图片最大宽度
|
||||
Min_Area = 2000 # 车牌区域允许最大面积
|
||||
PROVINCE_START = 1000
|
||||
|
||||
|
||||
def point_limit(point):
|
||||
if point[0] < 0:
|
||||
point[0] = 0
|
||||
if point[1] < 0:
|
||||
point[1] = 0
|
||||
|
||||
def accurate_place(card_img_hsv, limit1, limit2, color,cfg):
|
||||
row_num, col_num = card_img_hsv.shape[:2]
|
||||
xl = col_num
|
||||
xr = 0
|
||||
yh = 0
|
||||
yl = row_num
|
||||
#col_num_limit = cfg["col_num_limit"]
|
||||
row_num_limit = cfg["row_num_limit"]
|
||||
col_num_limit = col_num * 0.8 if color != "green" else col_num * 0.5 # 绿色有渐变
|
||||
for i in range(row_num):
|
||||
count = 0
|
||||
for j in range(col_num):
|
||||
H = card_img_hsv.item(i, j, 0)
|
||||
S = card_img_hsv.item(i, j, 1)
|
||||
V = card_img_hsv.item(i, j, 2)
|
||||
if limit1 < H <= limit2 and 34 < S and 46 < V:
|
||||
count += 1
|
||||
if count > col_num_limit:
|
||||
if yl > i:
|
||||
yl = i
|
||||
if yh < i:
|
||||
yh = i
|
||||
for j in range(col_num):
|
||||
count = 0
|
||||
for i in range(row_num):
|
||||
H = card_img_hsv.item(i, j, 0)
|
||||
S = card_img_hsv.item(i, j, 1)
|
||||
V = card_img_hsv.item(i, j, 2)
|
||||
if limit1 < H <= limit2 and 34 < S and 46 < V:
|
||||
count += 1
|
||||
if count > row_num - row_num_limit:
|
||||
if xl > j:
|
||||
xl = j
|
||||
if xr < j:
|
||||
xr = j
|
||||
return xl, xr, yh, yl
|
||||
|
||||
|
||||
|
||||
def CaridDetect(car_pic):
|
||||
|
||||
# 加载图片
|
||||
img = cv2.imread(car_pic)
|
||||
pic_hight, pic_width = img.shape[:2]
|
||||
|
||||
if pic_width > MAX_WIDTH:
|
||||
resize_rate = MAX_WIDTH / pic_width
|
||||
img = cv2.resize(img, (MAX_WIDTH, int(pic_hight*resize_rate)), interpolation=cv2.INTER_AREA)
|
||||
# 车牌识别的部分参数保存在js中,便于根据图片分辨率做调整
|
||||
f = open('config.js')
|
||||
j = json.load(f)
|
||||
for c in j["config"]:
|
||||
if c["open"]:
|
||||
cfg = c.copy()
|
||||
break
|
||||
else:
|
||||
raise RuntimeError('[ ERROR ] 没有设置有效配置参数.')
|
||||
|
||||
blur = cfg["blur"]
|
||||
# 高斯去噪
|
||||
if blur > 0:
|
||||
img = cv2.GaussianBlur(img, (blur, blur), 0) #图片分辨率调整
|
||||
oldimg = img
|
||||
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
||||
#equ = cv2.equalizeHist(img)
|
||||
#img = np.hstack((img, equ))
|
||||
# 去掉图像中不会是车牌的区域
|
||||
kernel = np.ones((20, 20), np.uint8)
|
||||
# morphologyEx 形态学变化函数
|
||||
img_opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
|
||||
img_opening = cv2.addWeighted(img, 1, img_opening, -1, 0);
|
||||
|
||||
# 找到图像边缘 Canny边缘检测
|
||||
ret, img_thresh = cv2.threshold(img_opening, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
|
||||
img_edge = cv2.Canny(img_thresh, 100, 200)
|
||||
# 使用开运算和闭运算让图像边缘成为一个整体
|
||||
kernel = np.ones((cfg["morphologyr"], cfg["morphologyc"]), np.uint8)
|
||||
img_edge1 = cv2.morphologyEx(img_edge, cv2.MORPH_CLOSE, kernel)
|
||||
img_edge2 = cv2.morphologyEx(img_edge1, cv2.MORPH_OPEN, kernel)
|
||||
|
||||
# 查找图像边缘整体形成的矩形区域,可能有很多,车牌就在其中一个矩形区域中
|
||||
# cv2.findContours()函数来查找检测物体的轮廓
|
||||
try:
|
||||
contours, hierarchy = cv2.findContours(img_edge2, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
||||
except ValueError:
|
||||
image, contours, hierarchy = cv2.findContours(img_edge2, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
||||
contours = [cnt for cnt in contours if cv2.contourArea(cnt) > Min_Area]
|
||||
# print('[ INFO ] len(contours): {}'.format(len(contours)))
|
||||
|
||||
# 一一排除不是车牌的矩形区域,找到最小外接矩形的长宽比复合车牌条件的边缘检测到的物体
|
||||
car_contours = []
|
||||
for cnt in contours:
|
||||
rect = cv2.minAreaRect(cnt)
|
||||
# 生成最小外接矩形,点集 cnt 存放的就是该四边形的4个顶点坐标(点集里面有4个点)
|
||||
# 函数 cv2.minAreaRect() 返回一个Box2D结构rect:(最小外接矩形的中心(x,y),(宽度,高度),旋转角度),
|
||||
# 但是要绘制这个矩形,我们需要矩形的4个顶点坐标box, 通过函数 cv2.boxPoints() 获得,
|
||||
# 返回形式[ [x0,y0], [x1,y1], [x2,y2], [x3,y3] ]。
|
||||
|
||||
# 得到的最小外接矩形的4个顶点顺序、中心坐标、宽度、高度、旋转角度(是度数形式,不是弧度数)
|
||||
# https://blog.csdn.net/lanyuelvyun/article/details/76614872
|
||||
|
||||
area_width, area_height = rect[1]
|
||||
if area_width < area_height:
|
||||
area_width, area_height = area_height, area_width
|
||||
wh_ratio = area_width / area_height
|
||||
#print(wh_ratio)
|
||||
# 要求矩形区域长宽比在2到5.5之间,2到5.5是车牌的长宽比,其余的矩形排除 一般的比例是3.5
|
||||
if wh_ratio > 2 and wh_ratio < 5.5:
|
||||
car_contours.append(rect)
|
||||
box = cv2.boxPoints(rect)
|
||||
box = np.int0(box)
|
||||
#oldimg = cv2.drawContours(oldimg, [box], 0, (0, 0, 255), 2)
|
||||
#cv2.imshow("edge4", oldimg)
|
||||
#print(rect)
|
||||
|
||||
# print("[ INFo ] len(car_contours): {}".format(len(car_contours)))
|
||||
# print("[ INFO ] 精确定位.")
|
||||
|
||||
card_imgs = []
|
||||
|
||||
# 矩形区域可能是倾斜的矩形,需要矫正,以便使用颜色定位
|
||||
# 这个就是为什么我们不选择YOLO,SSD或其他的目标检测算法来检测车牌号的原因!!!(给自己偷懒找个台阶 :) )
|
||||
for rect in car_contours:
|
||||
if rect[2] > -1 and rect[2] < 1:#创造角度,使得左、高、右、低拿到正确的值
|
||||
angle = 1
|
||||
else:
|
||||
angle = rect[2]
|
||||
rect = (rect[0], (rect[1][0]+5, rect[1][1]+5), angle)#扩大rect范围,避免车牌边缘被排除
|
||||
|
||||
box = cv2.boxPoints(rect)
|
||||
# 避免边界超出图像边界
|
||||
heigth_point = right_point = [0, 0]
|
||||
left_point = low_point = [pic_width, pic_hight]
|
||||
for point in box:
|
||||
if left_point[0] > point[0]:
|
||||
left_point = point
|
||||
if low_point[1] > point[1]:
|
||||
low_point = point
|
||||
if heigth_point[1] < point[1]:
|
||||
heigth_point = point
|
||||
if right_point[0] < point[0]:
|
||||
right_point = point
|
||||
|
||||
if left_point[1] <= right_point[1]: # 正角度
|
||||
new_right_point = [right_point[0], heigth_point[1]]
|
||||
pts2 = np.float32([left_point, heigth_point, new_right_point])#字符只是高度需要改变
|
||||
pts1 = np.float32([left_point, heigth_point, right_point])
|
||||
M = cv2.getAffineTransform(pts1, pts2) # 仿射变换
|
||||
dst = cv2.warpAffine(oldimg, M, (pic_width, pic_hight))
|
||||
point_limit(new_right_point)
|
||||
point_limit(heigth_point)
|
||||
point_limit(left_point)
|
||||
card_img = dst[int(left_point[1]):int(heigth_point[1]), int(left_point[0]):int(new_right_point[0])]
|
||||
card_imgs.append(card_img)
|
||||
#cv2.imshow("card", card_img)
|
||||
#cv2.waitKey(0)
|
||||
elif left_point[1] > right_point[1]: # 负角度
|
||||
|
||||
new_left_point = [left_point[0], heigth_point[1]]
|
||||
pts2 = np.float32([new_left_point, heigth_point, right_point])#字符只是高度需要改变
|
||||
pts1 = np.float32([left_point, heigth_point, right_point])
|
||||
M = cv2.getAffineTransform(pts1, pts2)
|
||||
dst = cv2.warpAffine(oldimg, M, (pic_width, pic_hight))
|
||||
point_limit(right_point)
|
||||
point_limit(heigth_point)
|
||||
point_limit(new_left_point)
|
||||
card_img = dst[int(right_point[1]):int(heigth_point[1]), int(new_left_point[0]):int(right_point[0])]
|
||||
card_imgs.append(card_img)
|
||||
#cv2.imshow("card", card_img)
|
||||
#cv2.waitKey(0)
|
||||
|
||||
# 开始使用颜色定位,排除不是车牌的矩形,目前只识别蓝、绿、黄车牌
|
||||
|
||||
colors = []
|
||||
for card_index,card_img in enumerate(card_imgs):
|
||||
green = yello = blue = black = white = 0
|
||||
card_img_hsv = cv2.cvtColor(card_img, cv2.COLOR_BGR2HSV)
|
||||
#有转换失败的可能,原因来自于上面矫正矩形出错
|
||||
if card_img_hsv is None:
|
||||
continue
|
||||
row_num, col_num= card_img_hsv.shape[:2]
|
||||
card_img_count = row_num * col_num
|
||||
|
||||
for i in range(row_num):
|
||||
for j in range(col_num):
|
||||
H = card_img_hsv.item(i, j, 0)
|
||||
S = card_img_hsv.item(i, j, 1)
|
||||
V = card_img_hsv.item(i, j, 2)
|
||||
if 11 < H <= 34 and S > 34:#图片分辨率调整
|
||||
yello += 1
|
||||
elif 35 < H <= 99 and S > 34:#图片分辨率调整
|
||||
green += 1
|
||||
elif 99 < H <= 124 and S > 34:#图片分辨率调整
|
||||
blue += 1
|
||||
|
||||
if 0 < H <180 and 0 < S < 255 and 0 < V < 46:
|
||||
black += 1
|
||||
elif 0 < H <180 and 0 < S < 43 and 221 < V < 225:
|
||||
white += 1
|
||||
color = "no"
|
||||
|
||||
limit1 = limit2 = 0
|
||||
if yello*2 >= card_img_count:
|
||||
color = "yello"
|
||||
limit1 = 11
|
||||
limit2 = 34#有的图片有色偏偏绿
|
||||
elif green*2 >= card_img_count:
|
||||
color = "green"
|
||||
limit1 = 35
|
||||
limit2 = 99
|
||||
elif blue*2 >= card_img_count:
|
||||
color = "blue"
|
||||
limit1 = 100
|
||||
limit2 = 124#有的图片有色偏偏紫
|
||||
elif black + white >= card_img_count*0.7: #TODO
|
||||
color = "bw"
|
||||
# print("[ INFO ] color: {}".format(color))
|
||||
colors.append(color)
|
||||
# print(blue, green, yello, black, white, card_img_count)
|
||||
#cv2.imshow("color", card_img)
|
||||
#cv2.waitKey(0)
|
||||
if limit1 == 0:
|
||||
continue
|
||||
#以上为确定车牌颜色
|
||||
|
||||
#以下为根据车牌颜色再定位,缩小边缘非车牌边界
|
||||
xl, xr, yh, yl = accurate_place(card_img_hsv, limit1, limit2, color,cfg)
|
||||
if yl == yh and xl == xr:
|
||||
continue
|
||||
need_accurate = False
|
||||
if yl >= yh:
|
||||
yl = 0
|
||||
yh = row_num
|
||||
need_accurate = True
|
||||
if xl >= xr:
|
||||
xl = 0
|
||||
xr = col_num
|
||||
need_accurate = True
|
||||
card_imgs[card_index] = card_img[yl:yh, xl:xr] if color != "green" or yl < (yh-yl)//4 else card_img[yl-(yh-yl)//4:yh, xl:xr]
|
||||
if need_accurate:#可能x或y方向未缩小,需要再试一次
|
||||
card_img = card_imgs[card_index]
|
||||
card_img_hsv = cv2.cvtColor(card_img, cv2.COLOR_BGR2HSV)
|
||||
xl, xr, yh, yl = accurate_place(card_img_hsv, limit1, limit2, color,cfg)
|
||||
if yl == yh and xl == xr:
|
||||
continue
|
||||
if yl >= yh:
|
||||
yl = 0
|
||||
yh = row_num
|
||||
if xl >= xr:
|
||||
xl = 0
|
||||
xr = col_num
|
||||
card_imgs[card_index] = card_img[yl:yh, xl:xr] if color != "green" or yl < (yh-yl)//4 else card_img[yl-(yh-yl)//4:yh, xl:xr]
|
||||
|
||||
|
||||
roi = card_img
|
||||
card_color = color
|
||||
labels = (int(right_point[1]), int(heigth_point[1]), int(left_point[0]), int(right_point[0]))
|
||||
|
||||
|
||||
return roi,labels, card_color#定位的车牌图像、车牌颜色
|
||||
|
||||
if __name__ == '__main__':
|
||||
for pic_file in os.listdir("./test_img"):
|
||||
|
||||
roi, label,color = CaridDetect(os.path.join("./test_img",pic_file))
|
||||
cv2.imwrite(os.path.join("./result",pic_file),roi)
|
||||
print("*"*50)
|
||||
print("[ ROI ] {}".format(roi))
|
||||
print("[ Color ] {}".format(color))
|
||||
print("[ Label ] {}".format(label))
|
||||
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from numpy.linalg import norm
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
from car_id_detect import *
|
||||
from svm_train import *
|
||||
|
||||
SZ = 20 #训练图片长宽
|
||||
MAX_WIDTH = 1000 #原始图片最大宽度
|
||||
Min_Area = 2000 #车牌区域允许最大面积
|
||||
PROVINCE_START = 1000
|
||||
|
||||
svm_model = SVM(C=1, gamma=0.5)
|
||||
model_1,model_2 = svm_model.train_svm()
|
||||
|
||||
|
||||
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 seperate_card(img, waves):
|
||||
'''
|
||||
根据找出的波峰,分隔图片,从而得到逐个字符图片
|
||||
'''
|
||||
part_cards = []
|
||||
for wave in waves:
|
||||
part_cards.append(img[:, wave[0]:wave[1]])
|
||||
return part_cards
|
||||
|
||||
def Cardseg(rois,colors,save_path):
|
||||
'''
|
||||
把一个roi列表和color列表,对应的每个车牌分割成一个一个的字
|
||||
然后做预测分类
|
||||
|
||||
当然也可以考虑OCR的办法,这里使用的是传统的分类问题解决的!!!!
|
||||
|
||||
'''
|
||||
seg_dic = {}
|
||||
old_seg_dic = {}
|
||||
for i, color in enumerate(colors):
|
||||
if color in ("blue", "yello", "green"):
|
||||
card_img = rois[i]
|
||||
gray_img = cv2.cvtColor(card_img, cv2.COLOR_BGR2GRAY)
|
||||
#黄、绿车牌字符比背景暗、与蓝车牌刚好相反,所以黄、绿车牌需要反向
|
||||
if color == "green" or color == "yello":
|
||||
gray_img = cv2.bitwise_not(gray_img)
|
||||
ret, gray_img = cv2.threshold(gray_img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
|
||||
#查找水平直方图波峰
|
||||
x_histogram = np.sum(gray_img, axis=1)
|
||||
x_min = np.min(x_histogram)
|
||||
x_average = np.sum(x_histogram)/x_histogram.shape[0]
|
||||
x_threshold = (x_min + x_average)/2
|
||||
wave_peaks = find_waves(x_threshold, x_histogram)
|
||||
if len(wave_peaks) == 0:
|
||||
# print("peak less 0:")
|
||||
continue
|
||||
|
||||
#认为水平方向,最大的波峰为车牌区域
|
||||
wave = max(wave_peaks, key=lambda x:x[1]-x[0])
|
||||
gray_img = gray_img[wave[0]:wave[1]]
|
||||
|
||||
#查找垂直直方图波峰
|
||||
row_num, col_num= gray_img.shape[:2]
|
||||
#去掉车牌上下边缘1个像素,避免白边影响阈值判断
|
||||
gray_img = gray_img[1:row_num-1]
|
||||
y_histogram = np.sum(gray_img, axis=0)
|
||||
y_min = np.min(y_histogram)
|
||||
y_average = np.sum(y_histogram)/y_histogram.shape[0]
|
||||
y_threshold = (y_min + y_average)/5 #U和0要求阈值偏小,否则U和0会被分成两半
|
||||
|
||||
wave_peaks = find_waves(y_threshold, y_histogram)
|
||||
|
||||
#for wave in wave_peaks:
|
||||
# cv2.line(card_img, pt1=(wave[0], 5), pt2=(wave[1], 5), color=(0, 0, 255), thickness=2)
|
||||
|
||||
#车牌字符数应大于6
|
||||
if len(wave_peaks) <= 6:
|
||||
# print("peak less 1:", len(wave_peaks))
|
||||
continue
|
||||
wave = max(wave_peaks, key=lambda x:x[1]-x[0])
|
||||
max_wave_dis = wave[1] - wave[0]
|
||||
|
||||
#判断是否是左侧车牌边缘
|
||||
if wave_peaks[0][1] - wave_peaks[0][0] < max_wave_dis/3 and wave_peaks[0][0] == 0:
|
||||
wave_peaks.pop(0)
|
||||
|
||||
#组合分离汉字
|
||||
cur_dis = 0
|
||||
for i,wave in enumerate(wave_peaks):
|
||||
if wave[1] - wave[0] + cur_dis > max_wave_dis * 0.6:
|
||||
break
|
||||
else:
|
||||
cur_dis += wave[1] - wave[0]
|
||||
if i > 0:
|
||||
wave = (wave_peaks[0][0], wave_peaks[i][1])
|
||||
wave_peaks = wave_peaks[i+1:]
|
||||
wave_peaks.insert(0, wave)
|
||||
|
||||
#去除车牌上的分隔点
|
||||
point = wave_peaks[2]
|
||||
if point[1] - point[0] < max_wave_dis/3:
|
||||
point_img = gray_img[:,point[0]:point[1]]
|
||||
if np.mean(point_img) < 255/5:
|
||||
wave_peaks.pop(2)
|
||||
|
||||
if len(wave_peaks) <= 6:
|
||||
# print("peak less 2:", len(wave_peaks))
|
||||
continue
|
||||
part_cards = seperate_card(gray_img, wave_peaks)
|
||||
|
||||
predict_result = []
|
||||
for i, part_card in enumerate(part_cards):
|
||||
#可能是固定车牌的铆钉
|
||||
if np.mean(part_card) < 255/5:
|
||||
# print("a point")
|
||||
continue
|
||||
part_card_old = part_card
|
||||
w = abs(part_card.shape[1] - SZ)//2
|
||||
|
||||
part_card = cv2.copyMakeBorder(part_card, 0, 0, w, w, cv2.BORDER_CONSTANT, value = [0,0,0]) #用来给图片添加边框
|
||||
part_card = cv2.resize(part_card, (SZ, SZ), interpolation=cv2.INTER_AREA)
|
||||
|
||||
#part_card = deskew(part_card)
|
||||
part_card = preprocess_hog([part_card])
|
||||
if i == 0:
|
||||
resp = model_2.predict(part_card)
|
||||
charactor = provinces[int(resp[0]) - PROVINCE_START]
|
||||
else:
|
||||
resp = model_1.predict(part_card)
|
||||
charactor = chr(resp[0])
|
||||
#判断最后一个数是否是车牌边缘,假设车牌边缘被认为是1
|
||||
if charactor == "1" and i == len(part_cards)-1:
|
||||
if part_card_old.shape[0]/part_card_old.shape[1] >= 7:#1太细,认为是边缘
|
||||
continue
|
||||
predict_result.append(charactor)
|
||||
|
||||
# # 保存图片
|
||||
# cv2.imwrite(os.path.join(save_path,str(i)+".jpg"),part_card)
|
||||
|
||||
seg_dic[i] = part_cards
|
||||
old_seg_dic[i] = part_card_old
|
||||
return seg_dic, old_seg_dic, predict_result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
for pic_file in os.listdir("./test_img"):
|
||||
roi, label, color = CaridDetect(os.path.join("./test_img",pic_file))
|
||||
|
||||
save_path = "./result_seg/"+pic_file.split(".")[0]
|
||||
if not os.path.exists(save_path):
|
||||
os.makedirs(save_path)
|
||||
seg_dict, _ , pre= Cardseg([roi],[color],save_path)
|
||||
print(pre)
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"config":[
|
||||
{
|
||||
"open":1,
|
||||
"blur":3,
|
||||
"morphologyr":4,
|
||||
"morphologyc":19,
|
||||
"col_num_limit":10,
|
||||
"row_num_limit":21
|
||||
},
|
||||
{
|
||||
"open":0,
|
||||
"blur":3,
|
||||
"morphologyr":5,
|
||||
"morphologyc":12,
|
||||
"col_num_limit":10,
|
||||
"row_num_limit":18
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
__author__ = xujing
|
||||
__date__ = 2019-07-05
|
||||
"""
|
||||
from PyQt5.QtCore import pyqtSlot
|
||||
from PyQt5.QtWidgets import QMainWindow
|
||||
|
||||
from PyQt5.QtCore import *
|
||||
from PyQt5.QtWidgets import *
|
||||
from PyQt5 import *
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtCore import pyqtSlot
|
||||
from PyQt5.QtWidgets import QMainWindow
|
||||
|
||||
from Ui_my_main_ui import Ui_MainWindow
|
||||
import sys
|
||||
import cv2
|
||||
from car_id_detect import *
|
||||
from svm_train import *
|
||||
from card_seg import *
|
||||
|
||||
class MainWindow(QMainWindow, Ui_MainWindow):
|
||||
"""
|
||||
槽函数
|
||||
"""
|
||||
def __init__(self, parent=None):
|
||||
"""
|
||||
Constructor
|
||||
|
||||
@param parent reference to the parent widget
|
||||
@type QWidget
|
||||
"""
|
||||
super(MainWindow, self).__init__(parent)
|
||||
self.setupUi(self)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_pushButton_clicked(self):
|
||||
"""
|
||||
最下化
|
||||
"""
|
||||
print('最小化')
|
||||
QMainWindow.showMinimized(self)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_pushButton_2_clicked(self):
|
||||
"""
|
||||
退出
|
||||
"""
|
||||
print("退出")
|
||||
sys.exit(0)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_pushButton_6_clicked(self):
|
||||
"""
|
||||
加载图像
|
||||
"""
|
||||
print("加载图像")
|
||||
try:
|
||||
self.file_dir_temp,_ = QFileDialog.getOpenFileName(self,"选择被检测的车辆","D:/")
|
||||
self.file_dir = self.file_dir_temp.replace("\\","/")
|
||||
print(self.file_dir)
|
||||
|
||||
roi, label, color = CaridDetect(self.file_dir)
|
||||
seg_dict, _, pre = Cardseg([roi],[color],None)
|
||||
print(pre)
|
||||
|
||||
# segment
|
||||
cv2.imwrite(os.path.join("./temp/seg_card.jpg"),roi)
|
||||
seg_img = cv2.imread("./temp/seg_card.jpg")
|
||||
seg_rows, seg_cols, seg_channels = seg_img.shape
|
||||
bytesPerLine = seg_channels * seg_cols
|
||||
cv2.cvtColor(seg_img, cv2.COLOR_BGR2RGB,seg_img)
|
||||
QImg = QImage(seg_img.data, seg_cols, seg_rows,bytesPerLine, QImage.Format_RGB888)
|
||||
self.label_2.setPixmap(QPixmap.fromImage(QImg).scaled(self.label_2.size(),
|
||||
Qt.KeepAspectRatio, Qt.SmoothTransformation))
|
||||
|
||||
# reg result
|
||||
pre.insert(2,"·")
|
||||
self.label_3.setText(" "+"".join(pre))
|
||||
|
||||
# clor view
|
||||
if color == "yello":
|
||||
self.label_4.setStyleSheet("background-color: rgb(255, 255, 0);")
|
||||
elif color == "green":
|
||||
self.label_4.setStyleSheet("background-color: rgb(0, 255,0);")
|
||||
elif color == "blue":
|
||||
self.label_4.setStyleSheet("background-color: rgb(0, 0, 255);")
|
||||
else:
|
||||
self.label_4.setText("未识别出车牌颜色")
|
||||
|
||||
|
||||
frame = cv2.imread(self.file_dir)
|
||||
# cv2.rectangle(frame, (label[0],label[2]), (label[1],label[3]), (0,0,255), 2)
|
||||
font = cv2.FONT_HERSHEY_SIMPLEX
|
||||
cv2.putText(frame, 'https://github.com/DataXujing/vehicle-license-plate-recognition', (10, 10), font, 0.3, (0, 0, 255), 1)
|
||||
img_rows, img_cols, channels = frame.shape
|
||||
bytesPerLine = channels * img_cols
|
||||
cv2.cvtColor(frame, cv2.COLOR_BGR2RGB,frame)
|
||||
QImg = QImage(frame.data, img_cols, img_rows,bytesPerLine, QImage.Format_RGB888)
|
||||
self.label.setPixmap(QPixmap.fromImage(QImg).scaled(self.label.size(),
|
||||
Qt.KeepAspectRatio, Qt.SmoothTransformation))
|
||||
QtWidgets.QApplication.processEvents()
|
||||
|
||||
except Exception as e:
|
||||
QMessageBox.warning(self,"错误提示","[错误提示(请联系开发人员处理)]:\n" + str(e)+"\n或识别失败导致")
|
||||
|
||||
|
||||
@pyqtSlot()
|
||||
def on_pushButton_7_clicked(self):
|
||||
"""
|
||||
加载视频
|
||||
"""
|
||||
print("加载视频")
|
||||
QMessageBox.information(self,"加载实时视频","未检测到实时视频源或暂未开通快该服务!")
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
|
||||
splash = QSplashScreen(QtGui.QPixmap(':/pic/pic/face.png'))
|
||||
|
||||
splash.show()
|
||||
splash.showMessage('渲染界面...')
|
||||
QThread.sleep(0.6)
|
||||
splash.showMessage('正在初始化程序...')
|
||||
QThread.sleep(0.6)
|
||||
app. processEvents()
|
||||
ui =MainWindow()
|
||||
ui.show()
|
||||
splash.finish(ui)
|
||||
|
||||
sys.exit(app.exec_())
|
|
@ -0,0 +1,36 @@
|
|||
# -*- mode: python -*-
|
||||
|
||||
block_cipher = None
|
||||
|
||||
|
||||
a = Analysis(['my_main_ui.py'],
|
||||
pathex=['C:\\Users\\xujing.LAPTOP-LLR84L1D\\Desktop\\car_id_detect_reg\\card_soft'],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
noarchive=False)
|
||||
pyz = PYZ(a.pure, a.zipped_data,
|
||||
cipher=block_cipher)
|
||||
exe = EXE(pyz,
|
||||
a.scripts,
|
||||
[],
|
||||
exclude_binaries=True,
|
||||
name='my_main_ui',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
console=False , icon='logo.ico')
|
||||
coll = COLLECT(exe,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
strip=False,
|
||||
upx=True,
|
||||
name='my_main_ui')
|
|
@ -0,0 +1,215 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>906</width>
|
||||
<height>600</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>交通门禁车牌检测和识别系统</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="my_pic.qrc">
|
||||
<normaloff>:/pic/pic/logo.ico</normaloff>:/pic/pic/logo.ico</iconset>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralWidget">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>50</y>
|
||||
<width>611</width>
|
||||
<height>501</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="pushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>720</x>
|
||||
<y>20</y>
|
||||
<width>75</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>最小化</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="pushButton_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>810</x>
|
||||
<y>20</y>
|
||||
<width>75</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>关闭</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="pushButton_3">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>690</x>
|
||||
<y>90</y>
|
||||
<width>191</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>车牌检测</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="my_pic.qrc">
|
||||
<normaloff>:/pic/pic/cut.png</normaloff>:/pic/pic/cut.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>700</x>
|
||||
<y>140</y>
|
||||
<width>181</width>
|
||||
<height>61</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="pushButton_4">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>690</x>
|
||||
<y>230</y>
|
||||
<width>191</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>车牌识别</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="my_pic.qrc">
|
||||
<normaloff>:/pic/pic/shibie.png</normaloff>:/pic/pic/shibie.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>700</x>
|
||||
<y>290</y>
|
||||
<width>171</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="pushButton_5">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>700</x>
|
||||
<y>360</y>
|
||||
<width>181</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>车牌颜色</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="my_pic.qrc">
|
||||
<normaloff>:/pic/pic/color.png</normaloff>:/pic/pic/color.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>700</x>
|
||||
<y>400</y>
|
||||
<width>181</width>
|
||||
<height>21</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">background-color: rgb(0, 0, 255);</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="pushButton_6">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>710</x>
|
||||
<y>492</y>
|
||||
<width>75</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>图像</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="my_pic.qrc">
|
||||
<normaloff>:/pic/pic/image.png</normaloff>:/pic/pic/image.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="pushButton_7">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>810</x>
|
||||
<y>490</y>
|
||||
<width>71</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>视频</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="my_pic.qrc">
|
||||
<normaloff>:/pic/pic/video.png</normaloff>:/pic/pic/video.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="pushButton_8">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>10</y>
|
||||
<width>201</width>
|
||||
<height>31</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Meiryo UI</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>交通门禁车牌检测和识别系统</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="my_pic.qrc">
|
||||
<normaloff>:/pic/pic/logo.ico</normaloff>:/pic/pic/logo.ico</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="my_pic.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -0,0 +1,11 @@
|
|||
<RCC>
|
||||
<qresource prefix="pic">
|
||||
<file>pic/color.png</file>
|
||||
<file>pic/cut.png</file>
|
||||
<file>pic/face.png</file>
|
||||
<file>pic/image.png</file>
|
||||
<file>pic/logo.ico</file>
|
||||
<file>pic/shibie.png</file>
|
||||
<file>pic/video.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 1.1 MiB |
After Width: | Height: | Size: 1.4 MiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 314 KiB |
After Width: | Height: | Size: 94 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 678 KiB |
After Width: | Height: | Size: 470 KiB |
After Width: | Height: | Size: 698 KiB |
After Width: | Height: | Size: 4.6 KiB |
|
@ -0,0 +1,6 @@
|
|||
cv2
|
||||
numpy
|
||||
json
|
||||
PyQt5
|
||||
qtawesome
|
||||
pyinstaller
|
|
@ -0,0 +1,8 @@
|
|||
@echo off
|
||||
color a
|
||||
echo [ INFO ] Time :
|
||||
time/T
|
||||
|
||||
python my_main_ui.py
|
||||
|
||||
pause
|
|
@ -0,0 +1,173 @@
|
|||
'''
|
||||
训练svm
|
||||
'''
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from numpy.linalg import norm
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
SZ = SZ = 20
|
||||
PROVINCE_START = 1000
|
||||
|
||||
provinces = [
|
||||
"zh_cuan", "川",
|
||||
"zh_e", "鄂",
|
||||
"zh_gan", "赣",
|
||||
"zh_gan1", "甘",
|
||||
"zh_gui", "贵",
|
||||
"zh_gui1", "桂",
|
||||
"zh_hei", "黑",
|
||||
"zh_hu", "沪",
|
||||
"zh_ji", "冀",
|
||||
"zh_jin", "津",
|
||||
"zh_jing", "京",
|
||||
"zh_jl", "吉",
|
||||
"zh_liao", "辽",
|
||||
"zh_lu", "鲁",
|
||||
"zh_meng", "蒙",
|
||||
"zh_min", "闽",
|
||||
"zh_ning", "宁",
|
||||
"zh_qing", "靑",
|
||||
"zh_qiong", "琼",
|
||||
"zh_shan", "陕",
|
||||
"zh_su", "苏",
|
||||
"zh_sx", "晋",
|
||||
"zh_wan", "皖",
|
||||
"zh_xiang", "湘",
|
||||
"zh_xin", "新",
|
||||
"zh_yu", "豫",
|
||||
"zh_yu1", "渝",
|
||||
"zh_yue", "粤",
|
||||
"zh_yun", "云",
|
||||
"zh_zang", "藏",
|
||||
"zh_zhe", "浙"
|
||||
]
|
||||
|
||||
|
||||
# 数据处理
|
||||
def deskew(img):
|
||||
m = cv2.moments(img)
|
||||
if abs(m['mu02']) < 1e-2:
|
||||
return img.copy()
|
||||
skew = m['mu11']/m['mu02']
|
||||
M = np.float32([[1, skew, -0.5*SZ*skew], [0, 1, 0]])
|
||||
img = cv2.warpAffine(img, M, (SZ, SZ), flags=cv2.WARP_INVERSE_MAP | cv2.INTER_LINEAR)
|
||||
return img
|
||||
|
||||
# 特征工程
|
||||
def preprocess_hog(digits):
|
||||
samples = []
|
||||
for img in digits:
|
||||
gx = cv2.Sobel(img, cv2.CV_32F, 1, 0)
|
||||
gy = cv2.Sobel(img, cv2.CV_32F, 0, 1)
|
||||
mag, ang = cv2.cartToPolar(gx, gy)
|
||||
bin_n = 16
|
||||
bin = np.int32(bin_n*ang/(2*np.pi))
|
||||
bin_cells = bin[:10,:10], bin[10:,:10], bin[:10,10:], bin[10:,10:]
|
||||
mag_cells = mag[:10,:10], mag[10:,:10], mag[:10,10:], mag[10:,10:]
|
||||
hists = [np.bincount(b.ravel(), m.ravel(), bin_n) for b, m in zip(bin_cells, mag_cells)]
|
||||
hist = np.hstack(hists)
|
||||
|
||||
# transform to Hellinger kernel
|
||||
eps = 1e-7
|
||||
hist /= hist.sum() + eps
|
||||
hist = np.sqrt(hist)
|
||||
hist /= norm(hist) + eps
|
||||
|
||||
samples.append(hist)
|
||||
return np.float32(samples)
|
||||
|
||||
|
||||
class StatModel(object):
|
||||
def load(self, fn):
|
||||
self.model = self.model.load(fn)
|
||||
def save(self, fn):
|
||||
self.model.save(fn)
|
||||
|
||||
class SVM(StatModel):
|
||||
def __init__(self, C = 1, gamma = 0.5):
|
||||
self.model = cv2.ml.SVM_create()
|
||||
self.model.setGamma(gamma)
|
||||
self.model.setC(C)
|
||||
self.model.setKernel(cv2.ml.SVM_RBF)
|
||||
self.model.setType(cv2.ml.SVM_C_SVC)
|
||||
# train svm
|
||||
def train(self, samples, responses):
|
||||
self.model.train(samples, cv2.ml.ROW_SAMPLE, responses)
|
||||
# inference
|
||||
def predict(self, samples):
|
||||
r = self.model.predict(samples)
|
||||
return r[1].ravel()
|
||||
|
||||
def train_svm(self):
|
||||
#识别英文字母和数字
|
||||
self.model = SVM(C=1, gamma=0.5)
|
||||
#识别中文
|
||||
self.modelchinese = SVM(C=1, gamma=0.5)
|
||||
if os.path.exists("./train_dat/svm.dat"):
|
||||
self.model.load("./train_dat/svm.dat")
|
||||
else:
|
||||
chars_train = []
|
||||
chars_label = []
|
||||
|
||||
for root, dirs, files in os.walk("./train/chars2"):
|
||||
if len(os.path.basename(root)) > 1:
|
||||
continue
|
||||
root_int = ord(os.path.basename(root))
|
||||
for filename in files:
|
||||
filepath = os.path.join(root,filename)
|
||||
digit_img = cv2.imread(filepath)
|
||||
digit_img = cv2.cvtColor(digit_img, cv2.COLOR_BGR2GRAY)
|
||||
chars_train.append(digit_img)
|
||||
#chars_label.append(1)
|
||||
chars_label.append(root_int)
|
||||
|
||||
chars_train = list(map(deskew, chars_train))
|
||||
chars_train = preprocess_hog(chars_train)
|
||||
#chars_train = chars_train.reshape(-1, 20, 20).astype(np.float32)
|
||||
chars_label = np.array(chars_label)
|
||||
print(chars_train.shape)
|
||||
self.model.train(chars_train, chars_label)
|
||||
|
||||
if os.path.exists("./train_dat/svmchinese.dat"):
|
||||
self.modelchinese.load("./train_dat/svmchinese.dat")
|
||||
else:
|
||||
chars_train = []
|
||||
chars_label = []
|
||||
for root, dirs, files in os.walk("./train/charsChinese"):
|
||||
if not os.path.basename(root).startswith("zh_"):
|
||||
continue
|
||||
pinyin = os.path.basename(root)
|
||||
index = provinces.index(pinyin) + PROVINCE_START + 1 #1是拼音对应的汉字
|
||||
for filename in files:
|
||||
filepath = os.path.join(root,filename)
|
||||
digit_img = cv2.imread(filepath)
|
||||
digit_img = cv2.cvtColor(digit_img, cv2.COLOR_BGR2GRAY)
|
||||
chars_train.append(digit_img)
|
||||
#chars_label.append(1)
|
||||
chars_label.append(index)
|
||||
chars_train = list(map(deskew, chars_train))
|
||||
chars_train = preprocess_hog(chars_train)
|
||||
#chars_train = chars_train.reshape(-1, 20, 20).astype(np.float32)
|
||||
chars_label = np.array(chars_label)
|
||||
print(chars_train.shape)
|
||||
self.modelchinese.train(chars_train, chars_label)
|
||||
|
||||
return self.model, self.modelchinese
|
||||
|
||||
def save_traindata(self):
|
||||
if not os.path.exists("./train_dat/svm.dat"):
|
||||
self.model.save("./train_dat/svm.dat")
|
||||
if not os.path.exists("./train_dat/svmchinese.dat"):
|
||||
self.modelchinese.save("./train_dat/svmchinese.dat")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
svm_model = SVM(C=1, gamma=0.5)
|
||||
# svm_model.save_traindata()
|
||||
model_1,model_2 = svm_model.train_svm()
|
||||
print(model_1)
|
||||
|
After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 61 KiB |
After Width: | Height: | Size: 63 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 470 KiB |
After Width: | Height: | Size: 678 KiB |
After Width: | Height: | Size: 639 KiB |
After Width: | Height: | Size: 695 KiB |
After Width: | Height: | Size: 601 KiB |
After Width: | Height: | Size: 698 KiB |
After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 138 KiB |
After Width: | Height: | Size: 4.3 MiB |