天天看點

基于樹莓派車牌識别門禁系統前言

基于樹莓派車牌識别門禁系統

  • 前言
    • 開發環境
    • 源碼
    • 硬體
    • 效果示範
    • 後記

前言

畢業設計想做一個集大學所自學過的所有語言,再加上嵌入式的完整應用類型。但由于疫情的原因,沒辦法完整的展現。不過中間還是自己一系列将前端、後端、前後端互動、資料庫以及Android控制都整了一遍,還是受益匪淺。在此将經驗分享出來順便自己也留戀一下,止增消遣。

開發環境

在最初,本來想直接在樹莓派上跑車牌識别的Python源碼,但由于樹莓派用的是3B型号的,要跑這種深度學習架構的東西記憶體不太夠,即使改了特征參數還是容易崩掉,最後實在沒辦法就采用PC做主要端、樹莓派做采集端的方案來做。後面發現這樣搞還容易加web端這些後續拓展,這也算是曲線救國哈哈

  1. 樹莓派 :3B加系統2019-09-26-raspbian-buster.img;
  2. PC :Visual studio2019;
  3. 資料庫: MySQL8.0版本;
  4. HTML :Adobe公司的DW;
  5. 前後端互動: Flask架構+ajax輪詢;
  6. Android :Android studio;
  7. 車牌識别算法 :OpenCV-4.1.0+Python3.7,具體參考大佬部落格車牌号識别 python + opencv;
  8. 深度學習訓練樣本 :Anaconda3-2019.10-Windows-x86_64+tensorflow,具體參考大佬部落格TensorFlow車牌識别完整版(含車牌資料集)。

源碼

連結:https://pan.baidu.com/s/11HsGVzaFizBd-yzvfqK9gw

提取碼:zxuv

具體的源碼請看百度網盤下載下傳,也在csdn裡扔了一份。這裡貼上一部分主要程式,可單獨運作。

PC識别主要端:

1.surface.py

# -*- coding: utf-8 -*-
import tkinter as tk
import urllib
from tkinter.filedialog import *
from tkinter import ttk
import predict
import cv2
from PIL import Image, ImageTk
import threading
import time
import numpy as np
import urllib.request
import socket
import MySQLdb

# 用于顯示的圖檔的路徑
IMAGE_PATH = '.\\pic\\chepai.jpg'
host = "192.168.43.161:8080"# 對應樹莓派上開啟的位址
if len(sys.argv)>1:
    host = sys.argv[1]
hoststr = 'http://' + host + '/?action=stream'
print('Streaming ' + hoststr)

stream=urllib.request.urlopen(hoststr)
bytes=b''

