一区二区三区三上|欧美在线视频五区|国产午夜无码在线观看视频|亚洲国产裸体网站|无码成年人影视|亚洲AV亚洲AV|成人开心激情五月|欧美性爱内射视频|超碰人人干人人上|一区二区无码三区亚洲人区久久精品

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

樹莓派項(xiàng)目實(shí)戰(zhàn):車牌識(shí)別系統(tǒng)開發(fā)全記錄!

上海晶珩電子科技有限公司 ? 2025-06-11 17:22 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

介紹

本項(xiàng)目的目標(biāo)是設(shè)計(jì)一個(gè)基于樹莓派電腦的自動(dòng)車牌識(shí)別系統(tǒng),用于控制停車場(chǎng)的道閘。

為什么?

我有一臺(tái)閑置的樹莓派,沒有參與任何項(xiàng)目,還有一臺(tái)攝像頭,以及一個(gè)潛在的問題點(diǎn)——辦公室停車場(chǎng)沒有自動(dòng)化的道閘控制系統(tǒng)。那么,為什么不利用這些設(shè)備來做一個(gè)有趣的項(xiàng)目呢?

本項(xiàng)目的目的并不是要?jiǎng)?chuàng)建一個(gè)生產(chǎn)就緒、穩(wěn)定且具有競(jìng)爭(zhēng)力的解決方案,而是要在使用有限設(shè)備解決實(shí)際問題的過程中享受樂趣,并創(chuàng)建一個(gè)可工作的產(chǎn)品。之后,還可以進(jìn)一步優(yōu)化這個(gè)解決方案,使其在輕量級(jí)邊緣設(shè)備上運(yùn)行得更快。

總體思路是使用樹莓派攝像頭以一定頻率拍照,處理圖像,檢測(cè)車牌,識(shí)別字符,并與數(shù)據(jù)庫(kù)中的允許車牌列表進(jìn)行比較。如果匹配,道閘將打開。

在基礎(chǔ)階段,我們將使用以下工具:

圖像源:樹莓派攝像頭模塊v2

車牌檢測(cè)器:使用PyTorch的YOLO v7

光學(xué)字符識(shí)別(OCR):EasyOCR

“數(shù)據(jù)庫(kù)”:Google表格中的表格

所有處理任務(wù)和計(jì)算都應(yīng)在樹莓派4B上本地執(zhí)行,解決方案必須能夠自主運(yùn)行。

9e85c5ce-46a5-11f0-986f-92fbcf53809c.png

基礎(chǔ)版本的簡(jiǎn)化流程圖

樹莓派將“近乎實(shí)時(shí)”地從攝像頭連續(xù)讀取幀。然后,使用在自定義數(shù)據(jù)集上微調(diào)的YOLOv7模型檢測(cè)車牌區(qū)域。之后,如果需要,對(duì)圖像進(jìn)行預(yù)處理,然后EasyOCR模型將從裁剪后的幀中檢測(cè)車牌號(hào)碼。然后檢查車牌字符串是否與“數(shù)據(jù)庫(kù)”中存儲(chǔ)的任何車牌匹配,并執(zhí)行相應(yīng)的操作。使用樹莓派的GPIO(通用輸入輸出)控制的繼電器開關(guān),我們可以連接停車道閘和任何附加負(fù)載,如燈光等。

GPIO引腳還允許連接輸入傳感器(如紅外、被動(dòng)紅外傳感器),并在檢測(cè)到車輛時(shí)觸發(fā)攝像頭。

再次強(qiáng)調(diào),這個(gè)問題可以通過多種方式解決,也許其中一些方式在某些要求和使用場(chǎng)景下會(huì)更高效、更簡(jiǎn)單。例如,所有繁重的處理都可以在云端進(jìn)行;我們可以使用基于GPU的邊緣設(shè)備;可以使用其他模型;使用ONNX、TFLite等進(jìn)行部署。但這個(gè)項(xiàng)目是作為一個(gè)實(shí)驗(yàn)來完成的,使用的是我目前擁有的設(shè)備,而且我并沒有尋找簡(jiǎn)單的方法。

環(huán)境設(shè)置

硬件設(shè)計(jì)

必要的硬件:

攝像頭:樹莓派攝像頭模塊v2

邊緣設(shè)備:樹莓派4 Model B 4GB

SD卡(>8GB)

電源:5V 3A USB-C

9ea298e8-46a5-11f0-986f-92fbcf53809c.png

開始時(shí)的設(shè)備:帶攝像頭模塊的樹莓派

附加設(shè)備:

散熱片、散熱風(fēng)扇

UPS

顯示器

繼電器/樹莓派HAT:用于控制外部設(shè)備(道閘)

攝像頭支架(“獨(dú)特的金屬線支架” )

*最好使用具有合適刷新時(shí)間的TFT或OLED屏幕,但當(dāng)時(shí)我只有這個(gè)。

進(jìn)行中的設(shè)備:帶散熱外殼的樹莓派 + 攝像頭模塊V2 + UPS + 電子墨水屏

9eb0082a-46a5-11f0-986f-92fbcf53809c.png

設(shè)置步驟

由于我決定使用PyTorch構(gòu)建解決方案,而PyTorch只提供Arm 64位(aarch64)的pip包,因此我們需要安裝64位的操作系統(tǒng)(Debian版本:11——“Bullseye”)。最新的arm64樹莓派操作系統(tǒng)可以從官方網(wǎng)站下載,并通過rpi-imager安裝。


完成安裝后,應(yīng)該如下所示:

9ec98598-46a5-11f0-986f-92fbcf53809c.png

將SD卡插入樹莓派并啟動(dòng)后,應(yīng)進(jìn)行以下調(diào)整:

編輯/boot/config.txt文件以啟用攝像頭。

