【OpenCV学习】(九)目标识别之车辆检测与计数
【OpenCV學習】(九)目標識別之車輛檢測及計數
背景
本篇將具體介紹一個實際應用項目——車輛檢測及計數,在交通安全中是很重要的一項計數;當然,本次完全采用OpenCV進行實現,和目前落地的采用深度學習的算法并不相同,但原理是一致的;本篇將從基礎開始介紹,一步步完成車輛檢測計數的項目;
一、圖像輪廓
本質:具有相同顏色或強度的連續點的曲線;
作用:
1、可用于圖形分析;
2、應用于物體的識別與檢測;
注意點:
1、為了檢測的準確性,需要先對圖像進行二值化或Canny操作;
2、畫輪廓的時候回修改輸入的圖像,需要先深拷貝原圖;
輪廓查找的函數原型:
findContours(img,mode,ApproximationMode…)
-
mode
RETR_EXTERNAL=0,表示只檢測外輪廓;
RETR_LIST=1,檢測的輪廓不建立等級關系;(常用)
RETR_CCOMP=2,每層最多兩級;
RETR_TREE=3,按樹形結構存儲輪廓,從右到左,從大到小;(常用)
-
ApproximationMode
CHAIN_APPROX_BOBE:保存輪廓上所有的點;
CHAIN_APPROX_SIMPLE:只保存輪廓的角點;
代碼實戰:
img = cv2.imread('./contours1.jpeg') # 轉變成單通道 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化 ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY) # 輪廓查找 contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)contours輸出結果:
(array([[[ 0, 0]],[[ 0, 435]],[[345, 435]],[[345, 0]]], dtype=int32),)可以看出,我們找最外層輪廓,找出了一個矩形輪廓的四個點;
當然,我們不需要通過畫形狀來繪制輪廓,可以通過一個內置函數來繪制輪廓;
繪制輪廓函數原型:
drawContours(img,contours,contoursIdx,color,thickness,…)
- contours:表示保存輪廓的數組;
- contoursIdx:表示繪制第幾個輪廓,-1表示所有輪廓;
代碼案例:
img = cv2.imread('./contours1.jpeg') img2 = img.copy() # 轉變成單通道 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化 ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY) # 輪廓查找 contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)cv2.drawContours(img, contours, -1, (0, 0, 255), 1) cv2.drawContours(img2, contours, -1, (0, 0, 255), -1)cv2.imshow('org', img) cv2.imshow('org2', img2) cv2.waitKey(0)如上圖所示,左圖是線寬設置為1,右圖為線寬設置為-1,也就是填充的效果;
當然,OpenCV還提供了計算輪廓周長和面積的方法;
輪廓面積函數原型:
contourArea(contour)
輪廓周長函數原型:
arcLength(curve,closed)
- curve:表示輪廓;
- closed:是否是閉合的輪廓;
上述兩個函數比較簡單,在這就不做代碼演示了;
二、多邊形逼近與凸包
多邊形逼近函數原型:
approxPolyDP(curve,epsilon,closed)
- epslion:精度;
凸包的函數原型:
convexHull(points,clockwise,…)
- points:輪廓;
- clockwise:繪制方向,順時針或逆時針;(不重要)
首先我們看一下基于輪廓查找輸出的輪廓形狀:
可以看出輪廓點十分密集,接下來看一下基于多變形逼近和凸包的效果:
代碼案例:
img = cv2.imread('./hand.png') img2 = img.copy() # 轉變成單通道 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化 ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY) # 輪廓查找 contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)# cv2.drawContours(img, contours, -1, (0, 0, 255), 1) e = 20 approx = cv2.approxPolyDP(contours[0], e, True) # 多邊形逼近 approx = (approx, ) cv2.drawContours(img, approx, 0, (0, 0, 255), 3)hull = cv2.convexHull(contours[0]) hull = (hull, ) cv2.drawContours(img2, hull, 0, (0, 0, 255), 3) # 凸包cv2.imshow('org', img) cv2.imshow('org2', img2) cv2.waitKey(0)這里需要注意一點,繪制輪廓的函數對于輪廓的傳入需要為元組,需要將得到的數組放到一個元組中!
當然,多邊形逼近這里設置的精度為20,所以比較粗糙,設置小一些可以達到更好的效果;
三、外接矩形
外接矩陣分為最大外接矩陣和最小外接矩陣,如下圖所示:
最小外接矩陣還有一個功能,就是計算旋轉角度,從上圖的綠框應該可以很明顯看出;
最小外接矩陣函數原型:
minAreaRect(points)
返回值:起始點(x,y)、寬高(w,h)、角度(angle)
最大外接矩形函數原型:
boundingRect(array)
返回值:起始點(x,y)、寬高(w,h)
代碼案例:
img = cv2.imread('./hello.jpeg') img2 = img.copy() # 轉變成單通道 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化 ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY) # 輪廓查找 contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)# 獲取最小外接矩形 r = cv2.minAreaRect(contours[1]) box = cv2.boxPoints(r) # 提取其中的點 box = np.int0(box) # 將浮點型轉換為整型 cv2.drawContours(img, (box, ), 0, (0, 0, 255), 2)# 獲取最大外接矩形 x, y, w, h = cv2.boundingRect(contours[1]) cv2.rectangle(img2, (x, y), (x+w, y+h), (0, 0, 255), 2)cv2.imshow('org', img) cv2.imshow('org2', img2) cv2.waitKey(0)四、車輛統計實戰
涉及的知識點:
- 窗口展示
- 圖像、視頻的加載
- 基本圖形的繪制
- 基本圖像運算與處理
- 形態學
- 輪廓查找
實現流程:
加載視頻 —— 通過形態學識別車輛 —— 對車輛進行統計 —— 顯示統計信息
1、加載視頻
這里就是一個簡單加載視頻的實現:
cap = cv2.VideoCapture('video.mp4') while True:ret, frame = cap.read()if(ret == True):cv2.imshow('video', frame)key = cv2.waitKey(1)if(key == 27): # Esc退出breakcap.release() cv2.destroyAllWindows()2、去除背景
函數原型:
createBackgroundSubtractorMOG()
- history:緩沖,表示多少毫秒,可不指定參數,用默認的即可;
具體實現原理比較復雜,用到了一些視頻序列關聯信息,把像素值不變的認為是背景;
注意:在opencv中已經不支持該函數,而是用createBackgroundSubtractorMOG2()替代;如果需要使用可以安裝opencv_contrib模塊,在其中的bgsegm中保留了該函數;
代碼實現:
cap = cv2.VideoCapture('video.mp4')bgsubmog = cv2.bgsegm.createBackgroundSubtractorMOG()while True:ret, frame = cap.read()if(ret == True):# 灰度處理cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)# 高斯去噪blur = cv2.GaussianBlur(frame, (3, 3), 5)mask = bgsubmog.apply(blur)cv2.imshow('video', mask)key = cv2.waitKey(1)if(key == 27): # Esc退出breakcap.release() cv2.destroyAllWindows()這里盡量采用舊版的MOG函數,新版的MOG2函數比較精細,會將樹葉等信息輸出,去除效果沒那么好;
3、形態處理
這里主要是為了處理一些小的噪聲點以及目標中的黑色塊;
代碼實現:
cap = cv2.VideoCapture('video.mp4')bgsubmog = cv2.bgsegm.createBackgroundSubtractorMOG()# 形態學kernel kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))while True:ret, frame = cap.read()if(ret == True):# 灰度處理cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)# 高斯去噪blur = cv2.GaussianBlur(frame, (3, 3), 5)mask = bgsubmog.apply(blur)# 腐蝕erode = cv2.erode(mask, kernel)# 膨脹dilate = cv2.dilate(erode, kernel, 3)# 閉操作close = cv2.morphologyEx(dilate, cv2.MORPH_CLOSE, kernel)close = cv2.morphologyEx(close, cv2.MORPH_CLOSE, kernel)contours, h = cv2.findContours(close, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE,)for (i, c) in enumerate(contours):(x, y, w, h) = cv2.boundingRect(c)cv2.rectangle(frame, (x, y), (x+w, y+h), (0,0,255), 2)cv2.imshow('video', frame)key = cv2.waitKey(1)if(key == 27): # Esc退出breakcap.release() cv2.destroyAllWindows()從圖中效果來看,還是會有很多小的檢測框,接下來就是處理重合檢測框以及去掉一些多余的檢測框,類似于NMS去重,當然原理還不太一樣;
4、車輛統計
首先需要過濾一些小的矩形,已經檢測框的長和寬,設定一些閾值即可;
代碼實現:
cap = cv2.VideoCapture('video.mp4')bgsubmog = cv2.bgsegm.createBackgroundSubtractorMOG()# 保存車輛中心點信息 cars = [] # 統計車的數量 car_n = 0# 形態學kernel kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))while True:ret, frame = cap.read()if(ret == True):# 灰度處理cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)# 高斯去噪blur = cv2.GaussianBlur(frame, (3, 3), 5)mask = bgsubmog.apply(blur)# 腐蝕erode = cv2.erode(mask, kernel)# 膨脹dilate = cv2.dilate(erode, kernel, 3)# 閉操作close = cv2.morphologyEx(dilate, cv2.MORPH_CLOSE, kernel)close = cv2.morphologyEx(close, cv2.MORPH_CLOSE, kernel)contours, h = cv2.findContours(close, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE,)# 畫一條線cv2.line(frame, (10, 550), (1200, 550), (0, 255, 255), 3)for (i, c) in enumerate(contours):(x, y, w, h) = cv2.boundingRect(c)# 過濾小的檢測框isshow = (w >= 90) and (h >= 90)if(not isshow):continue# 保存中心點信息cv2.rectangle(frame, (x, y), (x+w, y+h), (0,0,255), 2)centre_p = (x + int(w/2), y + int(h/2))cars.append(centre_p)cv2.circle(frame, (centre_p), 5, (0,0,255), -1)for (x, y) in cars:if(593 < y < 607):car_n += 1 cars.remove((x, y)) cv2.putText(frame, "Cars Count:" + str(car_n), (500, 60), cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 0, 255), 5) cv2.imshow('video', frame)key = cv2.waitKey(1)if(key == 27): # Esc退出breakcap.release() cv2.destroyAllWindows()簡單的效果已經出來了,對于大部分車輛都能夠很好的檢測并且計數了;
存在問題:
由于是用中心點與線的距離來判斷,車速過慢可能會在兩幀內重復計數,車速過快可能會計數不到;這就是傳統算法存在的一個問題,基于深度學習的方法可以很好解決這些問題,可關注目標跟蹤實戰的那一篇文章!
總結
項目到這里就介紹了,通過該項目主要是將所學的知識點進行串聯,重點在于形態學的運用!當然這個效果可能達不到實際應用的標準,這也是傳統算法的一個弊端;有能力的可以采用深度學習的方法進行實現,也可以關注我后續的目標跟蹤是實現車輛計數,效果會遠比這個好。
總結
以上是生活随笔為你收集整理的【OpenCV学习】(九)目标识别之车辆检测与计数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vlc连接视频(摄像头)方法步骤(一)
- 下一篇: 实验五 JR指令设计实验【计算机组成原理