class Surface(ttk.Frame):
	pic_path = ""
	viewhigh = 600
	viewwide = 600
	update_time = 0
	thread = None
	thread_run = False
	camera = None
	color_transform = {"green":("綠牌","#55FF55"), "yello":("黃牌","#FFFF00"), "blue":("藍牌","#6666FF")}
		
	def __init__(self, win):
		ttk.Frame.__init__(self, win)
		frame_left = ttk.Frame(self)
		frame_right1 = ttk.Frame(self)
		frame_right2 = ttk.Frame(self)
		win.title("車牌識别")
		win.state("zoomed")
		self.pack(fill=tk.BOTH, expand=tk.YES, padx="5", pady="5")
		frame_left.pack(side=LEFT,expand=1,fill=BOTH)
		frame_right1.pack(side=TOP,expand=1,fill=tk.Y)
		frame_right2.pack(side=RIGHT,expand=0)
		ttk.Label(frame_left, text='原圖:').pack(anchor="nw") 
		ttk.Label(frame_right1, text='車牌位置:').grid(column=0, row=0, sticky=tk.W)
		
		from_pic_ctl = ttk.Button(frame_right2, text="來自圖檔", width=20, command=self.from_pic)
		from_vedio_ctl = ttk.Button(frame_right2, text="來自攝像頭", width=20, command=self.from_vedio)
		self.image_ctl = ttk.Label(frame_left)
		self.image_ctl.pack(anchor="nw")
		
		self.roi_ctl = ttk.Label(frame_right1)
		self.roi_ctl.grid(column=0, row=1, sticky=tk.W)
		ttk.Label(frame_right1, text='識别結果:').grid(column=0, row=2, sticky=tk.W)
		self.r_ctl = ttk.Label(frame_right1, text="")
		self.r_ctl.grid(column=0, row=3, sticky=tk.W)
		self.color_ctl = ttk.Label(frame_right1, text="", width="20")
		self.color_ctl.grid(column=0, row=4, sticky=tk.W)
		from_vedio_ctl.pack(anchor="se", pady="5")
		from_pic_ctl.pack(anchor="se", pady="5")
		self.predictor = predict.CardPredictor()
		self.predictor.train_svm()
		
	def get_imgtk(self, img_bgr):
		img = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
		im = Image.fromarray(img)
		imgtk = ImageTk.PhotoImage(image=im)
		wide = imgtk.width()
		high = imgtk.height()
		if wide > self.viewwide or high > self.viewhigh:
			wide_factor = self.viewwide / wide
			high_factor = self.viewhigh / high
			factor = min(wide_factor, high_factor)
			wide = int(wide * factor)
			if wide <= 0 : wide = 1
			high = int(high * factor)
			if high <= 0 : high = 1
			im=im.resize((wide, high), Image.ANTIALIAS)
			imgtk = ImageTk.PhotoImage(image=im)
		return imgtk
	
	def show_roi(self, r, roi, color):
		if r :

			roi = cv2.cvtColor(roi, cv2.COLOR_BGR2RGB)
			roi = Image.fromarray(roi)
			self.imgtk_roi = ImageTk.PhotoImage(image=roi)
			self.roi_ctl.configure(image=self.imgtk_roi, state='enable')
			self.r_ctl.configure(text=str(r))
			self.update_time = time.time()
			try:
				c = self.color_transform[color]
				self.color_ctl.configure(text=c[0], background=c[1], state='enable')
			except: 
				self.color_ctl.configure(state='disabled')
		elif self.update_time + 8 < time.time():
			self.roi_ctl.configure(state='disabled')
			self.r_ctl.configure(text="")
			self.color_ctl.configure(state='disabled')
		
	def from_vedio(self):
		if self.thread_run:
			return
		if self.camera is None:
			self.camera = cv2.VideoCapture(0)
			if not self.camera.isOpened():
				mBox.showwarning('警告', '攝像頭打開失敗!')
				self.camera = None
				return
		self.thread = threading.Thread(target=self.vedio_thread, args=(self,bytes))
		self.thread.setDaemon(True)
		self.thread.start()
		self.thread_run = True
		
	def from_pic(self):
		self.thread_run = False
		self.pic_path = askopenfilename(title="選擇識别圖檔", filetypes=[("jpg圖檔", "*.jpg")])
		if self.pic_path:
			img_bgr = predict.imreadex(self.pic_path)
			self.imgtk = self.get_imgtk(img_bgr)
			self.image_ctl.configure(image=self.imgtk)
			r, roi, color = self.predictor.predict(img_bgr)
			combine(r)
			self.show_roi(r, roi, color)
			

	@staticmethod
	def vedio_thread(self,bytes):
		self.thread_run = True
		predict_time = time.time()
		while self.thread_run:
			# _, img_bgr = self.camera.read()
			# self.imgtk = self.get_imgtk(img_bgr)
			# self.image_ctl.configure(image=self.imgtk)
			# if time.time() - predict_time > 2:
			# 	r, roi, color = self.predictor.predict(img_bgr)
			# 	self.show_roi(r, roi, color)
			# 	predict_time = time.time()
			bytes += stream.read(1024)
			a = bytes.find(b'\xff\xd8')
			b = bytes.find(b'\xff\xd9')
			if a != -1 and b != -1:
				jpg = bytes[a:b + 2]
				bytes = bytes[b + 2:]
				# flags = 1 for color image
				# 來自網頁的攝像頭資料
				frame = cv2.imdecode(np.fromstring(jpg, dtype=np.uint8), flags=1)
				self.imgtk = self.get_imgtk(frame)
				self.image_ctl.configure(image=self.imgtk)
				if time.time() - predict_time > 2:
					r, roi, color = self.predictor.predict(frame)
					if len(r):
						print('OK')
						combine(r)
						cv2.imwrite(IMAGE_PATH, frame)	# 儲存目前識别的圖檔
						image = cv2.imread(IMAGE_PATH)
						cv2.imshow('img', image)
						self.show_roi(r, roi, color)  
						cv2.waitKey()
						
					predict_time = time.time()
		print("run end")
		
		