# This enables the extended features such as the camera.start_x=1# This needs to be at least 128M for the camera processing, if it's bigger you can just leave it as is.gpu_mem=128# You need to commment/remove the existing camera_auto_detect line since this causes issues with OpenCV/V4L2 capture.#camera_auto_detect=1

此外,你可能還需要通過raspi-config或GUI啟用I2C、SSH和VNC。

樹莓派配置設(shè)置如下:

9edd3dea-46a5-11f0-986f-92fbcf53809c.png

安裝依賴

我使用了Python 3.9和3.10版本,據(jù)報(bào)道,在某些情況下3.11版本的速度明顯更快,但目前還沒有穩(wěn)定的PyTorch 3.11版本。

通過pip包管理器使用requirements.txt文件安裝所有必要的庫(kù)和模塊:

matplotlib>=3.2.2numpy>=1.18.5opencv-python==4.5.4.60opencv-contrib-python==4.5.4.60Pillow>=7.1.2PyYAML>=5.3.1requests>=2.23.0scipy>=1.4.1torch>=1.7.0,!=1.12.0torchvision>=0.8.1,!=0.13.0tqdm>=4.41.0protobuf<4.21.3tensorboard>=2.4.1pandas>=1.1.4seaborn>=0.11.0easyocr>=1.6.2

如果你是手動(dòng)安裝或在現(xiàn)有環(huán)境中實(shí)現(xiàn)(請(qǐng)不要這樣做 :) ),請(qǐng)注意當(dāng)前OpenCV版本存在一些問題,為了正常工作,我們需要安裝精確版本4.5.4.60。

你可以使用pip list檢查是否已正確安裝所有包:

9eed2bd8-46a5-11f0-986f-92fbcf53809c.png

好了,我們已經(jīng)設(shè)置了硬件和環(huán)境,現(xiàn)在可以開始編碼了。

軟件設(shè)計(jì)

圖像捕獲

對(duì)于圖像捕獲,我們將使用OpenCV來流式傳輸視頻幀,而不是使用標(biāo)準(zhǔn)的picamera庫(kù),因?yàn)樗?4位操作系統(tǒng)上不可用,而且速度較慢。OpenCV直接訪問/dev/video0設(shè)備來捕獲幀。

自定義的OpenCV攝像頭讀取簡(jiǎn)單包裝器:

classPiCamera(): def__init__(self, src=0, img_size=(640,480), fps=36, rotate_180=False): self.img_size = img_size self.fps = fps self.cap = cv2.VideoCapture(src) #self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) #self.cap.set(cv2.CAP_PROP_FPS, self.fps) self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, self.img_size[0]) self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, self.img_size[1]) self.rotate_180 = rotate_180 defrun(self): # read the frame ret, image = self.cap.read() ifself.rotate_180: image = cv2.rotate(image, cv2.ROTATE_180) ifnotret: raiseRuntimeError("failed to read frame") returnimage

這里我使用image = cv2.rotate(image, cv2.ROTATE_180)是因?yàn)閿z像頭是倒置安裝的。

緩沖區(qū)大小和FPS設(shè)置可以用于修復(fù)延遲并正確對(duì)齊幀流。但在我的情況下,它們不起作用,因?yàn)檫@取決于攝像頭制造商和用于讀取幀的后端。

一旦攝像頭捕獲到圖像,我們就需要處理它,從車牌檢測(cè)開始。

車牌檢測(cè)模塊

對(duì)于這個(gè)任務(wù),我將使用YOLOv7的預(yù)訓(xùn)練模型,并在自定義車牌數(shù)據(jù)集上進(jìn)行微調(diào)。

YOLOv7是目前在準(zhǔn)確性和速度方面最先進(jìn)的實(shí)時(shí)物體檢測(cè)算法。它在COCO數(shù)據(jù)集上進(jìn)行了預(yù)訓(xùn)練。

你可以在論文中閱讀有關(guān)該算法的詳細(xì)信息:YOLOv7:可訓(xùn)練的自由目標(biāo)集為實(shí)時(shí)目標(biāo)檢測(cè)器樹立了新的行業(yè)標(biāo)準(zhǔn)。https://arxiv.org/abs/2207.02696

YOLOv7基準(zhǔn)測(cè)試如下:

9ef91bdc-46a5-11f0-986f-92fbcf53809c.png

從官方倉(cāng)庫(kù)克隆YOLOv7倉(cāng)庫(kù)。

gitclonehttps://github.com/WongKinYiu/yolov7.gitcdyolov7

YOLO的要求已經(jīng)包含在我們之前安裝的項(xiàng)目要求中。

對(duì)于微調(diào),我將使用預(yù)訓(xùn)練的YOLOv7 tiny版本,圖像大小為640。

#Download pre-trained weights!wget https://github.com/WongKinYiu/yolov7/releases/download/v0.1/yolov7-tiny.pt

默認(rèn)預(yù)訓(xùn)練物體檢測(cè):默認(rèn)yolov7-tiny檢測(cè)到的物體,標(biāo)準(zhǔn)COCO數(shù)據(jù)集類別

9f072df8-46a5-11f0-986f-92fbcf53809c.png

車牌檢測(cè)模型訓(xùn)練

在自定義數(shù)據(jù)集上訓(xùn)練模型非常簡(jiǎn)單直接。

我將在Google Colab上使用一些不錯(cuò)的GPU進(jìn)行模型微調(diào)。

在開始之前,我們需要?jiǎng)?chuàng)建并標(biāo)注一個(gè)只包含一個(gè)車牌類別的適當(dāng)數(shù)據(jù)集。

