【yolov3目标检测】(3) opencv+yolov3 检测交通路况,附python完整代码
各位同學好,今天和大家分享一下如何使用 opencv 調用 yolov3 模型,加載網絡權重,很方便地實現 yolov3 目標檢測。先放張圖看效果。
使用的網上找的行車記錄儀視頻做測試,數據集采用COCO數據集,檢測效果還是不錯的。
1. 預先準備
首先需要導入 COCO 數據集的分類名文件 'coco.names',以及yolov3的網絡結構 'yolo.cfg',網絡的權重參數 'yolo.weights',這些文件以及本案例的代碼我給大家都提供好了,有需要的自取。
鏈接:https://pan.baidu.com/s/12iPJTjiN7SIBJ7hpHomn_w?
提取碼:p548
yolov3 使用 Darknet53 網絡模型,這個網絡我之前復現過,感興趣的可以看一下:https://blog.csdn.net/dgvv4/article/details/121997986
使用?cv2.dnn.readNetFromDarknet() 從opencv中讀取網絡模型,傳入網絡結構和權重參數。
由于我這個視頻比較短,因此設置視頻重復播放,使用 cv2.CAP_PROP_POS_FRAMES 獲取當前視頻所在第幾幀,使用 cv2.CAP_PROP_FRAME_COUNT 獲取該視頻一共有多少幀。如果播放到了最后一幀,那就讓當前幀=0,從頭開始。
代碼如下,net 配置完成,播放視頻圖像。
import numpy as np
import cv2
import time#(1)加載預訓練的COCO數據集
classesFile = 'coco.names' # 指定coco數據集分類名所在路徑
classNames = [] # 創建列表,存放coco數據集的分類名稱# 打開數據集名稱的文件
with open(classesFile, 'rt') as f: #讀取文本文件classNames = f.read().rstrip('\n').split('\n') # 通過換行符來拆分,再讀入# 加載yolov3結構cfg文件
modelConfiguration = 'yolov3.cfg'
# 加載yolov3網絡權重
modelWeights = 'yolov3.weights'#(2)構建網絡結構
# 導入darknet53網絡,傳入cfg文件和網絡權重
net = cv2.dnn.readNetFromDarknet(modelConfiguration, modelWeights)
# 申明使用opencv作為后端
net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV)
# 申明使用CPU計算
net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU)#(3)獲取攝像頭
videoFile = 'C:\\GameDownload\\Deep Learning\\yolov3video.mp4'
cap = cv2.VideoCapture(videoFile) # 0代表電腦自帶的攝像頭,代表外接攝像頭 pTime = 0 # 設置第一幀開始處理的起始時間#(4)處理幀圖像
while True:# 接收圖片是否導入成功、幀圖像success, img = cap.read()# 改變圖像大小img = cv2.resize(img, (1280,720))# 視頻較短,循環播放if cap.get(cv2.CAP_PROP_POS_FRAMES) == cap.get(cv2.CAP_PROP_FRAME_COUNT):# 如果當前幀==總幀數,那就重置當前幀為0cap.set(cv2.CAP_PROP_POS_FRAMES, 0)#(5)顯示圖像# 查看FPScTime = time.time() #處理完一幀圖像的時間fps = 1/(cTime-pTime)pTime = cTime #重置起始時間# 在視頻上顯示fps信息,先轉換成整數再變成字符串形式,文本顯示坐標,文本字體,文本大小cv2.putText(img, str(int(fps)), (70,50), cv2.FONT_HERSHEY_PLAIN, 3, (255,0,0), 3) # 顯示圖像,輸入窗口名及圖像數據cv2.imshow('img', img) if cv2.waitKey(20) & 0xFF==27: #每幀滯留20毫秒后消失,ESC鍵退出break# 釋放視頻資源
cap.release()
cv2.destroyAllWindows()
2. 獲取檢測框信息
2.1 確定輸入及輸出
先看下面代碼中的第(6)步,圖像傳入神經網絡之前先進行預處理?cv2.dnn.blobFromImage(),包括減均值,比例縮放,裁剪,交換通道等,返回一個4通道的blob(blob可以簡單理解為一個N維的數組)。之后使用 net.setInput() 將blob類型圖像作為網絡輸入。
cv2.dnn.blobFromImage(img, scalefactor, size, mean, swapRB, crop, ddepth)'''
image: 輸入圖像
scalefactor: 圖像各通道數值的縮放比例,默認=1
size: 輸出圖像的空間尺寸,如size=(200,300)表示高h=300,寬w=200
mean: 用于各通道減去的值,以降低光照的影響,例:(image為BGR的3通道的圖像,mean=[104.0, 177.0, 123.0],表示B通道的值-104,G-177,R-123)
swapRB: 交換RB通道,默認為False。(cv2.imread讀取的是彩圖是BGR通道)
crop: 圖像裁剪,默認為False。當值為True時,先按比例縮放,然后從中心裁剪成size尺寸
ddepth: 輸出的圖像深度,可選CV_32F 或者 CV_8U.
'''
由下圖的 Darknet53 網絡結構圖可知,網絡有三個輸出層,輸出的shape分別為?[52,52,255] 用于預測小目標,[26,26,255] 用于預測中等大小的目標,[13,13,255] 用于預測大目標。因此我們通過?net.getUnconnectedOutLayers() 就能知道這三個輸出層處于網絡中的第幾層,得到的返回結果是?[200, 227, 254]。通過?net.getLayerNames() 只要輸入層的索引就能得到該索引所對應的層的名稱。得到輸出層的層名稱后,將其傳入 net.forward() 中,就能找到輸出層的輸出結果。
到這里就完成了網絡模型的輸入 blob,得到了模型的三個輸出?outputs,打印輸出結果的相關信息如下。以 outputs[0].shape =(300,85)為例,?300代表檢測框的數量,85代表:中心點坐標 x,y;框的寬高 w,h;置信度 c;80 個分類各自的概率
print(outputs[0].shape) # (300, 85)
print(outputs[1].shape) # (1200, 85)
print(outputs[2].shape) # (4800, 85)
print(outputs[0][0]) # 打印第0個檢測框所包含的信息'''
(300, 85)
(1200, 85)
(4800, 85)
[4.9195103e-02 5.5935599e-02 6.5290880e-01 1.8459144e-01 8.2010246e-080.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+000.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+000.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+000.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+000.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+000.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+000.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+000.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+000.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+000.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+000.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+000.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+000.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+000.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+000.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+000.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00]
'''
2.2 獲取檢測框信息
接下來我們就看到下面代碼中的第(4)步,現在有了每個區域的預測結果 outputs,需要從中找到和每個物體對應的最合適的檢測框。
注意一下,img.shape 是先指定圖像的高,再指定寬,(h, w, c),不要搞錯了。
其中 output 遍歷三個輸出層,每個輸出層包含n個檢測框,每個框包含85項信息。det 遍歷每一層的n個檢測框,det 包含85項信息。我們需要找到每個框屬于哪個分類,以及該框屬于該分類的概率 confidence。如果該分類概率大于閾值 confThreshold 證明找到了,這時候就將該框的左上角坐標(x,y),框的寬w和高h,及置信度 confidence 保存下來。
這里需要注意的是,每個框的85項信息中的其四個信息:中心坐標和寬高,都是歸一化之后的比例坐標和比例寬高。需要將比例寬高乘上原圖像寬高才能得到檢測框的真實寬高。
在第二節的代碼中補充。
import numpy as np
import cv2
import time#(1)加載預訓練的COCO數據集
classesFile = 'coco.names' # 指定coco數據集分類名所在路徑
classNames = [] # 創建列表,存放coco數據集的分類名稱# 打開數據集名稱的文件
with open(classesFile, 'rt') as f: #讀取文本文件classNames = f.read().rstrip('\n').split('\n') # 通過換行符來拆分,再讀入# 加載yolov3結構cfg文件
modelConfiguration = 'yolov3.cfg'
# 加載yolov3網絡權重
modelWeights = 'yolov3.weights'# 確定輸入圖像的寬和高
wInput, hInput = 320, 320# 自定義目標檢測的最小置信度
confThreshold = 0.5#(2)構建網絡結構
# 導入darknet53網絡,傳入cfg文件和網絡權重
net = cv2.dnn.readNetFromDarknet(modelConfiguration, modelWeights)
# 申明使用opencv作為后端
net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV)
# 申明使用CPU計算
net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU)#(3)獲取攝像頭
videoFile = 'C:\\GameDownload\\Deep Learning\\trafficvideo2.mp4'
cap = cv2.VideoCapture(videoFile) # 0代表電腦自帶的攝像頭,代表外接攝像頭 pTime = 0 # 設置第一幀開始處理的起始時間#(4)定義函數用于檢測目標,獲取檢測框信息,以及分類類別
def findObjects(outputs, img):# 圖像的高度、寬度、通道數hT, wT, cT = img.shape# 定義一個列表存放檢測框的中心點坐標和寬高bbox = []# 定義列表存放分類的名稱的索引classIds = []# 定義列表存放置信度confs = [] # 如果找到目標了,就將檢測框的信息存放起來# 遍歷三個輸出層for output in outputs:# 遍歷輸出層的85項信息for det in output: # det是數組類型# 在80個分類中找到哪個分類的值是最高的score = det[5:] # 忽略檢測框的x,y,w,h,c# 找到分類值最大對應的索引號classId = np.argmax(score)# 找到分類概率最大值的索引對應的值confidence = score[classId]# 如果檢測置信度大于規定的閾值,表明檢測到了物體if confidence > confThreshold:# 記錄檢測框的寬和高,這里的寬高是歸一化之后的比例寬度和高度w, h = int(det[2]*wT), int(det[3]*hT) # 比例寬高轉為像素寬高,像素寬高是整數# 記錄檢測框的左上角坐標x, y = det[0]*wT-w//2, det[1]*hT-h//2# 將檢測框的信息保存起來bbox.append([x, y, w, h])# 將目標屬于哪個類別的索引保存下來classIds.append(classId)# 保存檢測框的置信度,檢測出某個目標的概率confs.append(float(confidence))# 打印檢測框信息print('classids:', classIds, 'confidence:', confs, 'bbox:', bbox)#(5)處理幀圖像
while True:# 接收圖片是否導入成功、幀圖像success, img = cap.read()# 改變圖像大小img = cv2.resize(img, (1280,720))# 視頻較短,循環播放if cap.get(cv2.CAP_PROP_POS_FRAMES) == cap.get(cv2.CAP_PROP_FRAME_COUNT):# 如果當前幀==總幀數,那就重置當前幀為0cap.set(cv2.CAP_PROP_POS_FRAMES, 0)#(6)將img圖片轉換成blob塊類型,網絡只接受這種類型# 輸入img圖像,,圖像寬w高h,默認參數[0,0,0],,blob = cv2.dnn.blobFromImage(img, 1/255, (wInput,hInput), [0,0,0], 1, crop=False)# 將轉換類型后的圖像作為輸入數據net.setInput(blob)# 獲得網絡各層的名稱,由于網絡會輸出最后三層的結果,用于定位輸出層layerNames = net.getLayerNames() # 得到網絡所有層的名稱# 提取輸出層,返回輸出層是第幾層,層數是從1開始,索引是從0開始# net.getUnconnectedOutLayers() # [200, 227, 254]outputNames = [] # 存放輸出層名稱# 得到輸出層在網絡中屬于第幾層for outindex in net.getUnconnectedOutLayers():# 得到輸出層的名稱,outindex是第幾層(從1開始),傳入的是索引(從0開始)outputNames.append(layerNames[outindex-1])# 獲取輸出層返回結果outputs = net.forward(outputNames)print(outputs[0].shape) # (300, 85) 300代表檢測框的數量,85代表:中心點坐標x,y,框的寬高w,h,置信度c,80個分類各自的概率print(outputs[1].shape) # (1200, 85)print(outputs[2].shape) # (4800, 85)print(outputs[0][0]) # 打印第0個檢測框所包含的信息#(7)目標檢測findObjects(outputs, img) #(8)顯示圖像# 查看FPScTime = time.time() #處理完一幀圖像的時間fps = 1/(cTime-pTime)pTime = cTime #重置起始時間# 在視頻上顯示fps信息,先轉換成整數再變成字符串形式,文本顯示坐標,文本字體,文本大小cv2.putText(img, str(int(fps)), (70,50), cv2.FONT_HERSHEY_PLAIN, 3, (255,0,0), 3) # 顯示圖像,輸入窗口名及圖像數據cv2.namedWindow("img", 0) # 窗口大小可調整cv2.imshow('img', img) if cv2.waitKey(20) & 0xFF==27: #每幀滯留20毫秒后消失,ESC鍵退出break# 釋放視頻資源
cap.release()
cv2.destroyAllWindows()
打印得到的檢測框信息
classids: [2, 7, 7, 2, 2, 2, 2, 2, 2]
confidence: [0.8474083542823792, 0.6886539459228516, 0.7003887891769409, 0.9768345952033997, 0.7150803804397583, 0.7982962131500244, 0.7913717031478882, 0.9718230962753296, 0.8747612833976746]
bbox: [[769.3665313720703, 398.1743869781494, 110, 88], [348.8137969970703, 387.51572608947754, 98, 70], [347.2083282470703, 388.56964111328125, 101, 71], [453.0018081665039, 409.030611038208, 32, 26], [625.3595275878906, 397.98907947540283, 67, 57], [681.5968627929688, 405.41797828674316, 37, 36], [694.1919250488281, 410.92028617858887, 37, 30], [760.8668212890625, 402.86146450042725, 124, 75], [777.7303924560547, 401.2068338394165, 112, 77]]
3. 顯示預測框,完成目標檢測
進行完上面的操作后,我們現在得到一個物體上可能有很多的檢測框都滿足條件,接下來采用 NMS 非極大值抑制?cv2.dnn.NMSBoxes(),搜索出局部最大值,將置信度最大的框保存,其余剔除,確保每個目標至少有一個框。
cv2.dnn.NMSBoxes(bboxes, scores, score_threshold, nms_threshold, eta=None, top_k=None)'''
bboxes:檢測框信息 [x,y,w,h]
scores:每個待處理檢測框的置信度
score_threshold:用于過濾檢測框的置信度閾值
nms_threshold:NMS閾值
eta:自適應閾值公式中的相關系數
top_k: 如果 top_k>0,則保留最多 top_k 個邊界框索引值
'''
接下去就能使用矩形框繪制函數?cv2.rectangle() 把每個檢測框繪制出來。
在上一節的代碼中補充。
import numpy as np
import cv2
import time#(1)加載預訓練的COCO數據集
classesFile = 'C:\\Users\\admin\\.spyder-py3\\test\\機器視覺\\yolov3\\coco.names' # 指定coco數據集分類名所在路徑
classNames = [] # 創建列表,存放coco數據集的分類名稱# 打開數據集名稱的文件
with open(classesFile, 'rt') as f: #讀取文本文件classNames = f.read().rstrip('\n').split('\n') # 通過換行符來拆分,再讀入# 加載yolov3結構cfg文件
modelConfiguration = 'yolov3.cfg'
# 加載yolov3網絡權重
modelWeights = 'yolov3.weights'# 確定輸入圖像的寬和高
wInput, hInput = 320, 320# 自定義目標檢測的最小置信度
confThreshold = 0.5# 自定義非極大值抑制的參數
nms_threshold = 0.3#(2)構建網絡結構
# 導入darknet53網絡,傳入cfg文件和網絡權重
net = cv2.dnn.readNetFromDarknet(modelConfiguration, modelWeights)
# 申明使用opencv作為后端
net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV)
# 申明使用CPU計算
net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU)#(3)獲取攝像頭
videoFile = 'C:\\GameDownload\\Deep Learning\\trafficvideo1.mp4'
cap = cv2.VideoCapture(videoFile) # 0代表電腦自帶的攝像頭,代表外接攝像頭 #(4)定義函數用于檢測目標,獲取檢測框信息,以及分類類別
def findObjects(outputs, img):# 圖像的高度、寬度、通道數hT, wT, cT = img.shape # 先保存高度,再保存寬度# 定義一個列表存放檢測框的中心點坐標和寬高bbox = []# 定義列表存放分類的名稱的索引classIds = []# 定義列表存放置信度confs = [] # 如果找到目標了,就將檢測框的信息存放起來# 遍歷三個輸出層for output in outputs:# 遍歷輸出層的85項信息for det in output: # det是數組類型# 在80個分類中找到哪個分類的值是最高的score = det[5:] # 忽略檢測框的x,y,w,h,c# 找到分類值最大對應的索引號classId = np.argmax(score)# 找到分類概率最大值的索引對應的值confidence = score[classId]# 如果檢測置信度大于規定的閾值,表明檢測到了物體if confidence > confThreshold:# 記錄檢測框的寬和高,這里的寬高是歸一化之后的比例寬度和高度w, h = int(det[2]*wT), int(det[3]*hT) # 比例寬高轉為像素寬高,像素寬高是整數# 記錄檢測框的左上角坐標x, y = int(det[0]*wT-w/2), int(det[1]*hT-h/2)# 將檢測框的信息保存起來bbox.append([x, y, w, h])# 將目標屬于哪個類別的索引保存下來classIds.append(classId)# 保存檢測框的置信度,檢測出某個目標的概率confs.append(float(confidence))#(5)消除重疊的矩形框,非極大值抑制indices = cv2.dnn.NMSBoxes(bbox, confs, confThreshold, nms_threshold) # 返回檢測框的索引 # 遍歷索引繪制矩形框for i in indices:# 在所有包含目標的矩形框中找到最符合的矩形框box = bbox[i]# 提取矩形框的信息x, y, w, h = box[0], box[1], box[2], box[3]# 繪制矩形框cv2.rectangle(img, (x,y), (x+w,y+h), (255,255,0), 2)# 顯示文本cv2.putText(img, f'{classNames[classIds[i]]}',(x,y+h+18), cv2.FONT_HERSHEY_COMPLEX, 0.8, (0,255,0), 2)cv2.putText(img, f'{int(confs[i]*100)}%',(x,y-8), cv2.FONT_HERSHEY_COMPLEX, 0.8, (0,0,255), 2) #(6)處理幀圖像
while True:# 接收圖片是否導入成功、幀圖像success, img = cap.read()# 改變圖像大小img = cv2.resize(img, (1280,720))# 視頻較短,循環播放if cap.get(cv2.CAP_PROP_POS_FRAMES) == cap.get(cv2.CAP_PROP_FRAME_COUNT):# 如果當前幀==總幀數,那就重置當前幀為0cap.set(cv2.CAP_PROP_POS_FRAMES, 0)#(7)將img圖片轉換成blob塊類型,網絡只接受這種類型# 輸入img圖像,,圖像寬w高h,默認參數[0,0,0],,blob = cv2.dnn.blobFromImage(img, 1/255, (wInput,hInput), [0,0,0], 1, crop=False)# 將轉換類型后的圖像作為輸入數據net.setInput(blob)# 獲得網絡各層的名稱,由于網絡會輸出最后三層的結果,用于定位輸出層layerNames = net.getLayerNames() # 得到網絡所有層的名稱# 提取輸出層,返回輸出層是第幾層,層數是從1開始,索引是從0開始# net.getUnconnectedOutLayers() # [200, 227, 254]outputNames = [] # 存放輸出層名稱# 得到輸出層在網絡中屬于第幾層for outindex in net.getUnconnectedOutLayers():# 得到輸出層的名稱,outindex是第幾層(從1開始),傳入的是索引(從0開始)outputNames.append(layerNames[outindex-1])# 獲取輸出層返回結果outputs = net.forward(outputNames)#(8)目標檢測findObjects(outputs, img) #(9)顯示圖像 # 顯示圖像,輸入窗口名及圖像數據cv2.namedWindow("img", 0) # 窗口大小可調整cv2.imshow('img', img) if cv2.waitKey(1) & 0xFF==27: #每幀滯留20毫秒后消失,ESC鍵退出break# 釋放視頻資源
cap.release()
cv2.destroyAllWindows()
檢測結果如下:
總結
以上是生活随笔為你收集整理的【yolov3目标检测】(3) opencv+yolov3 检测交通路况,附python完整代码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【神经网络】(10) Resnet18、
- 下一篇: 【yolov4目标检测】(4) open