def close_window():
	print("destroy")
	if surface.thread_run :
		surface.thread_run = False
		surface.thread.join(2.0)
	win.destroy()

def combine(r):
	# 把數組中的字組合起來
	result = ""
	for data in r:
		result += data
	send_message(result)
	send_mysql(result)

def send_message(result):
	try:
		print('result:', result)
		if result:
			client.send(result.encode(encoding='utf-8'))  # 不能發空的東西,需要變成utf-8編碼形式
			print('OK')
	except Exception:
		client.close()

def send_mysql(result):
	if result:
		print('OK5')
		#連接配接
		cxn = MySQLdb.Connect(host = '127.0.0.1', port = 3306, user = 'root', passwd = '12345678', charset="utf8")
		#遊标
		cur = cxn.cursor()
		cur.execute("USE che1") #首先得運作mysql.py 或者直接在MySQL上建立表
		#查詢
		cur.execute("SELECT * FROM users where name='{}'".format(result))#定義查詢

		items =cur.fetchall() #擷取查詢到資料
		#将結果集強轉為list
		items = list(items)
		fw=open('F:\\python\\web端\\flaskweb\\flaskweb\\web.txt','w') #對應web端的直接路徑
		for t in items:
			fw.write(' '.join(str(s) for s in t) + '\n')
		fw.close()
		#print("儲存檔案成功")
		#關閉
		cur.close()#關閉遊标
		cxn.commit()#送出事務
		cxn.close()#釋放資料庫資源在這裡插入代碼片


if __name__ == '__main__':
	client = socket.socket()  # 有一些預設參數,即可使用ipv4,這一句是聲明socket類型和傳回socket連接配接對象
	client.connect(("192.168.43.161", 12348))
	win = tk.Tk()
	surface = Surface(win)
	win.protocol('WM_DELETE_WINDOW', close_window)
	win.mainloop()
           

2.predict.py參照之前大佬部落格,定位矯正切割識别這部分就改參數,其他不改動

3.mysql.py

import MySQLdb

#連接配接
cxn = MySQLdb.Connect(host = '127.0.0.1', port = 3306, user = 'root', passwd = '12345678', charset="utf8")
#遊标
cur = cxn.cursor()

try:
    cur.execute("DROP DATABASE che1")
except Exception as e:
    print(e)
finally:
    pass

#建立資料庫
cur.execute("CREATE DATABASE che1")
cur.execute("USE che1")

#建立表
cur.execute("CREATE TABLE users (id INT, name VARCHAR(255), username VARCHAR(255), sex VARCHAR(10), birth TIMESTAMP, telephone VARCHAR(20))")
#插入
cur.execute("INSERT INTO users VALUES(1, '閩A77518', '李明', '男', '1993-06-05 15:20:00', 13758546621),(2, '皖A87271', '張三', '女', '1993-04-03', 15487596721)")
#查詢
cur.execute("SELECT * FROM users where name='閩A77518'")
for row in cur.fetchall():
    print('%s\t%s\t%s\t%s\t%s\t%s' %row)

#關閉
cur.close()
cxn.commit()
cxn.close()
           

web端

1.flaskweb.py

# _*_ coding:utf-8 _*_

import cv2
from flask import Flask, render_template

# 用于顯示的圖檔的路徑
IMAGE_PATH = '.\\static\\img\\chepai.jpg'

app = Flask(__name__)	#程式執行個體是Flask的對象,一般情況下用如下方法執行個體化,Flask類隻有一個必須指定的參數,即程式主子產品或者包的名字,__name__是系統變量,該變量指的是本py檔案的檔案名"""


@app.route("/")	#當與前端約定好路由接口路徑時會自動執行下方的index函數
def index():
	
	return render_template("index.html")	#将參數傳回到index.html裡

@app.route("/charts/")
def charts():
	
	return render_template("charts.html")

@app.route("/faq/")
def faq():
	
	return render_template("faq.html")

@app.route("/grid/")
def grid():
	
	return render_template("grid.html")

@app.route("/test",methods=['GET'])	#與ajax方法的url協定統一路徑,使用get無加密方法
def test():
	fw=open('web.txt','r')
	da = fw.read()   #讀取檔案
	#print(d)
	if da.strip()=='':
		da=('資料庫中未有資訊')
		#print(d)
	return da

if __name__ == '__main__':
	app.run(
      host='192.168.43.161',	#本地IP
      port= 2222,	#端口号
    )
           