我的數(shù)據(jù)集部分基于我自己的照片,部分來自AUTO.RIA車牌數(shù)據(jù)集(向這些了不起的家伙致敬?。?,總共約2000張圖像。https://nomeroff.net.ua/datasets/

使用roboflow服務(wù)以Yolo格式進(jìn)行標(biāo)注。

9f1e407e-46a5-11f0-986f-92fbcf53809c.png

創(chuàng)建數(shù)據(jù)集.yaml文件:

train: dataset/trainval: dataset/valid# Classesnc: 1 # number of classesnames: ['numberplate'] # class names

訓(xùn)練模型

pythontrain.py --epochs25--workers8--device0--batch-size32--data data/numberplates.yaml --img640640--cfg cfg/training/yolov7.yaml --weights 'yolov7-tiny.pt' --name yolov7_tiny_numberplates --hyp data/hyp.scratch.tiny.yaml

對(duì)于基礎(chǔ)版本,我決定25個(gè)epoch應(yīng)該足夠了。25個(gè)epoch的模型訓(xùn)練結(jié)果:

9f3240a6-46a5-11f0-986f-92fbcf53809c.png

推理:微調(diào)后的yolov7-tiny檢測(cè)到的物體,單一類別

9f46d49e-46a5-11f0-986f-92fbcf53809c.png

對(duì)于項(xiàng)目的第一版來說似乎足夠了,以后可以根據(jù)實(shí)際應(yīng)用中發(fā)現(xiàn)的邊緣情況進(jìn)行更新。

為YOLOv7檢測(cè)器創(chuàng)建一個(gè)抽象的簡(jiǎn)單包裝器類:

classDetector(): def__init__(self, model_weights, img_size=640, device='cpu', half=False, trace=True, log_level='INFO', log_dir ='./logs/'): # Initialize self.model_weights = model_weights self.img_size = img_size self.device = torch.device(device) self.half = half # half = device.type != 'cpu' # half precision only supported on CUDA self.trace = trace # Convert model to Traced-model self.log_level = log_level ifself.log_level: self.num_log_level =getattr(logging, self.log_level.upper(),20)##Translate the log_level input string to one of the accepted values of the logging module, if no 20 - INFO self.log_dir = log_dir log_formatter = logging.Formatter("%(asctime)s %(message)s") logFile = self.log_dir +'detection.log' my_handler = RotatingFileHandler(logFile, mode='a', maxBytes=25*1024*1024, backupCount=10, encoding='utf-8', delay=False) my_handler.setFormatter(log_formatter) my_handler.setLevel(self.num_log_level) self.logger = logging.getLogger(__name__) self.logger.setLevel(self.num_log_level) self.logger.addHandler(my_handler) # Add path to yolo model as whenever load('weights.pt') is called, pytorch looks for model config in path enviornment variable (models/yolo) yolo_folder_dir =str(Path(__file__).parent.absolute()) +"\yolov7"# models folder path sys.path.insert(0, yolo_folder_dir) # Load model self.model = attempt_load(self.model_weights, map_location=self.device) # load FP32 model # Convert model to Traced-model ifself.trace: self.model = TracedModel(self.model, self.device, self.img_size) # if half: # model.half() # to FP16 # Get names and colors self.names = self.model.module.namesifhasattr(self.model,'module')elseself.model.names iflen(self.names) >1: self.colors = [[0,255,127]] + [[random.randint(0,255)for_inrange(3)]for_inself.names[1:]] else: self.colors = [[0,255,127]] sys.path.remove(yolo_folder_dir) defrun(self, inp_image, conf_thres=0.25): # Run Inference # Load data dataset = LoadImage(inp_image, device=self.device, half=self.half) t0 = time.time() self.file_name, self.img, self.im0 = dataset.preprocess() # Inference t1 = time.time() withtorch.no_grad(): # Calculating gradients would cause a GPU memory leak self.pred = self.model(self.img)[0] t2 = time.time() # Apply NMS self.pred = non_max_suppression(self.pred, conf_thres=conf_thres) t3 = time.time() # Process detections bbox =None # bounding boxe of detected object with max conf cropped_img =None # cropped detected object with max conf det_conf =None # confidence level for detected object with max conf self.det = self.pred[0] # pred[0] - NMX suppr returns list with 1 tensor per image; iflen(self.det): # Rescale boxes from img_size to im0 size self.det[:, :4] = scale_coords(self.img.shape[2:], self.det[:, :4], self.im0.shape).round() # Print results print_strng ="" forcinself.det[:, -1].unique(): n = (self.det[:, -1] == c).sum() # detections per class print_strng +=f"{n}{self.names[int(c)]}{'s'* (n >1)}" # add to string # Print time (inference + NMS) print( f'{print_strng}detected. ({(1E3* (t1 - t0)):.1f}ms)-Load data, ({(1E3* (t2 - t1)):.1f}ms)-Inference, ({(1E3* (t3 - t2)):.1f}ms)-NMS') # Write results to file if debug mode ifself.log_level: self.logger.debug( f'{self.file_name}{print_strng}detected. ({(1E3* (t1 - t0)):.1f}ms)-Load data, ({(1E3* (t2 - t1)):.1f}ms)-Inference, ({(1E3* (t3 - t2)):.1f}ms)-NMS') ifself.logger.getEffectiveLevel() ==10: # level 10 = debug gn = torch.tensor(self.im0.shape)[[1,0,1,0]] # normalization gain whwh for*xyxy, conf, clsinreversed(self.det): # save detections with bbox in xywh format xywh = (xyxy2xywh(torch.tensor(xyxy).view(1,4)) / gn).view(-1).tolist() # normalized xywh line = (int(cls), np.round(conf,3), *xywh) # label format self.logger.debug(f"{self.file_name}{('%g '*len(line)).rstrip() % line}") # Find detection with max confidence: indx = self.pred[0].argmax(0)[ 4] # pred[0] - NMX suppr returns list with 1 tensor per image; argmax(0)[4] - conf has indx 4 in [x1,y1,x2,y2,conf,cls] max_det = self.pred[0][indx] # Collect detected bounding boxe and corresponding cropped img bbox = max_det[:4] cropped_img = save_crop(max_det[:4], self.im0) cropped_img = cropped_img[:, :, ::-1]# # BGR to RGB det_conf = max_det[4:5] print(f'Detection total time:{time.time() - t0:.3f}s') return{'file_name': self.file_name,'orig_img': self.im0,'cropped_img': cropped_img,'bbox': bbox, 'det_conf': det_conf}

這里為了調(diào)試目的,我添加了將檢測(cè)數(shù)據(jù)記錄到文件的可能性,最多10個(gè)文件,每個(gè)文件25Mb,然后重寫。

對(duì)于當(dāng)前任務(wù),我需要檢測(cè)器只返回一個(gè)置信度最高的檢測(cè)結(jié)果。此外,檢測(cè)器輸出原始圖像、裁剪后的檢測(cè)區(qū)域及其對(duì)應(yīng)的邊界框、置信度分?jǐn)?shù),以及為每個(gè)圖像生成一個(gè)唯一名稱以便于調(diào)試。

