網(wǎng)站制作服務(wù)公司婚戀網(wǎng)站排名前三
基于iOS平臺(tái)的車(chē)牌識(shí)別&&表情識(shí)別項(xiàng)目
簡(jiǎn)介
? 該項(xiàng)目客戶端搭載于iOS平臺(tái),服務(wù)端搭載于阿里云服務(wù)器,主要功能是通過(guò)拍照或選取相冊(cè)圖片來(lái)進(jìn)行車(chē)牌的識(shí)別以及人臉表情識(shí)別。本文便是對(duì)項(xiàng)目整體流程設(shè)計(jì)思路和具體實(shí)現(xiàn)做一個(gè)詳細(xì)介紹。
整體實(shí)現(xiàn)流程
? 為了閱讀下述內(nèi)容時(shí)腦海中有個(gè)框架,便對(duì)項(xiàng)目做一個(gè)整體實(shí)現(xiàn)流程的描述。
后端
? ** 實(shí)現(xiàn)功能: **使用訓(xùn)練好的Yolo模型在照片中檢測(cè)出車(chē)牌部分,處理圖片進(jìn)行部分截取,對(duì)處理出的圖片進(jìn)行進(jìn)一步的模型檢測(cè),識(shí)別出車(chē)牌號(hào)、車(chē)牌顏色等信息,然后通過(guò)服務(wù)器與移動(dòng)端的網(wǎng)絡(luò)通信,將檢測(cè)結(jié)果返回給移動(dòng)端。
? 后端模型訓(xùn)練和檢測(cè)這一部分是由同專業(yè)的兩位大哥(tjx大哥、zmx大哥)寫(xiě)的,具體實(shí)現(xiàn)細(xì)節(jié)這里不過(guò)多介紹,下面具體介紹也只講通信部分,想知道模型的訓(xùn)練流程和檢測(cè)流程可以看作者的這兩篇文章——《從零開(kāi)始安裝并允許YOLOv5》、《簡(jiǎn)單使用YOLOv5自己訓(xùn)練模型》,相信看完之后也能有個(gè)大致概念。
移動(dòng)端
? **實(shí)現(xiàn)功能:**編寫(xiě)UI界面、添加交互行為、發(fā)起網(wǎng)絡(luò)請(qǐng)求,簡(jiǎn)單來(lái)說(shuō)就是做成一個(gè)App,用戶打開(kāi)App看到的就是UI界面,通過(guò)點(diǎn)擊、滑動(dòng)等等操作進(jìn)行拍照或選取相冊(cè)圖片,接著發(fā)起網(wǎng)絡(luò)請(qǐng)求將這個(gè)指定圖片上傳到服務(wù)器。
? 移動(dòng)端使用的是Swift進(jìn)行iOS開(kāi)發(fā),編寫(xiě)控件操作代碼及相關(guān)布局、照片圖庫(kù)的選取實(shí)現(xiàn)、圖片的保存設(shè)置、以及使用第三方包Alamofire發(fā)起網(wǎng)絡(luò)請(qǐng)求上傳圖片。
服務(wù)器端
? **實(shí)現(xiàn)功能:**作為交通樞紐,將移動(dòng)端發(fā)來(lái)的圖片作為參數(shù)傳給在服務(wù)器端運(yùn)行的上述后端代碼(這里表述不夠準(zhǔn)確,后端就是服務(wù)器端,但為了方便理解,將部署到服務(wù)器這個(gè)過(guò)程抽象為服務(wù)器端),介紹到圖片后進(jìn)行識(shí)別程序,得到的檢測(cè)結(jié)果返回給移動(dòng)端。
后端通信具體實(shí)現(xiàn)
? 使用Python實(shí)現(xiàn)通信需要使用網(wǎng)絡(luò)編程的框架比如Django、Flask。本項(xiàng)目采用的是Flask。
? 以下是一個(gè)Flask框架的簡(jiǎn)單示例:
# 導(dǎo)入Flask模塊
from flask import Flask# 創(chuàng)建Flask應(yīng)用程序?qū)嵗?/span>
app = Flask(__name__)# 定義路由'/hello',路由是指URL的路徑部分
@app.route('/hello')# 定義函數(shù)hello(),該函數(shù)將處理路由'/hello'的請(qǐng)求
def hello():return 'hello world' # 返回字符串'hello world'# 當(dāng)直接運(yùn)行該腳本時(shí)執(zhí)行以下代碼
if __name__ == '__main__':# 運(yùn)行Flask應(yīng)用程序,監(jiān)聽(tīng)所有網(wǎng)絡(luò)接口,調(diào)試模式開(kāi)啟,監(jiān)聽(tīng)的端口號(hào)為6006app.run("0.0.0.0", debug=True, port=6006)
這段代碼就實(shí)現(xiàn)了一個(gè)簡(jiǎn)單后端所要具有的通信功能,客戶端只要訪問(wèn)服務(wù)器所在ip指定端口的指定路由(即上述Hello)即可請(qǐng)求到信息,默認(rèn)是get請(qǐng)求(關(guān)于get請(qǐng)求和post請(qǐng)求的區(qū)別需要去學(xué)計(jì)算機(jī)網(wǎng)絡(luò)的ip協(xié)議)。
服務(wù)器運(yùn)行后效果:
畫(huà)圈處即為你的對(duì)外ip和訪問(wèn)端口6006,此時(shí)服務(wù)器就保持常運(yùn)行監(jiān)聽(tīng)端口,我們可以簡(jiǎn)單在瀏覽器發(fā)起一個(gè)get請(qǐng)求測(cè)試一下,效果如下:
發(fā)現(xiàn)能正常訪問(wèn)。
同理我們?cè)僮鲆粋€(gè)該項(xiàng)目的通信代碼,這個(gè)要考慮的細(xì)節(jié)就比較多了。
車(chē)牌識(shí)別后端通信代碼(1班):
# 導(dǎo)入所需模塊
import base64
from flask import Flask, request, send_file, jsonify
import cv2
import plate_rec
from yolo_det import process_image_with_yolov5# 創(chuàng)建Flask應(yīng)用程序?qū)嵗?/span>
app = Flask(__name__)# 定義路由'/detect_license_plate',處理POST請(qǐng)求
@app.route('/detect_license_plate', methods=['POST'])
def detect_license_plate_route():# 獲取上傳的文件uploaded_file = request.files['file']# 保存上傳的文件到服務(wù)器uploaded_file.save('uploaded_image.jpg')# 調(diào)用車(chē)牌檢測(cè)函數(shù)result_image_path = detect_license_plate('uploaded_image.jpg')# 將結(jié)果圖片轉(zhuǎn)換為Base64編碼的字符串with open(result_image_path, 'rb') as imag_file:result_image_data = base64.b64encode(imag_file.read())# 使用車(chē)牌識(shí)別模塊識(shí)別車(chē)牌號(hào)custom_string = plate_rec.recognize(result_image_path)print(custom_string)# 構(gòu)建響應(yīng)數(shù)據(jù)response = {'custom_string': custom_string,'image_data': result_image_data.decode('utf-8'),}# 返回檢測(cè)結(jié)果圖片和響應(yīng)數(shù)據(jù)return jsonify(response), 200, {'Content-Type': 'application/json; charset=utf-8'}# 車(chē)牌檢測(cè)函數(shù)
def detect_license_plate(url: str):# 設(shè)置模型路徑和圖片路徑onnx_path = '../weights/best.onnx'image_path = url# 使用YOLOv5模型進(jìn)行車(chē)牌檢測(cè)result_image, crop_image = process_image_with_yolov5(onnx_path, image_path)# 保存結(jié)果圖片和車(chē)牌區(qū)域圖片cv2.imwrite('../result/res.jpg', result_image)cv2.imwrite('../result/crop.jpg', crop_image)# 返回車(chē)牌區(qū)域圖片路徑return '../result/crop.jpg'# 當(dāng)直接運(yùn)行該腳本時(shí)執(zhí)行以下代碼
if __name__ == '__main__':# 運(yùn)行Flask應(yīng)用程序,監(jiān)聽(tīng)所有網(wǎng)絡(luò)接口,端口號(hào)為5000app.run(host='0.0.0.0', port=5000)
? 以上車(chē)牌識(shí)別通信代碼中,由于要實(shí)現(xiàn)返回圖片文件和一個(gè)檢測(cè)出車(chē)牌號(hào)結(jié)果的字符串,但我們需要在一次通信過(guò)程中實(shí)現(xiàn),那么這兩個(gè)數(shù)據(jù)就必須捆綁在一起,為了客戶端拿到返回值的適合能把兩個(gè)內(nèi)容區(qū)分開(kāi),這里使用了JSON來(lái)進(jìn)行編碼,這是一種常用編碼形式,能通過(guò)不同key值剝離開(kāi)不同類(lèi)型的數(shù)據(jù)。
人臉表情識(shí)別后端通信代碼(2班):
from flask import Flask, request, send_file
import cv2import main
# from yolo_det import process_image_with_yolov5#創(chuàng)建實(shí)例
app = Flask(__name__)
# app.config['UPLOAD_FOLDER'] = 'shuru'@app.route('/detect_license_plate', methods=['POST'])
def detect_license_plate_route():# 獲取上傳的文件uploaded_file = request.files['file']# 保存上傳的文件到服務(wù)器uploaded_file.save('../image.jpg')# 調(diào)用情緒分析函數(shù)result_image_path = detect_license_plate('../image.jpg')# 返回檢測(cè)結(jié)果圖片return send_file(result_image_path, mimetype='image/jpeg')def detect_license_plate(url: str):image_path = url# check_requirements(exclude=('pycocotools', 'thop'))# with torch.no_grad():result_image = main.detect(image_path)cv2.imwrite('./output/output.jpg', result_image)return './output/output.jpg'if __name__ == '__main__':app.run(host='0.0.0.0', port=5000)
服務(wù)器部署具體實(shí)現(xiàn)
? 這里實(shí)現(xiàn)的是把項(xiàng)目部署到云服務(wù)器上運(yùn)行,摒棄掉了原來(lái)在本機(jī)上跑服務(wù)器程序。
這一塊是遇到問(wèn)題最多的模塊,先說(shuō)一下為什么要進(jìn)行云服務(wù)器的部署,先上一張圖,如下:
? 這也是我和支哥秀姐想了最久的問(wèn)題之一,昨晚背計(jì)網(wǎng)面試題的時(shí)候靈光一現(xiàn),終于弄清楚是咋回事了。接下來(lái)就詳細(xì)說(shuō)說(shuō)。
? 學(xué)了計(jì)網(wǎng)的同學(xué)應(yīng)該就知道私有IP地址,也就是私網(wǎng),私網(wǎng)是不能直接訪問(wèn)的,如果想要訪問(wèn)需要做一個(gè)NAT網(wǎng)絡(luò)轉(zhuǎn)換器,轉(zhuǎn)換到公網(wǎng)進(jìn)行訪問(wèn),但這就不是代碼層面的事了,所以無(wú)論你是自己手機(jī)的熱點(diǎn)(每臺(tái)手機(jī)的移動(dòng)網(wǎng)絡(luò)都是私網(wǎng))、還是校園網(wǎng)亦或者是公司內(nèi)部網(wǎng)絡(luò),這都是內(nèi)網(wǎng),外界無(wú)法直接訪問(wèn),所以當(dāng)時(shí)存在一個(gè)情況就是必須要連阿支哥的熱點(diǎn)才能訪問(wèn)服務(wù)器(服務(wù)器也連著支哥熱點(diǎn)),這就是因?yàn)樗麄兇藭r(shí)在同一個(gè)局域網(wǎng)內(nèi),不存在找ip地址的需求。
? 而當(dāng)你配置了云服務(wù)器,云服務(wù)器提供的ip是公網(wǎng),你可以設(shè)置任何人都可以訪問(wèn),那么當(dāng)我發(fā)網(wǎng)絡(luò)請(qǐng)求的時(shí)候,通過(guò)DNS就能找到對(duì)應(yīng)IP,發(fā)送請(qǐng)求不會(huì)收到阻攔,這也是當(dāng)時(shí)車(chē)牌識(shí)別系統(tǒng)什么網(wǎng)絡(luò)都能使用的原因。
### 服務(wù)器部署流程
1、買(mǎi)一個(gè)服務(wù)器(有免費(fèi)體驗(yàn),需要身份驗(yàn)證一下,我用的就是免費(fèi)30天的)
2、配置信息,這里選一個(gè)Linux操作系統(tǒng),帶寬這些都可以隨意設(shè)置一下,影響不大,但是登陸密碼用戶名這些一定要記住!!!
3、創(chuàng)建一個(gè)實(shí)例,可以看到畫(huà)圈處就是你的IP地址,記得到時(shí)用公有IP地址。
4、通過(guò)FinalShell本地操控服務(wù)器,服務(wù)器本質(zhì)也是一臺(tái)電腦,你想要操作它就需要一些遠(yuǎn)程連接軟件,用本地的遠(yuǎn)程連接軟件連接到服務(wù)器,即可在本地操作你的服務(wù)器,這里推薦使用FinalShell,根據(jù)下圖步驟即可建立連接,同理也可以操作你的虛擬機(jī)。
5、部署項(xiàng)目
? 接下來(lái)就是把后端代碼運(yùn)行在服務(wù)器就行了,但是可以看到,在這里我們操作服務(wù)器只能用命令行的形式,用這種方式把項(xiàng)目上傳配置環(huán)境運(yùn)行未免過(guò)于麻煩,所以,我們就需要用一個(gè)非常方便的軟件——寶塔。
寶塔官網(wǎng)
找到一個(gè)linux面板的下載命令在FinalShell運(yùn)行即可。安裝完成如圖所示。
根據(jù)外網(wǎng)面板地址瀏覽器直接訪問(wèn)即可,如果沒(méi)法訪問(wèn),不要擔(dān)心,因?yàn)槲覀兎?wù)器的網(wǎng)絡(luò)不可能這么隨便全都能隨便訪問(wèn),所以我們必須給寶塔所需要的8888端口開(kāi)一個(gè)安全訪問(wèn)權(quán)限。如下圖:
手動(dòng)添加這兩個(gè)端口的授權(quán),8000是寶塔用的端口,5000是后端程序用的端口。
打開(kāi)之后再次進(jìn)入輸入FinalShell中顯示的賬號(hào)密碼即可進(jìn)入寶塔,如下圖:
現(xiàn)在就可以非常easy的操控你的服務(wù)器了!
先把我們的項(xiàng)目上傳到服務(wù)器,操作如下:
步驟三就是我們上傳上來(lái)的文件,接著就可以配置環(huán)境了。
下載python,操作如下:
下載依賴包:
首先先在你的項(xiàng)目里運(yùn)行以下命令得到一個(gè)依賴包文件,包含你項(xiàng)目中使用的所有依賴包。
pip freeze > requirements.txt
添加項(xiàng)目,選擇你的項(xiàng)目文件以及第一個(gè)要啟動(dòng)的程序,也就是那個(gè)通信的代碼文件,等待下載依賴包即可(這個(gè)時(shí)間有點(diǎn)長(zhǎng),因?yàn)镻ython太方便了,所以包實(shí)在太多了)
配置好后,運(yùn)行項(xiàng)目,如下:
這里還有一個(gè)小問(wèn)題需要處理,就是項(xiàng)目里的路徑需要改為服務(wù)器本地的絕對(duì)路徑,相對(duì)路徑會(huì)出現(xiàn)報(bào)錯(cuò),目前還不知曉原因,這一塊報(bào)錯(cuò)的時(shí)候作者直接躺了,是秀哥給解決的。
做完這些,服務(wù)器模塊就大工告成啦!
移動(dòng)端具體實(shí)現(xiàn)
車(chē)牌識(shí)別移動(dòng)端通信部分實(shí)現(xiàn)代碼(1班):
func fetchDataFromServer1() {// 定義請(qǐng)求的 URLlet url = "http://你的ip:5000/detect_license_plate"// 將圖片轉(zhuǎn)換為 JPEG 格式,并存儲(chǔ)為二進(jìn)制數(shù)據(jù)let imageData = self.image.jpegData(compressionQuality: 1.0)// 創(chuàng)建一個(gè) URLRequest 對(duì)象,設(shè)置超時(shí)時(shí)間、請(qǐng)求方法和 URLvar urlRequest = URLRequest(url: try! url.asURL())urlRequest.timeoutInterval = 30urlRequest.httpMethod = HTTPMethod.post.rawValue// 使用 Alamofire 發(fā)送一個(gè)帶有 multipart form data 的上傳請(qǐng)求AF.upload(multipartFormData: { multipartFormData inif let imageData = imageData {// 將圖片數(shù)據(jù)作為文件附加到請(qǐng)求中,并指定文件名和 MIME 類(lèi)型multipartFormData.append(imageData, withName: "file", fileName: "image.jpg", mimeType: "image/jpeg")}}, with: urlRequest).validate() // 對(duì)服務(wù)器的響應(yīng)進(jìn)行驗(yàn)證.responseJSON { response in// 處理響應(yīng)print(response)switch response.result {case .success:if let data = response.value{// 解析響應(yīng)數(shù)據(jù)為 JSON,并提取其中的字符串?dāng)?shù)據(jù)let JSONData = JSON(data)let customString = "\(JSONData["custom_string"].stringValue)"let imageString = "\(JSONData["image_data"].stringValue)"print(customString)// 將 base64 編碼的圖片數(shù)據(jù)解碼為 Data,然后將其轉(zhuǎn)換為 UIImage 對(duì)象let imageData = Data(base64Encoded: imageString)!let resultImage = UIImage(data: imageData)!print("成功")print(resultImage.description)print(customString)// 更新界面和數(shù)據(jù),將解碼后的圖片顯示到 imageView,將字符串?dāng)?shù)據(jù)顯示到 ansLabelself.resimage = resultImageself.vc.ansText = customStringself.vc.ansLabel.text = self.vc.ansTextself.vc.lastImage = self.resimageself.vc.imageView.image = self.vc.lastImageself.vc.modalPresentationStyle = .fullScreenself.present(self.vc, animated: true)}case .failure(let error):// 處理錯(cuò)誤情況print("Error: \(error)")print(response)}}
}
面部表情識(shí)別移動(dòng)端通信部分實(shí)現(xiàn)代碼(2班):
func fetchDataFromServer() {// 定義請(qǐng)求的 URLlet url = "http://你的ip:5000/detect_license_plate"// 將圖片轉(zhuǎn)換為 JPEG 格式,并存儲(chǔ)為二進(jìn)制數(shù)據(jù)let imageData = self.image.jpegData(compressionQuality: 1.0)// 使用 Alamofire 發(fā)送一個(gè)帶有 multipart form data 的上傳請(qǐng)求AF.upload(multipartFormData: { multipartFormData inif let imageData = imageData {// 將圖片數(shù)據(jù)作為文件附加到請(qǐng)求中,并指定文件名和 MIME 類(lèi)型multipartFormData.append(imageData, withName: "file", fileName: "image.jpg", mimeType: "image/jpeg")}}, to: url).validate() // 對(duì)服務(wù)器的響應(yīng)進(jìn)行驗(yàn)證.responseData { response in// 處理響應(yīng)switch response.result {case .success(let data):if let resultImage = UIImage(data: data) {// 在這里使用 resultImage,它是從服務(wù)器返回的 UIImage// 可以將 resultImage 顯示在 UIImageView 中,或者進(jìn)行其他處理print("成功")print(resultImage.description)// 更新界面和數(shù)據(jù),將 resultImage 顯示到 imageViewself.resimage = resultImageself.vc.lastImage = self.resimageself.vc.imageView.image = self.vc.lastImageself.vc.modalPresentationStyle = .fullScreen// 設(shè)置 block 回調(diào)函數(shù)self.block = {self.flag = true}self.vc.block = self.blockself.present(self.vc, animated: true)}case .failure(let error):// 處理錯(cuò)誤情況print("Error: \(error)")print(response)}}
}
如有錯(cuò)誤,歡迎指正。
轉(zhuǎn)載請(qǐng)配上原文引用。