2.同目錄下templates檔案夾下html

<!DOCTYPE html>
<html lang="en"><head>
    <meta charset="utf-8" />
    <title>車牌識别門禁系統</title>
    
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
    <meta name="apple-mobile-web-app-capable" content="yes" />    
    
    
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/bootstrap-responsive.min.css') }}">
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/font-awesome.css') }}">
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/adminia.css') }}">
    
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/bootstrap.min.css') }}">
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/pages/plans.css') }}">
    
    
    

  <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
  <!--[if lt IE 9]>
      <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
    <![endif]-->
	
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  	<script language="javascript">
		//頁面加載調用
        window.onload=function(){
         //每1秒重新整理時間
        setInterval("NowTime()",1000);
            }

		function NowTime(){
                var myDate=new Date();
                var y = myDate.getFullYear();
                var M = myDate.getMonth()+1;     //擷取目前月份(0-11,0代表1月)
                var d = myDate.getDate();        //擷取目前日(1-31)
                var h = myDate.getHours();       //擷取目前小時數(0-23)
                var m = myDate.getMinutes();     //擷取目前分鐘數(0-59)
                var s = myDate.getSeconds();     //擷取目前秒數(0-59)
                
                //檢查是否小于10
                M=check(M);
                d=check(d);
                h=check(h);
                m=check(m);
                s=check(s);
                var timestr = y+"-"+M+"-"+d+" "+h+":"+m+":"+s;
                document.getElementById("nowtime").innerHTML="目前時間:" + timestr;
            }
            //時間數字小于10,則在之前加個“0”補位。
            function check(i){
                var num = (i<10)?("0"+i) : i;
                return num;
            }
	</script>
  </head>

<body>
	
<div class="navbar navbar-fixed-top">
	
	<div class="navbar-inner">
		
		<div class="container">
			
			<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse"> 
				<span class="icon-bar"></span> 
				<span class="icon-bar"></span> 
				<span class="icon-bar"></span> 				
			</a>
			
			<a class="brand" href="./">車牌識别門禁系統web端</a>
			
		</div> <!-- /container -->
		
	</div> <!-- /navbar-inner -->
	
</div> <!-- /navbar -->




<div id="content">
	
	<div class="container">
		
		<div class="row">
			
			<div class="span3">
				
				<div class="account-container">
				
					<div class="account-avatar">
						<img src="{{ url_for('static', filename='img/headshot.png') }}" alt="" class="thumbnail" />
					</div> <!-- /account-avatar -->
				
					<div class="account-details">
					
						<span class="account-name">引觞垂月</span>
						
						<span class="account-role">人間清歡不覺淡 誰知其味漫</span>
					
					</div> <!-- /account-details -->
				
				</div> <!-- /account-container -->
				
				<hr />				
				<ul id="main-nav" class="nav nav-tabs nav-stacked">	
					<li>
						<a href="./">
							<i class="icon-home"></i>
							首頁 			
						</a>
					</li>
					<li>
						<a href="/charts/">
							<i class="icon-signal"></i>
							圖檔	
						</a>
					</li>
					<li>
						<a href="/faq/">
							<i class="icon-th-list"></i>
							視訊
						</a>
					</li>
					<li>
						<a href="/grid">
							<i class="icon-th-large"></i>
							曆史文學	
						</a>
					</li>
					<li>	
							<i class="icon-home"></i>
							目的:			
					</li>
				</ul>	
				<hr />
				
				<div class="sidebar-extra">
					<p>将車牌識别結果導入資料庫裡查詢車主資訊</p>
				</div> <!-- .sidebar-extra -->
				<br />
		
			</div> <!-- /span3 -->
			
			
			
			<div class="span9">
				
				<h1 class="page-title">
					<i class="icon-th-list"></i>
					車主資訊					
				</h1>
				
				
				
				
				
				
				<div class="widget">
					
					<div class="widget-header">
						<h3>對應資訊為:	序号 車牌号 車主名  性别     出生時間      聯系方式</h3>
					</div> <!-- /widget-header -->
														
					<div class="widget-content">
						
						<div class="pricing-plans plans-3">
							<div id="ajaxDiv" style="margin-top: 20px"></div>
	
						</div> <!-- /pricing-plans -->
										
					</div> <!-- /widget-content -->
					
				</div> <!-- /widget -->
				
				
				
				
				<div class="widget">
					
					<div class="widget-header">
						<h3>時間</h3>
					</div> <!-- /widget-header -->
														
					<div class="widget-content">
						
						<div id="nowtime">在這裡顯示時間</div>
					</div> <!-- /widget-content -->
					
				</div> <!-- /widget -->
				
				
				
			</div> <!-- /span9 -->
			
			
		</div> <!-- /row -->
		
	</div> <!-- /container -->
	