車牌區(qū)域圖像預(yù)處理

一般來說,下一步是對(duì)圖像進(jìn)行特定的預(yù)處理(如RGB轉(zhuǎn)灰度、去噪、腐蝕+膨脹、閾值處理、直方圖均衡化等),以便進(jìn)行下一步的OCR。預(yù)處理在很大程度上取決于并針對(duì)具體的OCR解決方案和拍攝條件進(jìn)行調(diào)整。但由于我正在使用EasyOCR構(gòu)建基礎(chǔ)版本(之后應(yīng)該替換為自定義解決方案),我決定不深入進(jìn)行預(yù)處理,只進(jìn)行兩個(gè)通用的步驟——灰度轉(zhuǎn)換和使用投影輪廓法進(jìn)行傾斜校正。

這里我使用的是平面角度校正,但之后應(yīng)該更新為使用真實(shí)車牌角點(diǎn)檢測(cè)器進(jìn)行單應(yīng)性計(jì)算和透視變換的校正。

# Skew Correction (projection profile)def_find_score(arr, angle): data = rotate(arr, angle, reshape=False, order=0) hist = np.sum(data, axis=1) score = np.sum((hist[1:] - hist[:-1]) **2) returnhist, scoredef_find_angle(img, delta =0.5, limit =10): angles = np.arange(-limit, limit+delta, delta) scores = [] forangleinangles: hist, score = _find_score(img, angle) scores.append(score) best_score =max(scores) best_angle = angles[scores.index(best_score)] print(f'Best angle:{best_angle}') returnbest_angledefcorrect_skew(img): # correctskew best_angle =_find_angle(img) data = rotate(img, best_angle, reshape=False, order=0) returndata

即使對(duì)于這樣扭曲的圖像,僅進(jìn)行傾斜校正就足以讓EasyOCR以高置信度正確讀取車牌號(hào)碼。

9f5c8af0-46a5-11f0-986f-92fbcf53809c.png

經(jīng)過上述圖像處理步驟后,我們可以認(rèn)為圖像已經(jīng)足夠好,可以進(jìn)行識(shí)別了。

車牌識(shí)別(OCR)

對(duì)于基礎(chǔ)版本,我決定使用EasyOCR解決方案,因?yàn)樗子谑褂?、識(shí)別準(zhǔn)確,而且可能是我所知道的唯一比無聊的tesseract更好的替代方案。

使用EasyOCR進(jìn)行車牌識(shí)別的簡(jiǎn)單包裝器類:

classEasyOcr(): def__init__(self, lang = ['en'], allow_list ='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ', min_size=50, log_level='INFO', log_dir ='./logs/'): self.reader = easyocr.Reader(lang, gpu=False) self.allow_list = allow_list self.min_size = min_size self.log_level = log_level ifself.log_level: self.num_log_level =getattr(logging, log_level.upper(), 20) ##Translate the log_level input string to one of the accepted values of the logging module, if no 20 - INFO self.log_dir = log_dir # Set logger log_formatter = logging.Formatter("%(asctime)s %(message)s") logFile = self.log_dir +'ocr.log' my_handler = RotatingFileHandler(logFile, mode='a', maxBytes=25*1024*1024, backupCount=10, encoding='utf-8', delay=False) my_handler.setFormatter(log_formatter) my_handler.setLevel(self.num_log_level) self.logger = logging.getLogger(__name__) self.logger.setLevel(self.num_log_level) self.logger.addHandler(my_handler) defrun(self, detect_result_dict): ifdetect_result_dict['cropped_img']isnotNone: t0 = time.time() img = detect_result_dict['cropped_img'] img = ocr_img_preprocess(img) file_name = detect_result_dict.get('file_name') ocr_result = self.reader.readtext(img, allowlist = self.allow_list, min_size=self.min_size) text = [x[1]forxinocr_result] confid = [x[2]forxinocr_result] text ="".join(text)iflen(text) >0elseNone confid = np.round(np.mean(confid),2)iflen(confid) >0elseNone t1 = time.time() print(f'Recognized number:{text}, conf.:{confid}.\nOCR total time:{(t1 - t0):.3f}s') ifself.log_level: # Write results to file if debug mode self.logger.debug(f'{file_name}Recognized number:{text}, conf.:{confid}, OCR total time:{(t1 - t0):.3f}s.') return{'text': text,'confid': confid} else: return{'text':None,'confid':None}

與檢測(cè)器類似,這里為了調(diào)試目的,也添加了將OCR數(shù)據(jù)記錄到文件的可能性。

