【OpenCV学习】(九)目标识别之车辆检测与计数
【OpenCV學(xué)習(xí)】(九)目標識別之車輛檢測及計數(shù)
背景
本篇將具體介紹一個實際應(yīng)用項目——車輛檢測及計數(shù),在交通安全中是很重要的一項計數(shù);當(dāng)然,本次完全采用OpenCV進行實現(xiàn),和目前落地的采用深度學(xué)習(xí)的算法并不相同,但原理是一致的;本篇將從基礎(chǔ)開始介紹,一步步完成車輛檢測計數(shù)的項目;
一、圖像輪廓
本質(zhì):具有相同顏色或強度的連續(xù)點的曲線;
作用:
1、可用于圖形分析;
2、應(yīng)用于物體的識別與檢測;
注意點:
1、為了檢測的準確性,需要先對圖像進行二值化或Canny操作;
2、畫輪廓的時候回修改輸入的圖像,需要先深拷貝原圖;
輪廓查找的函數(shù)原型:
findContours(img,mode,ApproximationMode…)
-
mode
RETR_EXTERNAL=0,表示只檢測外輪廓;
RETR_LIST=1,檢測的輪廓不建立等級關(guān)系;(常用)
RETR_CCOMP=2,每層最多兩級;
RETR_TREE=3,按樹形結(jié)構(gòu)存儲輪廓,從右到左,從大到小;(常用)
-
ApproximationMode
CHAIN_APPROX_BOBE:保存輪廓上所有的點;
CHAIN_APPROX_SIMPLE:只保存輪廓的角點;
代碼實戰(zhàn):
img = cv2.imread('./contours1.jpeg') # 轉(zhuǎn)變成單通道 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輸出結(jié)果:
(array([[[ 0, 0]],[[ 0, 435]],[[345, 435]],[[345, 0]]], dtype=int32),)可以看出,我們找最外層輪廓,找出了一個矩形輪廓的四個點;
當(dāng)然,我們不需要通過畫形狀來繪制輪廓,可以通過一個內(nèi)置函數(shù)來繪制輪廓;
繪制輪廓函數(shù)原型:
drawContours(img,contours,contoursIdx,color,thickness,…)
- contours:表示保存輪廓的數(shù)組;
- contoursIdx:表示繪制第幾個輪廓,-1表示所有輪廓;
代碼案例:
img = cv2.imread('./contours1.jpeg') img2 = img.copy() # 轉(zhuǎn)變成單通道 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)如上圖所示,左圖是線寬設(shè)置為1,右圖為線寬設(shè)置為-1,也就是填充的效果;
當(dāng)然,OpenCV還提供了計算輪廓周長和面積的方法;
輪廓面積函數(shù)原型:
contourArea(contour)
輪廓周長函數(shù)原型:
arcLength(curve,closed)
- curve:表示輪廓;
- closed:是否是閉合的輪廓;
上述兩個函數(shù)比較簡單,在這就不做代碼演示了;
二、多邊形逼近與凸包
多邊形逼近函數(shù)原型:
approxPolyDP(curve,epsilon,closed)
- epslion:精度;
凸包的函數(shù)原型:
convexHull(points,clockwise,…)
- points:輪廓;
- clockwise:繪制方向,順時針或逆時針;(不重要)
首先我們看一下基于輪廓查找輸出的輪廓形狀:
可以看出輪廓點十分密集,接下來看一下基于多變形逼近和凸包的效果:
代碼案例:
img = cv2.imread('./hand.png') img2 = img.copy() # 轉(zhuǎn)變成單通道 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)這里需要注意一點,繪制輪廓的函數(shù)對于輪廓的傳入需要為元組,需要將得到的數(shù)組放到一個元組中!
當(dāng)然,多邊形逼近這里設(shè)置的精度為20,所以比較粗糙,設(shè)置小一些可以達到更好的效果;
三、外接矩形
外接矩陣分為最大外接矩陣和最小外接矩陣,如下圖所示:
最小外接矩陣還有一個功能,就是計算旋轉(zhuǎn)角度,從上圖的綠框應(yīng)該可以很明顯看出;
最小外接矩陣函數(shù)原型:
minAreaRect(points)
返回值:起始點(x,y)、寬高(w,h)、角度(angle)
最大外接矩形函數(shù)原型:
boundingRect(array)
返回值:起始點(x,y)、寬高(w,h)
代碼案例:
img = cv2.imread('./hello.jpeg') img2 = img.copy() # 轉(zhuǎn)變成單通道 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) # 將浮點型轉(zhuǎn)換為整型 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)四、車輛統(tǒng)計實戰(zhàn)
涉及的知識點:
- 窗口展示
- 圖像、視頻的加載
- 基本圖形的繪制
- 基本圖像運算與處理
- 形態(tài)學(xué)
- 輪廓查找
實現(xiàn)流程:
加載視頻 —— 通過形態(tài)學(xué)識別車輛 —— 對車輛進行統(tǒng)計 —— 顯示統(tǒng)計信息
1、加載視頻
這里就是一個簡單加載視頻的實現(xiàn):
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、去除背景
函數(shù)原型:
createBackgroundSubtractorMOG()
- history:緩沖,表示多少毫秒,可不指定參數(shù),用默認的即可;
具體實現(xiàn)原理比較復(fù)雜,用到了一些視頻序列關(guān)聯(lián)信息,把像素值不變的認為是背景;
注意:在opencv中已經(jīng)不支持該函數(shù),而是用createBackgroundSubtractorMOG2()替代;如果需要使用可以安裝opencv_contrib模塊,在其中的bgsegm中保留了該函數(shù);
代碼實現(xiàn):
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函數(shù),新版的MOG2函數(shù)比較精細,會將樹葉等信息輸出,去除效果沒那么好;
3、形態(tài)處理
這里主要是為了處理一些小的噪聲點以及目標中的黑色塊;
代碼實現(xiàn):
cap = cv2.VideoCapture('video.mp4')bgsubmog = cv2.bgsegm.createBackgroundSubtractorMOG()# 形態(tài)學(xué)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去重,當(dāng)然原理還不太一樣;
4、車輛統(tǒng)計
首先需要過濾一些小的矩形,已經(jīng)檢測框的長和寬,設(shè)定一些閾值即可;
代碼實現(xiàn):
cap = cv2.VideoCapture('video.mp4')bgsubmog = cv2.bgsegm.createBackgroundSubtractorMOG()# 保存車輛中心點信息 cars = [] # 統(tǒng)計車的數(shù)量 car_n = 0# 形態(tài)學(xué)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()簡單的效果已經(jīng)出來了,對于大部分車輛都能夠很好的檢測并且計數(shù)了;
存在問題:
由于是用中心點與線的距離來判斷,車速過慢可能會在兩幀內(nèi)重復(fù)計數(shù),車速過快可能會計數(shù)不到;這就是傳統(tǒng)算法存在的一個問題,基于深度學(xué)習(xí)的方法可以很好解決這些問題,可關(guān)注目標跟蹤實戰(zhàn)的那一篇文章!
總結(jié)
項目到這里就介紹了,通過該項目主要是將所學(xué)的知識點進行串聯(lián),重點在于形態(tài)學(xué)的運用!當(dāng)然這個效果可能達不到實際應(yīng)用的標準,這也是傳統(tǒng)算法的一個弊端;有能力的可以采用深度學(xué)習(xí)的方法進行實現(xiàn),也可以關(guān)注我后續(xù)的目標跟蹤是實現(xiàn)車輛計數(shù),效果會遠比這個好。
總結(jié)
以上是生活随笔為你收集整理的【OpenCV学习】(九)目标识别之车辆检测与计数的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vlc连接视频(摄像头)方法步骤(一)
- 下一篇: 实验五 JR指令设计实验【计算机组成原理