</div> <!-- /content -->


<!-- Le javascript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="{{ url_for('static', filename='js/jquery-1.7.2.min.js') }}"></script>

<script src="{{ url_for('static', filename='js/bootstrap.js') }}"></script>

  </body>
</html>



<script type="text/javascript">
	setInterval(function(){
		$.ajax({
			type:"get",
			dataType:'text',
			url:"/test",
			cache:false,
			success:function(data){
				$("#ajaxDiv").html(data);
			},
			error:function(){
				 alert("失敗,請稍後再試!");
			}
		});
	},1000);

</script>

           

樹莓派上運作rpi.py

import socket
import tkinter as tk
import threading
import MySQLdb
import RPi.GPIO
import time

time_out=5
RELAY=18

def get_message(conn):
    while True:
        data = conn.recv(1024)
        #if not data :
            #print('this user is end,exit!\n next user')
            #break
        if data:
            print('data:',data.decode())
            var.set(data.decode())
            l = tk.Label(win,textvariable=var,font=('Arial', 30),width=30,height=10).pack(side='right')#         
            
            #連接配接
            cxn = MySQLdb.Connect(host = '127.0.0.1', port = 3306, user = 'root', passwd = '123456', charset="utf8")
            #遊标
            cur = cxn.cursor()
            cur.execute("USE che1")
            #查詢
            cur.execute("SELECT * FROM users where name='{}'".format(data.decode()))#定義查詢
            d=cur.fetchall()  #fetchall:接收全部的傳回結果行
            if len(d)>0:
                RPi.GPIO.setmode(RPi.GPIO.BCM)
                RPi.GPIO.setup(RELAY,RPi.GPIO.OUT)
                RPi.GPIO.output(RELAY,RPi.GPIO.HIGH)
                time.sleep(time_out)
                RPi.GPIO.output(RELAY,RPi.GPIO.LOW)
                time.sleep(time_out)
                RPi.GPIO.cleanup()
            #關閉
            cur.close()
            cxn.commit()
            cxn.close()

server = socket.socket()
server.bind(("192.168.43.197",12348)) #綁定要監聽的端口port
server.listen(5) # 監聽,這裡表示最多有5個用戶端連接配接伺服器,python2不好使
print('waiting the call')
conn,addr = server.accept() # 等電話打進來,每個conn代表一個用戶端的連接配接
print(conn)
print('the call has comming')

thread = threading.Thread(target = get_message, args = (conn, ))
thread.start()

win = tk.Tk()
win.title('chepai')
win.geometry('960x800')
tk.Label(win, text='車牌号',font=('Arial', 30),width=30,height=10).pack(side='left')#左
var = tk.StringVar()

win.mainloop()
           

Android通過tcp/ip控制gpio進而控制繼電器,源碼就不貼了,需要的可以去連結下了看看

硬體

1、Raspberry Pi 3B 嵌入式開發闆(系統燒寫、配置、驅動)

2、7 寸 LCD 觸摸屏

3、CSI 攝像頭子產品(用USB攝像頭也行)

4、繼電器子產品

效果示範

具體效果視訊是當時線上答辯示範時拍的,放在b站上。由于懶寫論文就沒加Android手動控制,反正也無傷大雅 示範視訊.

就後面增加的Android就是類似聊天框通信的那種,很簡單界面布置,就當測試用。切記,本人比較懶,IP寫死了,需要同一區域網路下。

界面如下

基于樹莓派車牌識别門禁系統前言

後記

前後大概用了20多天的時間,後面會陸續将每天的工作日記寫上來。如果有什麼錯誤或者問題,我應該大部分都有遇到,希望能解決各位心中的疑惑。本來想做到伺服器上的,可惜騰訊雲太坑了硬是域名解析不了,雖說這個東西隻需要到本地區域網路就可以了但還是很想過個瘾,隻能說小小的遺憾咯~

最後,七夕到處浪,寫部落格它不香嗎

人間清歡不覺淡,誰知其味漫,各位七夕快樂!

繼續閱讀