識(shí)別模塊返回檢測(cè)到的字符串和置信度分?jǐn)?shù)。

驗(yàn)證與操作

在我們成功從檢測(cè)到的車牌中獲取到識(shí)別文本后,是時(shí)候進(jìn)行驗(yàn)證并采取一些行動(dòng)了。對(duì)于車牌驗(yàn)證步驟,最合乎邏輯的做法是使用一個(gè)由客戶更新的數(shù)據(jù)庫(kù),我們每次或每天讀取一次,并將列表本地存儲(chǔ)。對(duì)于當(dāng)前的基礎(chǔ)版本,我決定不設(shè)置數(shù)據(jù)庫(kù),以節(jié)省時(shí)間和金錢,因?yàn)檫@不是重點(diǎn)。我將使用Google表格作為示例。

“數(shù)據(jù)庫(kù)”

9f70c1fa-46a5-11f0-986f-92fbcf53809c.png

截至目前,還沒有配置操作步驟,只是顯示在允許列表中的車牌號(hào)碼檢查結(jié)果。但對(duì)于樹莓派來說,通過GPIO控制的繼電器開關(guān)操作任何負(fù)載都非常容易。

可視化

為了能夠舒適地監(jiān)控和調(diào)試解決方案,我添加了一個(gè)可視化模塊,用于處理車牌識(shí)別過程的顯示、保存輸入圖像、裁剪后的車牌區(qū)域和輸出結(jié)果圖像。此外,我還添加了一個(gè)函數(shù),用于在電子墨水屏上顯示車牌區(qū)域和識(shí)別文本。

目前,為了方便起見,圖像以壓縮的JPG格式存儲(chǔ)在日志文件夾中,數(shù)量限制為10800張,隨后進(jìn)行覆蓋(文件夾最大大小約為500Mb)。在生產(chǎn)解決方案中,可視化并不是必需的,用于調(diào)試的圖像最好存儲(chǔ)在NumPy ndarrays或二進(jìn)制字符串中。

classVisualize(): def__init__(self, im0, file_name, cropped_img=None, bbox=None, det_conf=None, ocr_num=None, ocr_conf=None, num_check_response=None, out_img_size=(720,1280), outp_orig_img_size =640, log_dir ='./logs/', save_jpg_qual =65, log_img_qnt_limit =10800): self.im0 = im0 self.input_img = im0.copy() self.file_name = file_name self.cropped_img = cropped_img self.bbox = bbox self.det_conf = det_conf self.ocr_num = ocr_num self.ocr_conf = ocr_conf self.num_check_response = num_check_response self.out_img_size = out_img_size self.save_jpg_qual = save_jpg_qual self.log_dir = log_dir self.imgs_log_dir = self.log_dir +'imgs/' os.makedirs(os.path.dirname(self.imgs_log_dir), exist_ok=True) self.crop_imgs_log_dir = self.log_dir +'imgs/crop/' os.makedirs(os.path.dirname(self.crop_imgs_log_dir), exist_ok=True) self.orig_imgs_log_dir = self.log_dir +'imgs/inp/' os.makedirs(os.path.dirname(self.orig_imgs_log_dir), exist_ok=True) self.log_img_qnt_limit = log_img_qnt_limit # Create blank image h, w = self.out_img_size self.img = np.zeros((h, w,3), np.uint8) self.img[:, :] = (255,255,255) # Draw bounding box on top the image if(self.bboxisnotNone)and(self.det_confisnotNone): label =f'{self.det_conf.item():.2f}' color = [0,255,127] plot_one_box(self.bbox, self.im0, label=label, color=color, line_thickness=3) # Resize img width to fit the plot, keep origin aspect ratio h0, w0 = im0.shape[:2] aspect = w0 / h0 ifaspect >1: # horizontal image new_w = outp_orig_img_size new_h = np.round(new_w / aspect).astype(int) elifaspect self.log_img_qnt_limit: oldest_file =sorted([self.imgs_log_dir+fforfinos.listdir(self.imgs_log_dir)])[ 0] # , key=os.path.getctime os.remove(oldest_file) # Write compressed jpeg with results cv2.imwrite(f"{self.imgs_log_dir}{self.file_name}", self.img, [int(cv2.IMWRITE_JPEG_QUALITY), self.save_jpg_qual]) # TBD Write in byte string format defsave_input(self): ifself.input_imgisnotNone: # Remove oldest file if reach quantity limit ifself.get_dir_file_quantity(self.orig_imgs_log_dir) > self.log_img_qnt_limit: oldest_file =sorted([self.orig_imgs_log_dir+fforfinos.listdir(self.orig_imgs_log_dir)])[ 0] # , key=os.path.getctime os.remove(oldest_file) # Write compressed jpeg with results cv2.imwrite(f"{self.orig_imgs_log_dir}orig_inp_{self.file_name}", self.input_img) # TBD Write in byte string format defsave_crop(self): ifself.cropped_imgisnotNone: # Remove oldest file if reach quantity limit ifself.get_dir_file_quantity(self.crop_imgs_log_dir) > self.log_img_qnt_limit: oldest_file =sorted([self.crop_imgs_log_dir+fforfinos.listdir(self.crop_imgs_log_dir)])[ 0] # , key=os.path.getctime os.remove(oldest_file) # Write compressed jpeg with results cv2.imwrite(f"{self.crop_imgs_log_dir}crop_{self.file_name}", self.cropped_img) # TBD Write in byte string format # Display img on e-ink display 176*264. defdisplay(self): # Create blank image disp_img = np.zeros((epd2in7.EPD_WIDTH, epd2in7.EPD_HEIGHT,3), np.uint8) disp_img[:, :] = (255,255,255) ifself.cropped_imgisnotNone: # Add cropped number crop_resized = cv2.resize(self.cropped_img, (epd2in7.EPD_HEIGHT-4,85), interpolation=cv2.INTER_AREA) crop_resized_h, crop_resized_w = crop_resized.shape[:2] crop_w_x1 =int(epd2in7.EPD_HEIGHT/2- crop_resized_w/2) disp_img[2:crop_resized_h+2, crop_w_x1:crop_resized_w+crop_w_x1] = crop_resized ifself.ocr_numisnotNone: # Add recognized label label =f"{self.ocr_num}({self.ocr_conf})" t_thickn =2 # text font thickness in px font = cv2.FONT_HERSHEY_SIMPLEX # font fontScale =0.8 text_size = cv2.getTextSize(label, font, fontScale=fontScale, thickness=t_thickn)[0] ocr_w_x1 =int(epd2in7.EPD_HEIGHT /2- text_size[0] /2) ocr_h_y1 =int(crop_resized_h/2+2+ epd2in7.EPD_WIDTH/2) # Plot text on img cv2.putText(disp_img, label, (ocr_w_x1, ocr_h_y1), font, fontScale, color=(0,0,0), thickness=t_thickn, lineType=cv2.LINE_AA) Himage = cv2.resize(disp_img, (epd2in7.EPD_HEIGHT, epd2in7.EPD_WIDTH), interpolation=cv2.INTER_AREA) print(f"###Himage:{Himage.shape}") # convert to PIL format Himage = Image.fromarray(Himage) tic = time.perf_counter() epd = epd2in7.EPD()# get the display epd.init() # initialize the display epd.Clear(0xFF) # clear the display toc = time.perf_counter() print(f"Init, clean display -{toc - tic:0.4f}seconds") tic = time.perf_counter() epd.display(epd.getbuffer(Himage)) toc = time.perf_counter() print(f"Display image -{toc - tic:0.4f}seconds") epd.sleep()# Power off display @staticmethod defget_dir_file_quantity(dir_path): list_of_files = os.listdir(dir_path) returnlen(list_of_files)

