車牌圖像識别關鍵技術研究
文豔格
長安大學工程機械學院 公開處刑抄我文章不加引用

因為店家隻給了我兩頁紙的vc0706通信協定,許多細節我還是不清楚,寫得也沒有特别優雅,大家就湊合看吧。
目錄
1 硬體裝置
2 serial安裝
3 實作序列槽通信
3.1 發現端口
3.2 發送指令
3.2.1 協定格式
3.2.2 serial傳送的方式
3.3 擷取版本号(hello world)
3.4 複位
3.5 照相
3.5.1 停止目前幀重新整理
3.5.2 獲娶圖檔長度
3.5.3 恢複幀更新
3.5.4 拍照
4 反思
1 硬體裝置
- TTL序列槽攝像頭(VC0706)
- USB轉TTL燒錄器
2 serial安裝
第一次安裝的是serial的包導包的時候發現下載下傳錯了,正确應該是pyserial。安裝後直接import就可以了。
3 實作序列槽通信
3.1 發現端口
Windows下為COM(N, N=1、2...), Ubuntu下為‘
/dev/ttyS0
’
。
Windows初學者,可以給您一下兩種方式确定端口号。
方法一:輸入在終端(cmd)中輸入
python -m serial.tools.list_ports
輸出結果:
COM5
1 ports found
方法二:搜尋電腦上的裝置管理器,打開以後然後插入燒錄器,自動就會彈出。如果沒有彈出就可能是驅動沒有安裝,安裝好以後不好使,重新開機一下電腦,到了工作的時候大家都知道程式員會跟你說,你重新開機一下,清一下緩存,這兩句話。也有可能是驅動安裝的不對。
方法三:直接找一個有端口掃描的上位機,點選掃描就可以了。大部分上位機都是你一插進去就會檢測到你的端口。
----->
注意:當序列槽被占用的時候也有可能導緻失敗,例如你在編譯器有兩個程序運作下面的測試代碼,第二個程序就會因為端口占用而失效。也有的上位機是因為同時打開了兩個上位機的緣故(實驗課的時候同學遇到過情況),可以用任務管理器kill掉。
測試:
import serial
#Windows
ser = serial.Serial(port='COM5', baudrate=115200, timeout=0.5)
print(ser.name)
控制台列印結果:
COM5
Process finished with exit code 0
建立ser對象的代碼,不管你是window還是linux都可以不會報錯:
class PicSerial:
__ser = None # ser的單例
__isinit = False
@staticmethod
def get_available_port():
"""
檢測可以使用的端口号
:return->str: 端口号的名稱
"""
port = list(list_ports.comports())
if len(port) > 0:
port_name = port[0].device
print(port_name)
return port_name
# logging.info("Available port:", ports)
else:
print("There is no available port.")
# logging.error("There is no available port.")
def __new__(cls, *args, **kwargs):
if PicSerial.__ser is None:
cls.__ser = object.__new__(cls)
return cls.__ser
def __init__(self):
if not PicSerial.__isinit:
self.sername = self.get_available_port()
self.ser = serial.Serial(port=self.sername, baudrate=BAUDRATE)
PicSerial.__isinit = False
print("PicSerial init.")
3.2 發送指令
3.2.1 協定格式
3.2.2 serial傳送的方式
serial傳送的方式有:
- 串行端口對象。
- 隻傳單個位元組。
- 字元串。
- 位元組數組+位元組數組長度。
是以直接選用數組傳資料,這裡會遇到一個問題就是python的list會自動把十六進制數轉換為整形。
是以要進行轉換你可以直接寫成b“/x56/x00/x17/x00”。假如你不需要傳十進制也可以轉成list,直接map(chr,x)或map(ord,x)也是可以的。讀的時候也要注意隻要你放進list裡面就會自動轉成整形。
【我覺得這樣寫很降智,但是又不得不這樣寫】
#在PicSerial中
def isreply(self, cmd: bytes, option: str) -> bool:
"""
檢測是否有回複
:return:回複的内容
:param cmd:
:param option:
:return: True則有回複
"""
if isinstance(cmd, bytes) and isinstance(option, str) and len(cmd) > 0 and len(option) > 0:
self.ser.write(cmd)
reply = self.ser.read(4)
reply = list(map(chr, list(reply)))
print("49h,The function'{}' is running. reply:{}".format(sys._getframe().f_code.co_name, reply))
if len(reply) >= 4 and reply[0] == 'v' and reply[1] == SERIAL_NUM and reply[2] == option and reply[3] == STATUS:
return True
return False
測試:
#在test檔案中
class TestSerial(unittest.TestCase):
def test_isreply(self):
self.assertTrue(ser.isreply(GET_VERSION_CMD, VERSION))
self.assertFalse(ser.isreply('\x56\x00\x11\x00', VERSION))
self.assertFalse(ser.isreply(GET_VERSION_CMD, b'\x11'))
self.assertFalse(ser.isreply(123456, b'\x11'))
self.assertFalse(ser.isreply('', VERSION))
self.assertFalse(ser.isreply(b'', VERSION))
self.assertFalse(ser.isreply(GET_VERSION_CMD, ''))
self.assertFalse(ser.isreply(GET_VERSION_CMD, None))
self.assertFalse(ser.isreply(b'', ''))
self.assertFalse(ser.isreply(b'\x56\x00\xAA\x00', VERSION))
self.assertFalse(ser.isreply(GET_VERSION_CMD, '\xAA'))
#之後就省略不寫了
if __name__ == '__main__':
unittest.main()
結果:
3.3 擷取版本号(hello world)
按照協定一步一步操作
主 機 發: 56 00 11 00 攝像頭回: 76 00 11 00 0B 56 43 30 37 30 33 20 31 2E 30 30 (VC0703 1.00)
#在PicSerial中
def getversion(self) -> str:
"""
擷取版本号
:return:
"""
cmd = GET_VERSION_CMD
option = VERSION
if self.isreply(cmd, option):
left = self.ser.readall()
print("75h,The function'{}' has responded.left{}".format(sys._getframe().f_code.co_name, left))
return self.ser.read(12).decode()[1:]
測試:
#在test檔案中
def test_getversion(self):
self.assertEqual(ser.getversion(), 'VC0703 1.00')
結果:通過測試
3.4 複位
主 機 發: 56 00 26 00 攝像頭回: 76 00 26 00 00
#在PicSerial中
def reset(self):
"""
複位
:return:
"""
cmd = REST_CMD
option = RESET
if self.isreply(cmd, option):
if self.ser.read(1) == b'':
left = self.ser.readall()
print("75h,The function'{}' has responded.left{}".format(sys._getframe().f_code.co_name, left))
return True
return False
*測試和運作結果不一樣。
花了一點時間找到原因了,單元測我都是點選前面綠色的小箭頭,以為隻是運作目前的測試函數的内容,但是我發現它把其他的函數都運作了。是以要把之前的測試函數注釋掉得到的結果就一樣了。
測試通過。
3.5 照相
- 停止目前幀重新整理
- 獲娶圖檔長度
- 擷取圖檔
- 恢複幀更新
3.5.1 停止目前幀重新整理
這一步每一次拍照前必須執行一次。因為讀照片指令的時候會出現麻煩。這一步是有意義的,就是當你發現圖檔很大,的時候正常大小就兩個byte可以表示完了(排除你的圖檔面積十分大或十厘清晰),又或者是出現拍照内容為空的情況。假如數值非常的大,可以使用該函數,再不行就要選擇複位。
def stoprefresh(self):
"""
停止重新整理目前幀
:return:
"""
cmd = STOP_REFRESH_CMD
option = TAKE_PHOTO
self.ser.write(cmd)
if self.isreply(cmd, option) and self.ser.read(1) == b"\x00":
left = self.ser.readall()
print("87h,The function'{}' has responded.left{}".format(sys._getframe().f_code.co_name, left))
return True
return False
通過測試
def test_stoprefresh(self):
self.assertTrue(ser.stoprefresh())
3.5.2 獲娶圖檔長度
此時讀完後還是要小心會有後序的内容沒有讀完也會影響後序的讀buffer。
def getlength_bytes(self) -> bytes:
"""
擷取圖檔的長度
:return:
"""
cmd = GET_LENGTH_CMD
option_pic = '4'
self.ser.write(cmd)
if self.isreply(cmd, option_pic):
if self.ser.read(1) == b'\x04':
res = self.ser.read(4)
left = self.ser.readall()
print("103h,The function'{}' has responded.left{}".format(sys._getframe().f_code.co_name, left))
return res
return b'\x00\x00\x00\x00'
測試通過
def test_getlength(self):
self.assertEqual(ser.getlength(), b'\x00\x00\x12\x34')
3.5.3 恢複幀更新
def recover_refresh(self):
"""
恢複幀重新整理
:return:
"""
cmd = RECOVER_REFRESH_CMD
option = TAKE_PHOTO
self.ser.write(cmd)
if self.isreply(cmd, option):
# 讀出剩餘的位元組
left = self.ser.readall()
print("142h,The function'{}' has responded.left{}".format(sys._getframe().f_code.co_name, left))
return True
return False
測試并通過:
def test_recover_refresh(self):
self.assertTrue(ser.recover_refresh())
3.5.4 拍照
在這裡卡了很長時間,不知道為什麼長度是不确定的,每一次讀的長度都沒讀完,看代碼。
下面代碼隻是示範
#下面代碼隻是示範不在最終版本中
def savephoto(self, cmd, option, len):
"""
儲存圖檔
:param cmd:
:param option:
:param len: 照片的長度
:return:
"""
with open('write_pic/serialpic/photo.jpg', 'wb') as f:
if self.isreply(cmd, option):
print(self.ser.read(1))
countofread_complete_byte = 0 # 用于計算目前已經寫入的長度
while countofread_complete_byte != len + 10:
# read()是有上限的,不可以把全部都讀取
lines = self.ser.read(len + 10 - countofread_complete_byte)
countofread_complete_byte += lines.__len__()
f.write(lines)
print("142h,countofread_complete_byte:", countofread_complete_byte, "lines", lines.__len__())
left = self.ser.readall()
print("146h,少讀内容:", left, "共", left.__len__(), "個位元組")
res = self.ser.readall()
print(res)
現象:
- 現象是運作一直不停都是手動stop console,或者沒有stop console會一直列印lines為空,就此可以猜測read()不是阻塞的。
- 是圖檔位元組總數不斷增多。
- 每次周遊完後滿足self.ser.read(len + 10 - countofread_complete_byte)後再readall()還是有剩餘的内容。
我發現此時readall一共讀出了4049個位元組,圖檔資料4030個位元組+首尾兩部分共10個位元組,那多出來的9個位元組是什麼火眼金睛的Unyielding ● L發現了正确的開始位置為上圖紅色方塊處,碰巧多出來的是九個位元組,是以多出來的就不是這一張圖檔的内容,是以可以猜想程式沒有停止的原因是上一次圖檔還沒讀完我就手動停止,是以留下了資料,上一次沒有讀完的内容,這一次讀到了。
位元組串和字元串都可以切片。直接切出來儲存。
def getphoto(self):
"""
拍照并且儲存圖檔
:return:
"""
# self.reset()
# 1、停止幀重新整理
# self.stoprefresh()
# 擷取圖檔長度
# 傳回位元組長度用于整合指令,表示圖檔的總位元組數
length = self.getlength_bytes()
# 傳回整形,表示圖檔的總位元組數
len = self.bytesToInt(length)
print("158h,len:", len)
# 拍照
cmd = GET_PHOTO_START_CMD + length + GET_PHOTO_END_CMD
print("159hcmd", cmd)
self.ser.write(cmd)
readall = self.ser.readall()
readall_len = readall.__len__()
differ = readall_len - len - 10
if differ != 0:
res = readall[differ + 5:-5]
print("172h:", res)
self.savephoto(res)
else:
res = readall[5:-5]
print("175h:", res)
self.savephoto(res)
# 關閉序列槽
self.ser.close()
# 恢複重新整理
# self.recover_refresh()
成功輸出結果:
為了友善debug,停幀回複幀都是手動發送的,剩下的問題就是把注釋打開試一試能不能成功組合成一個函數,發現有的指令會讀空,是以可以推斷:一定又是前一個指令裡面又留下來什麼還沒有被讀取的位元組造成讀到的内容篡位了。
每一次執行完指令後看一看還有沒有遺留位元組,把剩餘位元組都取出來,然後differ的判斷都不需要了。【讀者看到的代碼都是最新版本的,此處我添加了left和print到對應函數中】,處理結果列印到控制台:
def getphoto(self):
"""
拍照并且儲存圖檔
:return:
"""
# 1、停止幀重新整理
self.stoprefresh()
# 擷取圖檔長度
# 傳回位元組長度用于整合指令,表示圖檔的總位元組數
length = self.getlength_bytes()
# 傳回整形,表示圖檔的總位元組數
len = self.bytesToInt(length)
print("161h,The function'{}' is running. len:{}".format(sys._getframe().f_code.co_name, len))
# 拍照
cmd = GET_PHOTO_START_CMD + length + GET_PHOTO_END_CMD
print("165h,The function'{}' is running. cmd:{}".format(sys._getframe().f_code.co_name, cmd))
self.ser.write(cmd)
readall = self.ser.readall()
readall_len = readall.__len__()
differ = readall_len - len - 10
if differ != 0:
res = readall[differ + 5:-5]
print("161h,The function'{}' is running. res:{}".format(sys._getframe().f_code.co_name, res))
self.savephoto(res)
else:
res = readall[5:-5]
print("161h,The function'{}' is running. res:{}".format(sys._getframe().f_code.co_name, res))
self.savephoto(res)
# 恢複重新整理
self.recover_refresh()
輸出圖檔結果(拍的是導線沒有聚焦)
# 這個測試應該怎麼寫? 有圖檔就輸出并且可以打開就可以了惹?有人能教教我?
def test_getphoto(self):
pass
更新2月13日
5 連續兩天解決運作不停止
通過無數種方式列印各種資訊,納悶了沒有錯啊,一個程序,單線程,都列印了saosao程序結束了啊!!沒有沒關閉的程序或者是線程,with不是自帶close嗎?我不放心也加了close(),我調用完getphoto以後連序列槽都關閉了,到底是什麼地方需要輸入?明明沒有一個地方需要輸入的地方。最後我做了一個大膽的猜想,我建立了一個demo.py試一下運作一句helloworld發現helloworld也是停不了,我猜想是pycharm自帶輸入導緻程式不停止,果然我解決了這個問題,我的九尾狐奶奶太惡毒了!
4 反思
- 犯了很多‘我覺得’的錯誤,我覺得這個值是什麼,多打斷點看清楚,那一段示範代碼裡面因為協定寫了是五個位元組,isreply我已經讀了4個位元組再讀一個一定是0x00,後來列印那一行傳回的值是0x04這才為猜想讀到上一張圖作下鋪墊。
- 當你寫的很複雜超過20行的邏輯代碼就知道一定是錯了。--UnyieldingL
- 編碼方面的内容耗費了很長時間,就在反思的時候發現了decode("hex")。惹?
-
list='aabbccddee' hexer=list.decode("hex") print hexer
- 列印日志要詳細。 每一個變量涉及的變量長度函數名,此時哪個函數運作。
- 還有一個問題,pycharm還是沒有自己停止,列印某個線程的堆棧。
- 當你發現無論發了什麼的指令read的時候都是空就是表示沒有恢複幀重新整理,相反發現無論發了什麼指令read都是亂七八糟的東西就是沒有停止幀重新整理。
- 耗時兩天的“bug”都是出于自己對pycharm不熟悉惹的禍,pycharm更新的太快了甚至有點不熟悉,尤其是debug step over和step into經常混淆
python實作序列槽通信1 硬體裝置2 serial安裝3 實作序列槽通信5 連續兩天解決運作不停止4 反思5 鳴謝 。大膽猜想小心求證。python實作序列槽通信1 硬體裝置2 serial安裝3 實作序列槽通信5 連續兩天解決運作不停止4 反思5 鳴謝
5 鳴謝
技術指導:UnyieldingL
程式媛鼓勵師:風向晚。