├── .gitignore ├── README.md ├── chepai ├── car4.jpg ├── dhm.jpg └── ganzou5.png ├── chuli.py ├── img_function.py ├── img_math.py ├── img_recognition.py ├── main.py ├── pic ├── 01.png ├── 02.jpg ├── 035.png ├── hy.png └── locate.png ├── svm.dat ├── svmchinese.dat └── 车牌识别.pptx /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python opencv 车牌识别 2 | 3 | + 数字图像处理课程设计作业 4 | + Python3+OpenCV 5 | + 使用tkinter搭建界面 6 | + `tmp/`文件夹是数字图像处理过程 7 | + `chepai/`文件夹是车牌图片 8 | + `pic/`文件夹是程序界面图 9 | + PPT文件是验收时要讲的 10 | + 程序是从网上学习的并自己弄的,不完善,识别率不高 11 | 12 | ## 开发环境配置 13 | > `pip install numpy` 14 | 15 | > `pip install pillow` 16 | 17 | > `pip install opencv-python` 18 | 19 | ## 图片展示 20 | 21 | ![首界面](pic/01.png) 22 | 23 | ![识别结果](pic/02.jpg) 24 | 25 | ![图像处理过程](pic/035.png) 26 | -------------------------------------------------------------------------------- /chepai/car4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chao2020x/license-plate-recognition/1269a2def2b7d5fdd6d7c6f65d30e4f27078709a/chepai/car4.jpg -------------------------------------------------------------------------------- /chepai/dhm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chao2020x/license-plate-recognition/1269a2def2b7d5fdd6d7c6f65d30e4f27078709a/chepai/dhm.jpg -------------------------------------------------------------------------------- /chepai/ganzou5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chao2020x/license-plate-recognition/1269a2def2b7d5fdd6d7c6f65d30e4f27078709a/chepai/ganzou5.png -------------------------------------------------------------------------------- /chuli.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from tkinter import ttk 3 | from PIL import Image, ImageTk 4 | import img_math,cv2,os 5 | 6 | class App(ttk.Frame): 7 | width = 750 #宽 8 | heigh = 400 #高 9 | def __init__(self, win): 10 | ttk.Frame.__init__(self,win) 11 | self.pack() 12 | win.title("车牌识别系统") 13 | win.geometry('+300+200') 14 | win.minsize(App.width,App.heigh) 15 | 16 | frame_1 = ttk.Frame(self) 17 | frame_1.grid(column=0, row=0) 18 | 19 | frame_2 = ttk.Frame(self) 20 | frame_2.grid(column=1, row=0) 21 | 22 | frame_3 = ttk.Frame(self) 23 | frame_3.grid(column=2, row=0) 24 | 25 | frame_4 = ttk.Frame(self) 26 | frame_4.grid(column=0, row=1) 27 | 28 | 29 | 30 | #显示分离后的车牌字符 31 | frame_5111 = ttk.Frame(self) 32 | frame_5111.grid(column=1, row=1) 33 | 34 | #显示分离后的车牌字符 35 | frame_5 = ttk.Frame(frame_5111) 36 | frame_5.pack() 37 | frame_5222 = ttk.Frame(frame_5111) 38 | frame_5222.pack() 39 | 40 | 41 | frame_6 = ttk.Frame(self) 42 | frame_6.grid(column=2, row=1) 43 | 44 | self.image_1 = ttk.Label(frame_1) 45 | self.image_1.pack() 46 | self.image_11 = ttk.Label(frame_1,text='灰度变化',font=('Times', '14')) 47 | self.image_11.pack() 48 | 49 | self.image_2 = ttk.Label(frame_2) 50 | self.image_2.pack() 51 | self.image_22 = ttk.Label(frame_2,text='边缘检测',font=('Times', '14')) 52 | self.image_22.pack() 53 | 54 | self.image_3 = ttk.Label(frame_3) 55 | self.image_3.pack() 56 | self.image_33 = ttk.Label(frame_3,text='形态学处理',font=('Times', '14')) 57 | self.image_33.pack() 58 | 59 | self.image_4 = ttk.Label(frame_4) 60 | self.image_4.pack() 61 | self.image_44 = ttk.Label(frame_4,text='车牌定位',font=('Times', '14')) 62 | self.image_44.pack() 63 | 64 | self.image_5_1 = ttk.Label(frame_5) 65 | self.image_5_1.grid(column=0, row=0) 66 | self.image_5_2 = ttk.Label(frame_5) 67 | self.image_5_2.grid(column=1, row=0) 68 | self.image_5_3 = ttk.Label(frame_5) 69 | self.image_5_3.grid(column=2, row=0) 70 | self.image_5_4 = ttk.Label(frame_5) 71 | self.image_5_4.grid(column=3, row=0) 72 | self.image_5_5 = ttk.Label(frame_5) 73 | self.image_5_5.grid(column=4, row=0) 74 | self.image_5_6 = ttk.Label(frame_5) 75 | self.image_5_6.grid(column=5, row=0) 76 | self.image_5_7 = ttk.Label(frame_5) 77 | self.image_5_7.grid(column=6, row=0) 78 | self.image_5_8 = ttk.Label(frame_5222,text='字符分割',font=('Times', '14')) 79 | self.image_5_8.pack() 80 | 81 | # self.image_6 = ttk.Label(frame_6) 82 | # self.image_6.pack() 83 | chu = ttk.Button( 84 | frame_6, text="退出", width=20, command=self.close_window) 85 | chu.grid(column=0, row=2) 86 | self.jiazai() 87 | 88 | def get_imgtk(self, img_bgr): 89 | img = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) 90 | im = Image.fromarray(img) 91 | pil_image_resized = im.resize((250,170),Image.ANTIALIAS) 92 | imgtk = ImageTk.PhotoImage(image=pil_image_resized) 93 | return imgtk 94 | 95 | def get_imgtk_1(self, img_bgr): 96 | img = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) 97 | im = Image.fromarray(img) 98 | pil_image_resized = im.resize((30,30),Image.ANTIALIAS) 99 | imgtk = ImageTk.PhotoImage(image=pil_image_resized) 100 | return imgtk 101 | 102 | def jiazai(self): 103 | img_1 = img_math.img_read("tmp/img_gray.jpg") 104 | self.img1 = self.get_imgtk(img_1) 105 | self.image_1.configure(image=self.img1) 106 | 107 | img_2 = img_math.img_read("tmp/img_edge.jpg") 108 | self.img2 = self.get_imgtk(img_2) 109 | self.image_2.configure(image=self.img2) 110 | 111 | img_3 = img_math.img_read("tmp/img_xingtai.jpg") 112 | self.img3 = self.get_imgtk(img_3) 113 | self.image_3.configure(image=self.img3) 114 | 115 | img_4 = img_math.img_read("tmp/img_caijian.jpg") 116 | self.img4 = self.get_imgtk(img_4) 117 | self.image_4.configure(image=self.img4) 118 | 119 | 120 | img_5_1 = img_math.img_read("tmp/chechar1.jpg") 121 | self.img51 = self.get_imgtk_1(img_5_1) 122 | self.image_5_1.configure(image=self.img51) 123 | 124 | img_5_2 = img_math.img_read("tmp/chechar2.jpg") 125 | self.img52 = self.get_imgtk_1(img_5_2) 126 | self.image_5_2.configure(image=self.img52) 127 | 128 | img_5_3 = img_math.img_read("tmp/chechar3.jpg") 129 | self.img53 = self.get_imgtk_1(img_5_3) 130 | self.image_5_3.configure(image=self.img53) 131 | 132 | img_5_4 = img_math.img_read("tmp/chechar4.jpg") 133 | self.img54 = self.get_imgtk_1(img_5_4) 134 | self.image_5_4.configure(image=self.img54) 135 | 136 | img_5_5 = img_math.img_read("tmp/chechar5.jpg") 137 | self.img55 = self.get_imgtk_1(img_5_5) 138 | self.image_5_5.configure(image=self.img55) 139 | 140 | img_5_6 = img_math.img_read("tmp/chechar6.jpg") 141 | self.img56 = self.get_imgtk_1(img_5_6) 142 | self.image_5_6.configure(image=self.img56) 143 | 144 | img_5_7 = img_math.img_read("tmp/chechar7.jpg") 145 | self.img57 = self.get_imgtk_1(img_5_7) 146 | self.image_5_7.configure(image=self.img57) 147 | 148 | def close_window(self): 149 | uu = ['tmp/'+i for i in os.listdir('tmp/')] 150 | for i in uu: 151 | os.remove(i) 152 | print("destroy") 153 | root.destroy() 154 | 155 | if __name__ == '__main__': 156 | root = tk.Tk() 157 | app = App(root) 158 | root.mainloop() -------------------------------------------------------------------------------- /img_function.py: -------------------------------------------------------------------------------- 1 | import os 2 | import cv2 3 | from PIL import Image 4 | import numpy as np 5 | import img_math 6 | import img_recognition 7 | 8 | SZ = 20 # 训练图片长宽 9 | MAX_WIDTH = 1000 # 原始图片最大宽度 10 | Min_Area = 2000 # 车牌区域允许最大面积 11 | PROVINCE_START = 1000 12 | 13 | 14 | class StatModel(object): 15 | def load(self, fn): 16 | self.model = self.model.load(fn) 17 | 18 | def save(self, fn): 19 | self.model.save(fn) 20 | 21 | 22 | class SVM(StatModel): 23 | def __init__(self, C=1, gamma=0.5): 24 | self.model = cv2.ml.SVM_create() 25 | self.model.setGamma(gamma) 26 | self.model.setC(C) 27 | self.model.setKernel(cv2.ml.SVM_RBF) 28 | self.model.setType(cv2.ml.SVM_C_SVC) 29 | 30 | # # 训练svm 31 | # def train(self, samples, responses): 32 | # self.model.train(samples, cv2.ml.ROW_SAMPLE, responses) 33 | 34 | # 字符识别 35 | def predict(self, samples): 36 | r = self.model.predict(samples) 37 | return r[1].ravel() 38 | 39 | 40 | class CardPredictor: 41 | def __init__(self): 42 | pass 43 | 44 | def train_svm(self): 45 | # 识别英文字母和数字 46 | self.model = SVM(C=1, gamma=0.5) 47 | # 识别中文 48 | self.modelchinese = SVM(C=1, gamma=0.5) 49 | if os.path.exists("svm.dat"): 50 | self.model.load("svm.dat") 51 | if os.path.exists("svmchinese.dat"): 52 | self.modelchinese.load("svmchinese.dat") 53 | 54 | def img_first_pre(self, car_pic_file): 55 | """ 56 | :param car_pic_file: 图像文件 57 | :return:已经处理好的图像文件 原图像文件 58 | """ 59 | if type(car_pic_file) == type(""): 60 | img = img_math.img_read(car_pic_file) #读取文件 61 | else: 62 | img = car_pic_file 63 | 64 | pic_hight, pic_width = img.shape[:2] #取彩色图片的高、宽 65 | if pic_width > MAX_WIDTH: 66 | resize_rate = MAX_WIDTH / pic_width 67 | # 缩小图片 68 | img = cv2.resize(img, (MAX_WIDTH, int(pic_hight * resize_rate)), interpolation=cv2.INTER_AREA) 69 | # 关于interpolation 有几个参数可以选择: 70 | # cv2.INTER_AREA - 局部像素重采样,适合缩小图片。 71 | # cv2.INTER_CUBIC和 cv2.INTER_LINEAR 更适合放大图像,其中INTER_LINEAR为默认方法。 72 | 73 | img = cv2.GaussianBlur(img, (5, 5), 0) 74 | # 高斯滤波是一种线性平滑滤波,对于除去高斯噪声有很好的效果 75 | # 0 是指根据窗口大小( 5,5 )来计算高斯函数标准差 76 | 77 | 78 | oldimg = img 79 | # 转化成灰度图像 80 | # 转换颜色空间 cv2.cvtColor 81 | # BGR ---> Gray cv2.COLOR_BGR2GRAY 82 | # BGR ---> HSV cv2.COLOR_BGR2HSV 83 | img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 84 | 85 | cv2.imwrite("tmp/img_gray.jpg", img) 86 | 87 | #ones()返回一个全1的n维数组 88 | Matrix = np.ones((20, 20), np.uint8) 89 | 90 | # 开运算:先进性腐蚀再进行膨胀就叫做开运算。它被用来去除噪声。 cv2.MORPH_OPEN 91 | img_opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, Matrix) 92 | 93 | # 图片叠加与融合 94 | # g (x) = (1 − α)f0 (x) + αf1 (x) a→(0,1)不同的a值可以实现不同的效果 95 | img_opening = cv2.addWeighted(img, 1, img_opening, -1, 0) 96 | # cv2.imwrite("tmp/img_opening.jpg", img_opening) 97 | # 创建20*20的元素为1的矩阵 开操作,并和img重合 98 | 99 | 100 | # Otsu’s二值化 101 | ret, img_thresh = cv2.threshold(img_opening, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) 102 | # Canny 边缘检测 103 | # 较大的阈值2用于检测图像中明显的边缘 一般情况下检测的效果不会那么完美,边缘检测出来是断断续续的 104 | # 较小的阈值1用于将这些间断的边缘连接起来 105 | img_edge = cv2.Canny(img_thresh, 100, 200) 106 | cv2.imwrite("tmp/img_edge.jpg", img_edge) 107 | 108 | Matrix = np.ones((4, 19), np.uint8) 109 | # 闭运算:先膨胀再腐蚀 110 | img_edge1 = cv2.morphologyEx(img_edge, cv2.MORPH_CLOSE, Matrix) 111 | # 开运算 112 | img_edge2 = cv2.morphologyEx(img_edge1, cv2.MORPH_OPEN, Matrix) 113 | cv2.imwrite("tmp/img_xingtai.jpg", img_edge2) 114 | return img_edge2, oldimg 115 | 116 | def img_only_color(self, filename, oldimg, img_contours): 117 | """ 118 | :param filename: 图像文件 119 | :param oldimg: 原图像文件 120 | :return: 识别到的字符、定位的车牌图像、车牌颜色 121 | """ 122 | pic_hight, pic_width = img_contours.shape[:2] # #取彩色图片的高、宽 123 | 124 | lower_blue = np.array([100, 110, 110]) 125 | upper_blue = np.array([130, 255, 255]) 126 | lower_yellow = np.array([15, 55, 55]) 127 | upper_yellow = np.array([50, 255, 255]) 128 | lower_green = np.array([50, 50, 50]) 129 | upper_green = np.array([100, 255, 255]) 130 | 131 | # BGR ---> HSV 132 | hsv = cv2.cvtColor(filename, cv2.COLOR_BGR2HSV) 133 | # 利用cv2.inRange函数设阈值,去除背景部分 134 | # 参数1:原图 135 | # 参数2:图像中低于值,图像值变为0 136 | # 参数3:图像中高于值,图像值变为0 137 | mask_blue = cv2.inRange(hsv, lower_blue, upper_blue) 138 | mask_yellow = cv2.inRange(hsv, lower_yellow, upper_yellow) 139 | mask_green = cv2.inRange(hsv, lower_yellow, upper_green) 140 | 141 | # 图像算术运算 按位运算 按位操作有: AND, OR, NOT, XOR 等 142 | output = cv2.bitwise_and(hsv, hsv, mask=mask_blue + mask_yellow + mask_green) 143 | # 根据阈值找到对应颜色 144 | 145 | output = cv2.cvtColor(output, cv2.COLOR_BGR2GRAY) 146 | Matrix = np.ones((20, 20), np.uint8) 147 | #使用一个 20x20 的卷积核 148 | img_edge1 = cv2.morphologyEx(output, cv2.MORPH_CLOSE, Matrix) #闭运算 149 | img_edge2 = cv2.morphologyEx(img_edge1, cv2.MORPH_OPEN, Matrix) #开运算 150 | 151 | card_contours = img_math.img_findContours(img_edge2) 152 | card_imgs = img_math.img_Transform(card_contours, oldimg, pic_width, pic_hight) 153 | colors, car_imgs = img_math.img_color(card_imgs) 154 | 155 | predict_result = [] 156 | predict_str = "" 157 | roi = None 158 | card_color = None 159 | 160 | for i, color in enumerate(colors): 161 | 162 | if color in ("blue", "yello", "green"): 163 | card_img = card_imgs[i] 164 | 165 | try: 166 | gray_img = cv2.cvtColor(card_img, cv2.COLOR_BGR2GRAY) 167 | except: 168 | print("gray转换失败") 169 | 170 | # 黄、绿车牌字符比背景暗、与蓝车牌刚好相反,所以黄、绿车牌需要反向 171 | if color == "green" or color == "yello": 172 | gray_img = cv2.bitwise_not(gray_img) 173 | ret, gray_img = cv2.threshold(gray_img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) 174 | x_histogram = np.sum(gray_img, axis=1) 175 | 176 | x_min = np.min(x_histogram) 177 | x_average = np.sum(x_histogram) / x_histogram.shape[0] 178 | x_threshold = (x_min + x_average) / 2 179 | wave_peaks = img_math.find_waves(x_threshold, x_histogram) 180 | 181 | if len(wave_peaks) == 0: 182 | # print("peak less 0:") 183 | continue 184 | # 认为水平方向,最大的波峰为车牌区域 185 | wave = max(wave_peaks, key=lambda x: x[1] - x[0]) 186 | 187 | 188 | gray_img = gray_img[wave[0]:wave[1]] 189 | # 查找垂直直方图波峰 190 | row_num, col_num = gray_img.shape[:2] 191 | # 去掉车牌上下边缘1个像素,避免白边影响阈值判断 192 | gray_img = gray_img[1:row_num - 1] 193 | y_histogram = np.sum(gray_img, axis=0) 194 | y_min = np.min(y_histogram) 195 | y_average = np.sum(y_histogram) / y_histogram.shape[0] 196 | y_threshold = (y_min + y_average) / 5 # U和0要求阈值偏小,否则U和0会被分成两半 197 | wave_peaks = img_math.find_waves(y_threshold, y_histogram) 198 | if len(wave_peaks) < 6: 199 | # print("peak less 1:", len(wave_peaks)) 200 | continue 201 | 202 | wave = max(wave_peaks, key=lambda x: x[1] - x[0]) 203 | max_wave_dis = wave[1] - wave[0] 204 | # 判断是否是左侧车牌边缘 205 | if wave_peaks[0][1] - wave_peaks[0][0] < max_wave_dis / 3 and wave_peaks[0][0] == 0: 206 | wave_peaks.pop(0) 207 | 208 | # 组合分离汉字 209 | cur_dis = 0 210 | for i, wave in enumerate(wave_peaks): 211 | if wave[1] - wave[0] + cur_dis > max_wave_dis * 0.6: 212 | break 213 | else: 214 | cur_dis += wave[1] - wave[0] 215 | if i > 0: 216 | wave = (wave_peaks[0][0], wave_peaks[i][1]) 217 | wave_peaks = wave_peaks[i + 1:] 218 | wave_peaks.insert(0, wave) 219 | 220 | point = wave_peaks[2] 221 | point_img = gray_img[:, point[0]:point[1]] 222 | if np.mean(point_img) < 255 / 5: 223 | wave_peaks.pop(2) 224 | 225 | if len(wave_peaks) <= 6: 226 | # print("peak less 2:", len(wave_peaks)) 227 | continue 228 | # print(wave_peaks) 229 | 230 | # wave_peaks 车牌字符 类型列表 包含7个(开始的横坐标,结束的横坐标) 231 | 232 | 233 | 234 | part_cards = img_math.seperate_card(gray_img, wave_peaks) 235 | 236 | for i, part_card in enumerate(part_cards): 237 | # 可能是固定车牌的铆钉 238 | 239 | if np.mean(part_card) < 255 / 5: 240 | # print("a point") 241 | continue 242 | part_card_old = part_card 243 | 244 | w = abs(part_card.shape[1] - SZ) // 2 245 | 246 | part_card = cv2.copyMakeBorder(part_card, 0, 0, w, w, cv2.BORDER_CONSTANT, value=[0, 0, 0]) 247 | part_card = cv2.resize(part_card, (SZ, SZ), interpolation=cv2.INTER_AREA) 248 | 249 | part_card = img_recognition.preprocess_hog([part_card]) 250 | if i == 0: 251 | resp = self.modelchinese.predict(part_card) 252 | charactor = img_recognition.provinces[int(resp[0]) - PROVINCE_START] 253 | else: 254 | resp = self.model.predict(part_card) 255 | charactor = chr(resp[0]) 256 | # 判断最后一个数是否是车牌边缘,假设车牌边缘被认为是1 257 | if charactor == "1" and i == len(part_cards) - 1: 258 | if part_card_old.shape[0] / part_card_old.shape[1] >= 7: # 1太细,认为是边缘 259 | continue 260 | predict_result.append(charactor) 261 | predict_str = "".join(predict_result) 262 | 263 | roi = card_img 264 | card_color = color 265 | break 266 | cv2.imwrite("tmp/img_caijian.jpg", roi) 267 | return predict_str, roi, card_color # 识别到的字符、定位的车牌图像、车牌颜色 268 | -------------------------------------------------------------------------------- /img_math.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import numpy as np 3 | 4 | Min_Area = 2000 # 车牌区域允许最大面积 5 | 6 | """ 7 | 该文件包含读文件函数 8 | 取零值函数 9 | 矩阵校正函数 10 | 颜色判断函数 11 | """ 12 | 13 | def img_read(filename): 14 | ''' 15 | 以uint8方式读取filename 16 | 放入imdecode中,cv2.IMREAD_COLOR读取彩色照片 17 | ''' 18 | 19 | #cv2.IMREAD_COLOR:读入一副彩色图像。图像的透明度会被忽略,这是默认参数 20 | #cv2.IMREAD_GRAYSCALE:以灰度模式读入图像 21 | #cv2.IMREAD_UNCHANGED:读入一幅图像,并且包括图像的 alpha 通道 22 | return cv2.imdecode(np.fromfile(filename, dtype=np.uint8), cv2.IMREAD_COLOR) 23 | 24 | 25 | 26 | def find_waves(threshold, histogram): 27 | up_point = -1 # 上升点 28 | is_peak = False 29 | if histogram[0] > threshold: 30 | up_point = 0 31 | is_peak = True 32 | wave_peaks = [] 33 | for i, x in enumerate(histogram): 34 | if is_peak and x < threshold: 35 | if i - up_point > 2: 36 | is_peak = False 37 | wave_peaks.append((up_point, i)) 38 | elif not is_peak and x >= threshold: 39 | is_peak = True 40 | up_point = i 41 | if is_peak and up_point != -1 and i - up_point > 4: 42 | wave_peaks.append((up_point, i)) 43 | return wave_peaks 44 | 45 | 46 | def point_limit(point): 47 | if point[0] < 0: 48 | point[0] = 0 49 | if point[1] < 0: 50 | point[1] = 0 51 | 52 | 53 | def accurate_place(card_img_hsv, limit1, limit2, color): 54 | row_num, col_num = card_img_hsv.shape[:2] 55 | xl = col_num 56 | xr = 0 57 | yh = 0 58 | yl = row_num 59 | row_num_limit = 21 60 | col_num_limit = col_num * 0.8 if color != "green" else col_num * 0.5 # 绿色有渐变 61 | for i in range(row_num): 62 | count = 0 63 | for j in range(col_num): 64 | H = card_img_hsv.item(i, j, 0) 65 | S = card_img_hsv.item(i, j, 1) 66 | V = card_img_hsv.item(i, j, 2) 67 | if limit1 < H <= limit2 and 34 < S and 46 < V: 68 | count += 1 69 | if count > col_num_limit: 70 | if yl > i: 71 | yl = i 72 | if yh < i: 73 | yh = i 74 | for j in range(col_num): 75 | count = 0 76 | for i in range(row_num): 77 | H = card_img_hsv.item(i, j, 0) 78 | S = card_img_hsv.item(i, j, 1) 79 | V = card_img_hsv.item(i, j, 2) 80 | if limit1 < H <= limit2 and 34 < S and 46 < V: 81 | count += 1 82 | if count > row_num - row_num_limit: 83 | if xl > j: 84 | xl = j 85 | if xr < j: 86 | xr = j 87 | return xl, xr, yh, yl 88 | 89 | 90 | def img_findContours(img_contours): 91 | # 查找轮廓 92 | # cv2.findContours() 93 | # 有三个参数,第一个是输入图像,第二个是轮廓检索模式,第三个是轮廓近似方法。参数为二值图,即黑白的(不是灰度图) 94 | # 返回值有三个,第一个是图像,第二个是轮廓,第三个是(轮廓的)层析结构。 95 | # 轮廓(第二个返回值)是一个 Python列表,其中存储这图像中的所有轮廓。 96 | # 每一个轮廓都是一个 Numpy 数组,包含对象边界点(x,y)的坐标。 97 | contours, hierarchy = cv2.findContours(img_contours, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) 98 | # cv2.RETR_TREE建立一个等级树结构的轮廓 99 | # cv2.CHAIN_APPROX_SIMPLE压缩水平方向,垂直方向,对角线方向的元素, 100 | # 只保留该方向的终点坐标,例如一个矩形轮廓只需4个点来保存轮廓信息 101 | 102 | # cv2.contourArea计算该轮廓的面积 103 | contours = [cnt for cnt in contours if cv2.contourArea(cnt) > Min_Area] 104 | # print("findContours len = ", len(contours)) 105 | 106 | # 面积小的都筛选掉 107 | car_contours = [] 108 | for cnt in contours: 109 | ant = cv2.minAreaRect(cnt)# 得到最小外接矩形的(中心(x,y), (宽,高), 旋转角度) 110 | width, height = ant[1] 111 | if width < height: 112 | width, height = height, width 113 | ration = width / height 114 | 115 | if 2 < ration < 5.5: 116 | car_contours.append(ant) 117 | # box = cv2.boxPoints(ant) # 获得要绘制这个矩形的 4 个角点 118 | 119 | return car_contours 120 | 121 | 122 | def img_Transform(car_contours, oldimg, pic_width, pic_hight): 123 | """ 124 | 进行矩形矫正 125 | """ 126 | car_imgs = [] 127 | for car_rect in car_contours: #(中心(x,y), (宽,高), 旋转角度) 128 | if -1 < car_rect[2] < 1: 129 | angle = 1 130 | # 对于角度为-1 1之间时,默认为1 131 | else: 132 | angle = car_rect[2] 133 | car_rect = (car_rect[0], (car_rect[1][0] + 5, car_rect[1][1] + 5), angle) 134 | 135 | box = cv2.boxPoints(car_rect) # 获得要绘制这个矩形的 4 个角点 136 | 137 | heigth_point = right_point = [0, 0] 138 | left_point = low_point = [pic_width, pic_hight] 139 | 140 | for point in box: 141 | if left_point[0] > point[0]: 142 | left_point = point 143 | if low_point[1] > point[1]: 144 | low_point = point 145 | if heigth_point[1] < point[1]: 146 | heigth_point = point 147 | if right_point[0] < point[0]: 148 | right_point = point 149 | 150 | if left_point[1] <= right_point[1]: # 正角度 151 | new_right_point = [right_point[0], heigth_point[1]] 152 | pts2 = np.float32([left_point, heigth_point, new_right_point]) # 字符只是高度需要改变 153 | pts1 = np.float32([left_point, heigth_point, right_point]) 154 | # 仿射变换 155 | M = cv2.getAffineTransform(pts1, pts2) 156 | dst = cv2.warpAffine(oldimg, M, (pic_width, pic_hight)) 157 | 158 | point_limit(new_right_point) 159 | point_limit(heigth_point) 160 | point_limit(left_point) 161 | 162 | car_img = dst[int(left_point[1]):int(heigth_point[1]), int(left_point[0]):int(new_right_point[0])] 163 | car_imgs.append(car_img) 164 | 165 | elif left_point[1] > right_point[1]: # 负角度 166 | new_left_point = [left_point[0], heigth_point[1]] 167 | pts2 = np.float32([new_left_point, heigth_point, right_point]) # 字符只是高度需要改变 168 | pts1 = np.float32([left_point, heigth_point, right_point]) 169 | M = cv2.getAffineTransform(pts1, pts2) 170 | dst = cv2.warpAffine(oldimg, M, (pic_width, pic_hight)) 171 | point_limit(right_point) 172 | point_limit(heigth_point) 173 | point_limit(new_left_point) 174 | car_img = dst[int(right_point[1]):int(heigth_point[1]), int(new_left_point[0]):int(right_point[0])] 175 | car_imgs.append(car_img) 176 | 177 | return car_imgs 178 | 179 | def img_color(card_imgs): 180 | """ 181 | 颜色判断函数 182 | """ 183 | colors = [] 184 | for card_index, card_img in enumerate(card_imgs): 185 | 186 | green = yello = blue = black = white = 0 187 | try: 188 | card_img_hsv = cv2.cvtColor(card_img, cv2.COLOR_BGR2HSV) 189 | except: 190 | print("矫正矩形出错, 转换失败")# 可能原因:上面矫正矩形出错 191 | 192 | if card_img_hsv is None: 193 | continue 194 | row_num, col_num = card_img_hsv.shape[:2] 195 | card_img_count = row_num * col_num 196 | 197 | for i in range(row_num): 198 | for j in range(col_num): 199 | H = card_img_hsv.item(i, j, 0) 200 | S = card_img_hsv.item(i, j, 1) 201 | V = card_img_hsv.item(i, j, 2) 202 | if 11 < H <= 34 and S > 34: 203 | yello += 1 204 | elif 35 < H <= 99 and S > 34: 205 | green += 1 206 | elif 99 < H <= 124 and S > 34: 207 | blue += 1 208 | 209 | if 0 < H < 180 and 0 < S < 255 and 0 < V < 46: 210 | black += 1 211 | elif 0 < H < 180 and 0 < S < 43 and 221 < V < 225: 212 | white += 1 213 | color = "no" 214 | 215 | limit1 = limit2 = 0 216 | if yello * 2 >= card_img_count: 217 | color = "yellow" 218 | limit1 = 11 219 | limit2 = 34 # 有的图片有色偏偏绿 220 | elif green * 2 >= card_img_count: 221 | color = "green" 222 | limit1 = 35 223 | limit2 = 99 224 | elif blue * 2 >= card_img_count: 225 | color = "blue" 226 | limit1 = 100 227 | limit2 = 124 # 有的图片有色偏偏紫 228 | elif black + white >= card_img_count * 0.7: 229 | color = "bw" 230 | colors.append(color) 231 | card_imgs[card_index] = card_img 232 | 233 | if limit1 == 0: 234 | continue 235 | xl, xr, yh, yl = accurate_place(card_img_hsv, limit1, limit2, color) 236 | if yl == yh and xl == xr: 237 | continue 238 | need_accurate = False 239 | if yl >= yh: 240 | yl = 0 241 | yh = row_num 242 | need_accurate = True 243 | if xl >= xr: 244 | xl = 0 245 | xr = col_num 246 | need_accurate = True 247 | 248 | if color == "green": 249 | card_imgs[card_index] = card_img 250 | else: 251 | card_imgs[card_index] = card_img[yl:yh, xl:xr] if color != "green" or yl < (yh - yl) // 4 else card_img[ 252 | yl - ( 253 | yh - yl) // 4:yh, 254 | xl:xr] 255 | 256 | if need_accurate: 257 | card_img = card_imgs[card_index] 258 | card_img_hsv = cv2.cvtColor(card_img, cv2.COLOR_BGR2HSV) 259 | xl, xr, yh, yl = accurate_place(card_img_hsv, limit1, limit2, color) 260 | if yl == yh and xl == xr: 261 | continue 262 | if yl >= yh: 263 | yl = 0 264 | yh = row_num 265 | if xl >= xr: 266 | xl = 0 267 | xr = col_num 268 | if color == "green": 269 | card_imgs[card_index] = card_img 270 | else: 271 | card_imgs[card_index] = card_img[yl:yh, xl:xr] if color != "green" or yl < (yh - yl) // 4 else card_img[ 272 | yl - ( 273 | yh - yl) // 4:yh, 274 | xl:xr] 275 | return colors, card_imgs 276 | 277 | def seperate_card(img, waves): 278 | """ 279 | 分离车牌字符 280 | """ 281 | h , w = img.shape 282 | part_cards = [] 283 | i = 0 284 | for wave in waves: 285 | i = i+1 286 | part_cards.append(img[:, wave[0]:wave[1]]) 287 | chrpic = img[0:h,wave[0]:wave[1]] 288 | 289 | #保存分离后的车牌图片 290 | cv2.imwrite('tmp/chechar{}.jpg'.format(i),chrpic) 291 | 292 | 293 | return part_cards 294 | 295 | -------------------------------------------------------------------------------- /img_recognition.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import numpy as np 3 | from numpy.linalg import norm 4 | 5 | SZ = 20 # 训练图片长宽 6 | MAX_WIDTH = 1000 # 原始图片最大宽度 7 | Min_Area = 2000 # 车牌区域允许最大面积 8 | PROVINCE_START = 1000 9 | # 来自opencv的sample,用于svm训练 10 | # def deskew(img): 11 | # m = cv2.moments(img) 12 | # if abs(m['mu02']) < 1e-2: 13 | # return img.copy() 14 | # skew = m['mu11'] / m['mu02'] 15 | # M = np.float32([[1, skew, -0.5 * SZ * skew], [0, 1, 0]]) 16 | # img = cv2.warpAffine(img, M, (SZ, SZ), flags=cv2.WARP_INVERSE_MAP | cv2.INTER_LINEAR) 17 | # return img 18 | 19 | 20 | # # 来自opencv的sample,用于svm训练 21 | def preprocess_hog(digits): 22 | samples = [] 23 | for img in digits: 24 | gx = cv2.Sobel(img, cv2.CV_32F, 1, 0) 25 | gy = cv2.Sobel(img, cv2.CV_32F, 0, 1) 26 | mag, ang = cv2.cartToPolar(gx, gy) 27 | bin_n = 16 28 | bin = np.int32(bin_n * ang / (2 * np.pi)) 29 | bin_cells = bin[:10, :10], bin[10:, :10], bin[:10, 10:], bin[10:, 10:] 30 | mag_cells = mag[:10, :10], mag[10:, :10], mag[:10, 10:], mag[10:, 10:] 31 | hists = [np.bincount(b.ravel(), m.ravel(), bin_n) for b, m in zip(bin_cells, mag_cells)] 32 | hist = np.hstack(hists) 33 | 34 | # transform to Hellinger kernel 35 | eps = 1e-7 36 | hist /= hist.sum() + eps 37 | hist = np.sqrt(hist) 38 | hist /= norm(hist) + eps 39 | 40 | samples.append(hist) 41 | return np.float32(samples) 42 | 43 | 44 | provinces = [ 45 | "zh_cuan", "川", 46 | "zh_e", "鄂", 47 | "zh_gan", "赣", 48 | "zh_gan1", "甘", 49 | "zh_gui", "贵", 50 | "zh_gui1", "桂", 51 | "zh_hei", "黑", 52 | "zh_hu", "沪", 53 | "zh_ji", "冀", 54 | "zh_jin", "津", 55 | "zh_jing", "京", 56 | "zh_jl", "吉", 57 | "zh_liao", "辽", 58 | "zh_lu", "鲁", 59 | "zh_meng", "蒙", 60 | "zh_min", "闽", 61 | "zh_ning", "宁", 62 | "zh_qing", "青", 63 | "zh_qiong", "琼", 64 | "zh_shan", "陕", 65 | "zh_su", "苏", 66 | "zh_sx", "晋", 67 | "zh_wan", "皖", 68 | "zh_xiang", "湘", 69 | "zh_xin", "新", 70 | "zh_yu", "豫", 71 | "zh_yu1", "渝", 72 | "zh_yue", "粤", 73 | "zh_yun", "云", 74 | "zh_zang", "藏", 75 | "zh_zhe", "浙" 76 | ] 77 | 78 | color_tr = { 79 | "green": ("绿牌", "#55FF55"), 80 | "yello": ("黄牌", "#FFFF00"), 81 | "blue": ("蓝牌", "#6666FF") 82 | } 83 | 84 | 85 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import cv2,os 2 | from tkinter.filedialog import askopenfilename 3 | from tkinter import ttk 4 | import tkinter as tk 5 | from PIL import Image, ImageTk 6 | import img_function as predict 7 | import img_math as img_math 8 | import img_recognition as img_rec 9 | 10 | 11 | class UI_main(ttk.Frame): 12 | pic_path = "" #图片路径 13 | pic_source = "" 14 | colorimg = 'white' #车牌颜色 15 | cameraflag = 0 16 | width = 700 #宽 17 | height = 400 #高 18 | color_transform = img_rec.color_tr 19 | 20 | def __init__(self, win): 21 | ttk.Frame.__init__(self, win) 22 | 23 | 24 | win.title("车牌识别系统") 25 | win.geometry('+300+200') 26 | win.minsize(UI_main.width,UI_main.height) 27 | win.configure(relief=tk.RIDGE) 28 | # win.update() 29 | 30 | 31 | self.pack(fill=tk.BOTH) 32 | frame_left = ttk.Frame(self) 33 | frame_right_1 = ttk.Frame(self) 34 | frame_right_2 = ttk.Frame(self) 35 | frame_left.pack(side=tk.LEFT, expand=1, fill=tk.BOTH) 36 | frame_right_1.pack(side=tk.TOP, expand=1, fill=tk.Y) 37 | frame_right_2.pack() 38 | 39 | #界面左边 --->车牌识别主界面大图片 40 | self.image_ctl = ttk.Label(frame_left) 41 | self.image_ctl.pack() 42 | 43 | #界面右边 --->定位车牌位置、识别结果 44 | ttk.Label(frame_right_1, text='定位车牌:', font=('Times', '14')).grid( 45 | column=0, row=6, sticky=tk.NW) 46 | 47 | 48 | self.roi_ct2 = ttk.Label(frame_right_1) 49 | self.roi_ct2.grid(column=0, row=7, sticky=tk.W,pady=5) 50 | ttk.Label(frame_right_1, text='识别结果:', font=('Times', '14')).grid( 51 | column=0, row=8, sticky=tk.W,pady=5) 52 | self.r_ct2 = ttk.Label(frame_right_1, text="", font=('Times', '20')) 53 | self.r_ct2.grid(column=0, row=9, sticky=tk.W,pady=5) 54 | 55 | #车牌颜色 56 | self.color_ct2 = ttk.Label(frame_right_1,background=self.colorimg, 57 | text="", width="4",font=('Times', '14')) 58 | self.color_ct2.grid(column=0, row=10, sticky=tk.W) 59 | 60 | #界面右下角 61 | from_pic_ctl = ttk.Button( 62 | frame_right_2, text="车牌图片", width=20, command=self.from_pic) 63 | from_pic_ctl.grid(column=0, row=1) 64 | 65 | #清除识别数据 66 | from_pic_chu = ttk.Button( 67 | frame_right_2, text="清除识别数据", width=20, command=self.clean) 68 | from_pic_chu.grid(column=0, row=2) 69 | #查看图像处理过程 70 | from_pic_chu = ttk.Button( 71 | frame_right_2, text="查看图像处理过程", width=20, command=self.pic_chuli) 72 | from_pic_chu.grid(column=0, row=3) 73 | 74 | 75 | 76 | self.clean() 77 | 78 | self.predictor = predict.CardPredictor() 79 | self.predictor.train_svm() 80 | 81 | def get_imgtk(self, img_bgr): 82 | img = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) 83 | im = Image.fromarray(img) 84 | pil_image_resized = im.resize((500,400),Image.ANTIALIAS) 85 | imgtk = ImageTk.PhotoImage(image=pil_image_resized) 86 | return imgtk 87 | 88 | #显示图片处理过程 89 | def pic_chuli(self): 90 | os.system("python ./chuli.py") 91 | 92 | def pic(self, pic_path): 93 | img_bgr = img_math.img_read(pic_path) 94 | first_img, oldimg = self.predictor.img_first_pre(img_bgr) 95 | if not self.cameraflag: 96 | self.imgtk = self.get_imgtk(img_bgr) 97 | self.image_ctl.configure(image=self.imgtk) 98 | r_color, roi_color, color_color = self.predictor.img_only_color(oldimg, 99 | oldimg, first_img) 100 | self.color_ct2.configure(background=color_color) 101 | try: 102 | Plate = HyperLPR_PlateRecogntion(img_bgr) 103 | r_color = Plate[0][0] 104 | except: 105 | pass 106 | self.show_roi(r_color, roi_color, color_color) 107 | self.colorimg = color_color 108 | print("|", color_color, 109 | r_color, "|", self.pic_source) 110 | 111 | #来自图片--->打开系统接口获取图片绝对路径 112 | def from_pic(self): 113 | self.cameraflag = 0 114 | self.pic_path = askopenfilename(title="选择识别图片", filetypes=[( 115 | "图片", "*.jpg;*.jpeg;*.png")]) 116 | 117 | self.clean() 118 | self.pic_source = "本地文件:" + self.pic_path 119 | self.pic(self.pic_path) 120 | print(self.colorimg) 121 | 122 | def show_roi(self, r, roi, color): # 车牌定位后的图片 123 | if r: 124 | try: 125 | roi = cv2.cvtColor(roi, cv2.COLOR_BGR2RGB) 126 | roi = Image.fromarray(roi) 127 | pil_image_resized = roi.resize((200, 50), Image.ANTIALIAS) 128 | self.tkImage2 = ImageTk.PhotoImage(image=pil_image_resized) 129 | self.roi_ct2.configure(image=self.tkImage2, state='enable') 130 | except: 131 | pass 132 | self.r_ct2.configure(text=str(r)) 133 | try: 134 | c = self.color_transform[color] 135 | self.color_ct2.configure(text=c[0], state='enable') 136 | except: 137 | self.color_ct2.configure(state='disabled') 138 | 139 | #清除识别数据,还原初始结果 140 | def clean(self): 141 | img_bgr3 = img_math.img_read("pic/hy.png") 142 | self.imgtk2 = self.get_imgtk(img_bgr3) 143 | self.image_ctl.configure(image=self.imgtk2) 144 | 145 | self.r_ct2.configure(text="") 146 | self.color_ct2.configure(text="", state='enable') 147 | #显示车牌颜色 148 | self.color_ct2.configure(background='white' ,text="颜色", state='enable') 149 | self.pilImage3 = Image.open("pic/locate.png") 150 | pil_image_resized = self.pilImage3.resize((200, 50), Image.ANTIALIAS) 151 | self.tkImage3 = ImageTk.PhotoImage(image=pil_image_resized) 152 | self.roi_ct2.configure(image=self.tkImage3, state='enable') 153 | 154 | 155 | if __name__ == '__main__': 156 | win = tk.Tk() 157 | 158 | ui_main= UI_main(win) 159 | # 进入消息循环 160 | win.mainloop() 161 | -------------------------------------------------------------------------------- /pic/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chao2020x/license-plate-recognition/1269a2def2b7d5fdd6d7c6f65d30e4f27078709a/pic/01.png -------------------------------------------------------------------------------- /pic/02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chao2020x/license-plate-recognition/1269a2def2b7d5fdd6d7c6f65d30e4f27078709a/pic/02.jpg -------------------------------------------------------------------------------- /pic/035.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chao2020x/license-plate-recognition/1269a2def2b7d5fdd6d7c6f65d30e4f27078709a/pic/035.png -------------------------------------------------------------------------------- /pic/hy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chao2020x/license-plate-recognition/1269a2def2b7d5fdd6d7c6f65d30e4f27078709a/pic/hy.png -------------------------------------------------------------------------------- /pic/locate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chao2020x/license-plate-recognition/1269a2def2b7d5fdd6d7c6f65d30e4f27078709a/pic/locate.png -------------------------------------------------------------------------------- /车牌识别.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chao2020x/license-plate-recognition/1269a2def2b7d5fdd6d7c6f65d30e4f27078709a/车牌识别.pptx --------------------------------------------------------------------------------