演示

測(cè)試解決方案

讓我們測(cè)試一下我們現(xiàn)在已經(jīng)完成的內(nèi)容。在靜態(tài)圖像上的檢測(cè)和識(shí)別流程:

手機(jī)上傳的圖像結(jié)果。

9f7b2280-46a5-11f0-986f-92fbcf53809c.jpg

使用設(shè)備攝像頭在街道上進(jìn)行端到端解決方案測(cè)試:

9f8446a8-46a5-11f0-986f-92fbcf53809c.jpg

9f97ae8c-46a5-11f0-986f-92fbcf53809c.jpg

9fa19046-46a5-11f0-986f-92fbcf53809c.jpg

9faafbcc-46a5-11f0-986f-92fbcf53809c.jpg

如我們所見,這里傾斜校正派上了用場(chǎng)。

性能

在當(dāng)前配置下,檢測(cè)大約需要700..800ms,OCR步驟大約需要900..1200ms,平均FPS約為0.4..0.5

9fb87fea-46a5-11f0-986f-92fbcf53809c.png

雖然這樣的幀率值對(duì)于當(dāng)前的停車道閘自動(dòng)化項(xiàng)目來說并不關(guān)鍵,但顯然還有很大的改進(jìn)空間。

從htop我們可以看到,CPU利用率接近滿負(fù)荷:

9fcbcf82-46a5-11f0-986f-92fbcf53809c.png

所有測(cè)試都是在樹莓派操作系統(tǒng)的默認(rèn)設(shè)置下進(jìn)行的。如果你禁用UI和所有其他默認(rèn)啟用的后臺(tái)服務(wù),性能將更加穩(wěn)定和高效。

額外收獲

事實(shí)證明,我們的檢測(cè)器模塊即使沒有任何額外的調(diào)整,也能完美地檢測(cè)樂高汽車的車牌。

9fdf54c6-46a5-11f0-986f-92fbcf53809c.pnga02086bc-46a5-11f0-986f-92fbcf53809c.png

因此,有了樹莓派Build Hat和我從兒子那里借來的樂高積木,我決定搭建自己的停車道閘,并在“真實(shí)”條件下進(jìn)行完整的端到端測(cè)試。

基于樂高Build Hat專有庫(kù)的簡(jiǎn)單操作模塊包裝器:

classAction(): def__init__(self): self.motor = Motor('A') self.motor.set_default_speed(25) self.matrix = Matrix('B') self.ok_color = [[(6,10)forxinrange(3)]foryinrange(3)] self.nok_color = [[(9,10)forxinrange(3)]foryinrange(3)] self.matrix.set_transition(2)#fade-in/out self.matrix.set_pixel((1,1), ("blue",10)) def_handle_motor(self, speed, pos, apos): print("Motor:", speed, pos, apos) defrun(self, action_status): whileTrue: ifaction_status[0] =='Allowed': self.matrix.set_pixels(self.ok_color) time.sleep(1) self.motor.run_for_degrees(-90, blocking=False) time.sleep(5) self.motor.run_for_degrees(90, blocking=False) time.sleep(1) elifaction_status[0] =='Prohibited': self.matrix.set_pixels(self.nok_color) time.sleep(3) else: self.matrix.clear() self.matrix.set_pixel((1,1), ("blue",10)) time.sleep(1) self.matrix.set_pixel((1,1), (0,10)) time.sleep(1)

我在一個(gè)并行線程中運(yùn)行這個(gè)模塊,當(dāng)檢測(cè)到車牌且action_status發(fā)生變化時(shí),從主程序中觸發(fā)操作。

a02f4422-46a5-11f0-986f-92fbcf53809c.jpg

“弗蘭肯斯坦的怪物”——樹莓派 + UPS + 攝像頭v2 + 電子墨水屏 + 帶有連接的樂高電機(jī)和LED矩陣的Build HAT。

我將其中一個(gè)樂高車牌號(hào)碼添加到了Google表格“數(shù)據(jù)庫(kù)”中,現(xiàn)在我們可以將所有部分組合在一起并運(yùn)行它:

“真實(shí)”自動(dòng)化停車道閘控制系統(tǒng)的端到端演示

a03d38ca-46a5-11f0-986f-92fbcf53809c.gif

最終思考

總的來說,我們已經(jīng)成功實(shí)現(xiàn)了使用樹莓派進(jìn)行自動(dòng)車牌識(shí)別以控制停車道閘的完全功能系統(tǒng)。

需要強(qiáng)調(diào)的問題之一是——由于處理速度較慢,我們可能會(huì)遇到圖像延遲,因?yàn)閿z像頭有自己的緩沖區(qū),而我們以較慢的速度抓取圖像,即使場(chǎng)景已經(jīng)改變,一段時(shí)間內(nèi)我們?nèi)匀粡木彌_區(qū)中讀取“舊”幀。對(duì)于當(dāng)前的使用案例來說,這并不是非常關(guān)鍵,但為了改進(jìn)它,我添加了幀跳過功能,間隔大約等于我們的總處理時(shí)間。這樣可以更快地讀取幀并清理緩沖區(qū),同時(shí)也減輕了CPU的負(fù)載,因?yàn)槲覀儾粫?huì)處理每一幀。但是,如果我們需要近乎實(shí)時(shí)的流暢圖像流而不出現(xiàn)延遲,最好的選擇是將攝像頭讀取設(shè)置為一個(gè)單獨(dú)的并行線程,該線程將以最大速度從緩沖區(qū)中讀取幀,而我們的主程序只在需要時(shí)從該進(jìn)程中抓取幀。然而,需要注意的是,在Python中,多線程并不是真正的多進(jìn)程,而是一種模擬,它有助于從架構(gòu)的角度更清晰地組織和運(yùn)行你的代碼。

后續(xù)步驟

OCR:加速OCR,因?yàn)樗钱?dāng)前的瓶頸。我傾向于開發(fā)一個(gè)自定義的小型基于RNN的模型。如果時(shí)間不是問題,而你只需要準(zhǔn)確性——你可以嘗試在EasyOCR中使用不同的模型并進(jìn)行微調(diào)。或者你可以嘗試其他解決方案,如WPOD-NET。此外,提高識(shí)別質(zhì)量的一個(gè)重要點(diǎn)是——針對(duì)具體的使用案例(攝像頭位置、光照條件等)調(diào)整圖像預(yù)處理。

檢測(cè)器:為了加速,我們可以使用更小的幀大小——如果攝像頭應(yīng)該只對(duì)近處的車輛工作,就不需要高分辨率的圖像。另一個(gè)選項(xiàng)是,如果攝像頭和車輛的可能位置大致固定,我們可以只抓取車牌預(yù)期出現(xiàn)的區(qū)域,而不是整個(gè)幀。

對(duì)于這兩個(gè)模型,我們之后可以使用遷移學(xué)習(xí)、量化、剪枝和其他方法,使其在邊緣設(shè)備上更輕量、更快。

但無論如何,如果實(shí)時(shí)處理是關(guān)鍵(顯然對(duì)于自動(dòng)化停車道閘案例來說不是),沒有配備張量核心的設(shè)備是無法實(shí)現(xiàn)的。在僅配備CPU的設(shè)備上,速度和質(zhì)量之間總是需要權(quán)衡。

另一個(gè)改進(jìn)選項(xiàng)是——對(duì)于當(dāng)前案例來說,沒有必要24/7讓CPU全速運(yùn)行,攝像頭可以在車輛接近時(shí)通過PIR或紅外傳感器觸發(fā)。

我將在下一次迭代中嘗試實(shí)現(xiàn)的最后一點(diǎn)是——將解決方案切換到微服務(wù),并實(shí)現(xiàn)生產(chǎn)者-消費(fèi)者數(shù)據(jù)流模式。

好了,感謝你閱讀這篇關(guān)于項(xiàng)目實(shí)施經(jīng)驗(yàn)的冗長(zhǎng)而枯燥的描述。

原文地址:

https://medium.com/@alexey.yeryomenko/automatic-number-plate-recognition-with-raspberry-pi-e1ac8a804c79

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 車牌識(shí)別系統(tǒng)

    關(guān)注

    0

    文章

    16

    瀏覽量

    9630
  • 樹莓派
    +關(guān)注

    關(guān)注

    121

    文章

    2009

    瀏覽量

    107482
收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    基于LabVIEW的車牌識(shí)別系統(tǒng)

    又沒人做過基于LabVIEW的車牌識(shí)別系統(tǒng)或類似的?
    發(fā)表于 06-11 15:32

    【TL6748 DSP申請(qǐng)】基于DSP的車牌識(shí)別系統(tǒng)

    有機(jī)會(huì)玩TMS320F項(xiàng)目描述:用TMS320C6748搭載攝像頭實(shí)現(xiàn)車牌自動(dòng)識(shí)別系統(tǒng),基于模糊控制,圖像分割、模式識(shí)別等理論,通過對(duì)采集的數(shù)據(jù)進(jìn)行分析掃描,自動(dòng)補(bǔ)全不全或污損的
    發(fā)表于 09-09 16:59

    怎么用FPGA做車牌識(shí)別系統(tǒng)?

    最近在做畢業(yè)設(shè)計(jì),要求用FPGA,原本打算做車牌識(shí)別系統(tǒng),但是太難了,大家有沒有好的想法。。
    發(fā)表于 11-25 23:23

    基于labview vision的機(jī)動(dòng)車車牌識(shí)別系統(tǒng)

    機(jī)動(dòng)車車牌識(shí)別系統(tǒng),內(nèi)附子VI打開密碼
    發(fā)表于 02-26 19:35

    【Rico Board申請(qǐng)】基于SoC的車牌識(shí)別系統(tǒng)

    最近導(dǎo)師在外面公司承接的一個(gè)車牌識(shí)別系統(tǒng)其實(shí)還是非常適合用SoC作為硬件平臺(tái)來完成這個(gè)項(xiàng)目;3.本人預(yù)期的設(shè)想是在SD卡上裝入一個(gè)ubuntu,在里面再植入opencv的開發(fā)環(huán)境,
    發(fā)表于 11-10 10:12

    【MediaTek X20開發(fā)板申請(qǐng)】小區(qū)車牌自動(dòng)識(shí)別系統(tǒng)

    項(xiàng)目名稱:小區(qū)車牌自動(dòng)識(shí)別系統(tǒng)試用計(jì)劃:MediaTek X20 開發(fā)板是一款誠(chéng)邁科技和聯(lián)發(fā)科技聯(lián)合發(fā)布的符合96board規(guī)范的開源硬件,具有非常強(qiáng)大的運(yùn)算能力和多媒體處理能力最新的
    發(fā)表于 12-29 17:05

    【HarmonyOS HiSpark AI Camera】車牌識(shí)別系統(tǒng)

    項(xiàng)目名稱:車牌識(shí)別系統(tǒng)試用計(jì)劃:申請(qǐng)理由本人在嵌入式開發(fā)行業(yè)從事了五年的開發(fā)經(jīng)驗(yàn),在智能家居,無線mesh網(wǎng)絡(luò)領(lǐng)域擁有豐富的經(jīng)驗(yàn)。并且自學(xué)A
    發(fā)表于 11-18 18:15

    怎么實(shí)現(xiàn)基于MATLAB的車牌識(shí)別系統(tǒng)的設(shè)計(jì)?

    一個(gè)完整的牌照識(shí)別系統(tǒng)包括哪些單元?怎么實(shí)現(xiàn)基于MATLAB的車牌識(shí)別系統(tǒng)的設(shè)計(jì)?
    發(fā)表于 05-12 07:04

    車牌識(shí)別系統(tǒng)的設(shè)計(jì)與實(shí)現(xiàn)

    車牌照自動(dòng)識(shí)別系統(tǒng)是制約道路交通智能化的重要因素,包括車牌定位、字符分割和字符識(shí)別三個(gè)主要部分。本文首先確定車輛牌照在原始圖像中的水平位置和垂直位置,從而定
    發(fā)表于 02-21 10:59 ?51次下載

    基于MATLAB的車牌識(shí)別系統(tǒng)的研究

    基于MATLAB的車牌識(shí)別系統(tǒng)的研究 1 引言     車輛牌照是機(jī)動(dòng)車唯一的管理標(biāo)識(shí)符號(hào),在交通管理中具有不可替代的作用,因此車輛牌照識(shí)別系統(tǒng)
    發(fā)表于 12-10 10:29 ?3117次閱讀
    基于MATLAB的<b class='flag-5'>車牌</b><b class='flag-5'>識(shí)別系統(tǒng)</b>的研究

    車牌識(shí)別技術(shù)的發(fā)展及意義_車牌識(shí)別系統(tǒng)原理介紹

    本文主要介紹了車牌識(shí)別系統(tǒng)原理、車牌識(shí)別技術(shù)的意義、車牌識(shí)別技術(shù)應(yīng)用表現(xiàn)和國(guó)內(nèi)
    發(fā)表于 01-02 15:12 ?1.7w次閱讀
    <b class='flag-5'>車牌</b><b class='flag-5'>識(shí)別</b>技術(shù)的發(fā)展及意義_<b class='flag-5'>車牌</b><b class='flag-5'>識(shí)別系統(tǒng)</b>原理介紹

    基于MATLAB的車牌識(shí)別系統(tǒng)

    基于MATLAB的車牌識(shí)別系統(tǒng)設(shè)計(jì)說明。
    發(fā)表于 04-16 09:30 ?18次下載

    基于LABVIEW的車牌識(shí)別系統(tǒng)資料

    基于LABVIEW的車牌識(shí)別系統(tǒng)資料
    發(fā)表于 01-11 18:16 ?67次下載

    項(xiàng)目分享|基于ELF 1開發(fā)板的車牌識(shí)別系統(tǒng)

    項(xiàng)目選用ElfBoardELF1開發(fā)板作為核心硬件平臺(tái),利用USB接口連接的攝像頭捕捉并識(shí)別車牌信息。一旦車牌成功
    的頭像 發(fā)表于 03-12 09:22 ?888次閱讀
    <b class='flag-5'>項(xiàng)目</b>分享|基于ELF 1<b class='flag-5'>開發(fā)</b>板的<b class='flag-5'>車牌</b><b class='flag-5'>識(shí)別系統(tǒng)</b>

    車牌識(shí)別新花樣:樹莓打造智能車牌監(jiān)控系統(tǒng)!

    樹莓是創(chuàng)客們打造家庭安防系統(tǒng)的熱門之選,這得益于其具備運(yùn)用人工智能(AI)的能力。AI系統(tǒng)識(shí)別潛在威脅,在此情境下,還能
    的頭像 發(fā)表于 04-26 09:03 ?256次閱讀
    <b class='flag-5'>車牌</b><b class='flag-5'>識(shí)別</b>新花樣:<b class='flag-5'>樹莓</b><b class='flag-5'>派</b>打造智能<b class='flag-5'>車牌</b>監(jiān)控<b class='flag-5'>系統(tǒng)</b>!