Python-OpenCV 处理视频(三)(四)(五): 标记运动轨迹 运动检测 运动方向判断
0x00. 光流
光流是進行視頻中運動對象軌跡標(biāo)記的一種很常用的方法,在OpenCV中實現(xiàn)光流也很容易。
CalcOpticalFlowPyrLK?函數(shù)計算一個稀疏特征集的光流,使用金字塔中的迭代 Lucas-Kanade 方法。
簡單的實現(xiàn)流程:
加載一段視頻。
調(diào)用GoodFeaturesToTrack函數(shù)尋找興趣點。
調(diào)用CalcOpticalFlowPyrLK函數(shù)計算出兩幀圖像中興趣點的移動情況。
刪除未移動的興趣點。
在兩次移動的點之間繪制一條線段。
代碼示例:
import cv2.cv as cvcapture = cv.CaptureFromFile('img/myvideo.avi')nbFrames = int(cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_COUNT)) fps = cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FPS) wait = int(1/fps * 1000/1) width = int(cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_WIDTH)) height = int(cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_HEIGHT))prev_gray = cv.CreateImage((width,height), 8, 1) #Will hold the frame at t-1 gray = cv.CreateImage((width,height), 8, 1) # Will hold the current frameprevPyr = cv.CreateImage((height / 3, width + 8), 8, cv.CV_8UC1) #Will hold the pyr frame at t-1 currPyr = cv.CreateImage((height / 3, width + 8), 8, cv.CV_8UC1) # idem at tmax_count = 500 qLevel= 0.01 minDist = 10 prev_points = [] #Points at t-1 curr_points = [] #Points at t lines=[] #To keep all the lines overtimefor f in xrange( nbFrames ):frame = cv.QueryFrame(capture) #Take a frame of the videocv.CvtColor(frame, gray, cv.CV_BGR2GRAY) #Convert to grayoutput = cv.CloneImage(frame)prev_points = cv.GoodFeaturesToTrack(gray, None, None, max_count, qLevel, minDist) #Find points on the image#Calculate the movement using the previous and the current frame using the previous pointscurr_points, status, err = cv.CalcOpticalFlowPyrLK(prev_gray, gray, prevPyr, currPyr, prev_points, (10, 10), 3, (cv.CV_TERMCRIT_ITER|cv.CV_TERMCRIT_EPS,20, 0.03), 0)#If points status are ok and distance not negligible keep the pointk = 0for i in range(len(curr_points)):nb = abs( int(prev_points[i][0])-int(curr_points[i][0]) ) + abs( int(prev_points[i][1])-int(curr_points[i][1]) )if status[i] and nb > 2 :prev_points[k] = prev_points[i]curr_points[k] = curr_points[i]k += 1prev_points = prev_points[:k]curr_points = curr_points[:k]#At the end only interesting points are kept#Draw all the previously kept lines otherwise they would be lost the next framefor (pt1, pt2) in lines:cv.Line(frame, pt1, pt2, (255,255,255))#Draw the lines between each points at t-1 and tfor prevpoint, point in zip(prev_points,curr_points):prevpoint = (int(prevpoint[0]),int(prevpoint[1]))cv.Circle(frame, prevpoint, 15, 0)point = (int(point[0]),int(point[1]))cv.Circle(frame, point, 3, 255)cv.Line(frame, prevpoint, point, (255,255,255))lines.append((prevpoint,point)) #Append current lines to the lines listcv.Copy(gray, prev_gray) #Put the current frame prev_grayprev_points = curr_pointscv.ShowImage("The Video", frame)#cv.WriteFrame(writer, frame)cv.WaitKey(wait)直接調(diào)用攝像頭使用該方法:
import cv2.cv as cvcapture = cv.CaptureFromCAM(0)width = int(cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_WIDTH)) height = int(cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_HEIGHT))prev_gray = cv.CreateImage((width,height), 8, 1) gray = cv.CreateImage((width,height), 8, 1)prevPyr = cv.CreateImage((height / 3, width + 8), 8, cv.CV_8UC1) #Will hold the pyr frame at t-1 currPyr = cv.CreateImage((height / 3, width + 8), 8, cv.CV_8UC1) # idem at tmax_count = 500 qLevel= 0.01 minDist = 10 prev_points = [] #Points at t-1 curr_points = [] #Points at t lines=[] #To keep all the lines overtimewhile True:frame = cv.QueryFrame(capture)cv.CvtColor(frame, gray, cv.CV_BGR2GRAY) #Convert to grayoutput = cv.CloneImage(frame)prev_points = cv.GoodFeaturesToTrack(gray, None, None, max_count, qLevel, minDist)curr_points, status, err = cv.CalcOpticalFlowPyrLK(prev_gray, gray, prevPyr, currPyr, prev_points, (10, 10), 3, (cv.CV_TERMCRIT_ITER|cv.CV_TERMCRIT_EPS,20, 0.03), 0)#If points status are ok and distance not negligible keep the pointk = 0for i in range(len(curr_points)):nb = abs( int(prev_points[i][0])-int(curr_points[i][0]) ) + abs( int(prev_points[i][1])-int(curr_points[i][1]) )if status[i] and nb > 2 :prev_points[k] = prev_points[i]curr_points[k] = curr_points[i]k += 1prev_points = prev_points[:k]curr_points = curr_points[:k]#At the end only interesting points are kept#Draw all the previously kept lines otherwise they would be lost the next framefor (pt1, pt2) in lines:cv.Line(frame, pt1, pt2, (255,255,255))#Draw the lines between each points at t-1 and tfor prevpoint, point in zip(prev_points,curr_points):prevpoint = (int(prevpoint[0]),int(prevpoint[1]))cv.Circle(frame, prevpoint, 15, 0)point = (int(point[0]),int(point[1]))cv.Circle(frame, point, 3, 255)cv.Line(frame, prevpoint, point, (255,255,255))lines.append((prevpoint,point)) #Append current lines to the lines listcv.Copy(gray, prev_gray) #Put the current frame prev_grayprev_points = curr_pointscv.ShowImage("The Video", frame)#cv.WriteFrame(writer, frame)c = cv.WaitKey(1)if c == 27: #Esc on Windowsbreak0x01. 尋找最大特征值的角點
cv.GoodFeaturesToTrack 函數(shù)可以檢測出圖像中最大特征值的角點,使用這個函數(shù)可以對圖像中的特征點進行跟蹤,從而繪制出運動軌跡。
直接加載視頻:
import cv2.cv as cvcapture = cv.CaptureFromFile('img/myvideo.avi')#-- Informations about the video -- nbFrames = int(cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_COUNT)) fps = cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FPS) wait = int(1/fps * 1000/1) width = int(cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_WIDTH)) height = int(cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_HEIGHT)) #For recording #codec = cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FOURCC) #writer=cv.CreateVideoWriter("img/output.avi", int(codec), int(fps), (width,height), 1) #Create writer with same parameters #----------------------------------prev_gray = cv.CreateImage((width,height), 8, 1) #Will hold the frame at t-1 gray = cv.CreateImage((width,height), 8, 1) # Will hold the current frameoutput = cv.CreateImage((width,height), 8, 3)prevPyr = cv.CreateImage((height / 3, width + 8), 8, cv.CV_8UC1) currPyr = cv.CreateImage((height / 3, width + 8), 8, cv.CV_8UC1)max_count = 500 qLevel= 0.01 minDist = 10begin = Trueinitial = [] features = [] prev_points = [] curr_points = []for f in xrange( nbFrames ):frame = cv.QueryFrame(capture)cv.CvtColor(frame, gray, cv.CV_BGR2GRAY) #Convert to graycv.Copy(frame, output)if (len(prev_points) <= 10): #Try to get more points#Detect points on the imagefeatures = cv.GoodFeaturesToTrack(gray, None, None, max_count, qLevel, minDist)prev_points.extend(features) #Add the new points to listinitial.extend(features) #Idemif begin:cv.Copy(gray, prev_gray) #Now we have two frames to comparebegin = False#Compute movementcurr_points, status, err = cv.CalcOpticalFlowPyrLK(prev_gray, gray, prevPyr, currPyr, prev_points, (10, 10), 3, (cv.CV_TERMCRIT_ITER|cv.CV_TERMCRIT_EPS,20, 0.03), 0)#If points status are ok and distance not negligible keep the pointk = 0for i in range(len(curr_points)):nb = abs( int(prev_points[i][0])-int(curr_points[i][0]) ) + abs( int(prev_points[i][1])-int(curr_points[i][1]) )if status[i] and nb > 2 :initial[k] = initial[i]curr_points[k] = curr_points[i]k += 1curr_points = curr_points[:k]initial = initial[:k]#At the end only interesting points are kept#Draw the line between the first position of a point and the#last recorded position of the same pointfor i in range(len(curr_points)):cv.Line(output, (int(initial[i][0]),int(initial[i][1])), (int(curr_points[i][0]),int(curr_points[i][1])), (255,255,255))cv.Circle(output, (int(curr_points[i][0]),int(curr_points[i][1])), 3, (255,255,255))cv.Copy(gray, prev_gray)prev_points = curr_pointscv.ShowImage("The Video", output)cv.WriteFrame(writer, output)cv.WaitKey(wait)調(diào)用攝像頭繪制:
import cv2.cv as cvcapture = cv.CaptureFromCAM(0)width = int(cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_WIDTH)) height = int(cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_HEIGHT))prev_gray = cv.CreateImage((width,height), 8, 1) #Will hold the frame at t-1 gray = cv.CreateImage((width,height), 8, 1) # Will hold the current frameoutput = cv.CreateImage((width,height), 8, 3)prevPyr = cv.CreateImage((height / 3, width + 8), 8, cv.CV_8UC1) currPyr = cv.CreateImage((height / 3, width + 8), 8, cv.CV_8UC1)max_count = 500 qLevel= 0.01 minDist = 10begin = Trueinitial = [] features = [] prev_points = [] curr_points = []while True:frame = cv.QueryFrame(capture)cv.CvtColor(frame, gray, cv.CV_BGR2GRAY) #Convert to graycv.Copy(frame, output)if (len(prev_points) <= 10): #Try to get more points#Detect points on the imagefeatures = cv.GoodFeaturesToTrack(gray, None, None, max_count, qLevel, minDist)prev_points.extend(features) #Add the new points to listinitial.extend(features) #Idemif begin:cv.Copy(gray, prev_gray) #Now we have two frames to comparebegin = False#Compute movementcurr_points, status, err = cv.CalcOpticalFlowPyrLK(prev_gray, gray, prevPyr, currPyr, prev_points, (10, 10), 3, (cv.CV_TERMCRIT_ITER|cv.CV_TERMCRIT_EPS,20, 0.03), 0)#If points status are ok and distance not negligible keep the pointk = 0for i in range(len(curr_points)):nb = abs( int(prev_points[i][0])-int(curr_points[i][0]) ) + abs( int(prev_points[i][1])-int(curr_points[i][1]) )if status[i] and nb > 2 :initial[k] = initial[i]curr_points[k] = curr_points[i]k += 1curr_points = curr_points[:k]initial = initial[:k]for i in range(len(curr_points)):cv.Line(output, (int(initial[i][0]),int(initial[i][1])), (int(curr_points[i][0]),int(curr_points[i][1])), (255,255,255))cv.Circle(output, (int(curr_points[i][0]),int(curr_points[i][1])), 3, (255,255,255))cv.Copy(gray, prev_gray)prev_points = curr_pointscv.ShowImage("The Video", output)c = cv.WaitKey(1)if c == 27: #Esc on Windowsbreak ————————————————————————————————————分割線來了————————————————————————————————————0x00. 平均值法
通過計算兩幀圖像之間變化了的像素點占的百分比,來確定圖像中是否有動作產(chǎn)生。
這里主要用到 Absdiff 函數(shù),比較兩幀圖像之間有差異的點,當(dāng)然需要將圖像進行一些處理,例如平滑處理,灰度化處理,二值化處理,經(jīng)過處理之后的二值圖像上的點將更有效。
代碼示例:
import cv2.cv as cvcapture=cv.CaptureFromCAM(0)frame1 = cv.QueryFrame(capture) frame1gray = cv.CreateMat(frame1.height, frame1.width, cv.CV_8U) cv.CvtColor(frame1, frame1gray, cv.CV_RGB2GRAY)res = cv.CreateMat(frame1.height, frame1.width, cv.CV_8U)frame2gray = cv.CreateMat(frame1.height, frame1.width, cv.CV_8U)w= frame2gray.width h= frame2gray.height nb_pixels = frame2gray.width * frame2gray.heightwhile True:frame2 = cv.QueryFrame(capture)cv.CvtColor(frame2, frame2gray, cv.CV_RGB2GRAY)cv.AbsDiff(frame1gray, frame2gray, res)cv.ShowImage("After AbsDiff", res)cv.Smooth(res, res, cv.CV_BLUR, 5,5)element = cv.CreateStructuringElementEx(5*2+1, 5*2+1, 5, 5, cv.CV_SHAPE_RECT)cv.MorphologyEx(res, res, None, None, cv.CV_MOP_OPEN)cv.MorphologyEx(res, res, None, None, cv.CV_MOP_CLOSE)cv.Threshold(res, res, 10, 255, cv.CV_THRESH_BINARY_INV)cv.ShowImage("Image", frame2)cv.ShowImage("Res", res)#-----------nb=0for y in range(h):for x in range(w):if res[y,x] == 0.0:nb += 1avg = (nb*100.0)/nb_pixels#print "Average: ",avg, "%\r",if avg >= 5:print "Something is moving !"#-----------cv.Copy(frame2gray, frame1gray)c=cv.WaitKey(1)if c==27: #Break if user enters 'Esc'.break0x01. 背景建模與前景檢測
背景建模也是檢測運動物體的一種辦法,下面是代碼示例:
import cv2.cv as cvcapture = cv.CaptureFromCAM(0) width = int(cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_WIDTH)) height = int(cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_HEIGHT))gray = cv.CreateImage((width,height), cv.IPL_DEPTH_8U, 1)background = cv.CreateMat(height, width, cv.CV_32F) backImage = cv.CreateImage((width,height), cv.IPL_DEPTH_8U, 1) foreground = cv.CreateImage((width,height), cv.IPL_DEPTH_8U, 1) output = cv.CreateImage((width,height), 8, 1)begin = True threshold = 10while True:frame = cv.QueryFrame( capture )cv.CvtColor(frame, gray, cv.CV_BGR2GRAY)if begin:cv.Convert(gray, background) #Convert gray into background formatbegin = Falsecv.Convert(background, backImage) #convert existing background to backImagecv.AbsDiff(backImage, gray, foreground) #Absdiff to get differencescv.Threshold(foreground, output, threshold, 255, cv.CV_THRESH_BINARY_INV)cv.Acc(foreground, background,output) #Accumulate to backgroundcv.ShowImage("Output", output)cv.ShowImage("Gray", gray)c=cv.WaitKey(1)if c==27: #Break if user enters 'Esc'.break0x02. 我的方法
上面的幾種辦法我都試了下,基本上能識別出運動的物體,但是發(fā)現(xiàn)總是有點瑕疵,所以又比對了幾種別人的方案,然后合成了一個自己的方案:
具體處理思路:
對兩幀圖像做一個absdiff得到新圖像。
對新圖像做灰度和二值化處理。
使用findContours函數(shù)獲取二值化處理之后的圖片中的輪廓。
使用contourArea()過濾掉自己不想要的面積范圍的輪廓。
這個辦法基本上能夠檢測出物體的圖像中物體的移動,而且我覺得通過設(shè)定contourArea()函數(shù)的過濾范圍,可以檢測距離攝像頭不同距離范圍的運動物體。
以下是代碼示例:
#!usr/bin/env python #coding=utf-8import cv2 import numpy as npcamera = cv2.VideoCapture(0) width = int(camera.get(3)) height = int(camera.get(4))firstFrame = Nonewhile True:(grabbed, frame) = camera.read()gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)gray = cv2.GaussianBlur(gray, (21, 21), 0)if firstFrame is None:firstFrame = graycontinueframeDelta = cv2.absdiff(firstFrame, gray)thresh = cv2.threshold(frameDelta, 25, 255, cv2.THRESH_BINARY)[1]# thresh = cv2.adaptiveThreshold(frameDelta,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\# cv2.THRESH_BINARY,11,2)# thresh = cv2.adaptiveThreshold(frameDelta,255,cv2.ADAPTIVE_THRESH_MEAN_C,\# cv2.THRESH_BINARY,11,2)thresh = cv2.dilate(thresh, None, iterations=2)(_, cnts, _) = cv2.findContours(thresh.copy(), cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)for c in cnts:if cv2.contourArea(c) < 10000:continue(x, y, w, h) = cv2.boundingRect(c)cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)cv2.imshow("Security Feed", frame)firstFrame = gray.copy() camera.release() cv2.destroyAllWindows()——————————————————————————————————汪星人說這是分割線——————————————————————————————————
注意,我使用的OpenCV 版本是 3.0, 低版本就有可能出現(xiàn)第一條評論里的報錯
在檢測出運動的物體之后,我還需要知道運動的方向,使用了上一節(jié)中的辦法檢測運動我發(fā)現(xiàn)很難去計算運動方向,開始考慮通過計算輪廓的中點的變化來實現(xiàn),但是因為每次檢測出得輪廓的數(shù)量不穩(wěn)定,所以這個辦法會讓誤差不可控。
這時我發(fā)現(xiàn)了?goodFeaturesToTrack?函數(shù),簡直是救了我,goodFeaturesToTrack?函數(shù)可以獲取圖像中的最大特征值的角點,以下是我的思路:
Tips: 看代碼之前請先看看我下面寫的實現(xiàn)思路,另外還有代碼里的注釋也對于理解代碼會有所幫助
對兩幀圖像做一個?absdiff?得到新圖像。
對新圖像做灰度和二值化處理。
使用?goodFeaturesToTrack?函數(shù)得到最大特征值的角點。
計算角點的平均點,寫入隊列。(通過計算平均點的解決辦法類似物理中剛體問題抽象成質(zhì)點解決的思路)
維護一個長度為 10 的隊列,隊列滿時計算隊列中數(shù)據(jù)的增減情況,來確定運動方向。
代碼示例(只實現(xiàn)了左右移動的判斷):
#!usr/bin/env python #coding=utf-8import cv2 import numpy as np import Queuecamera = cv2.VideoCapture(0) width = int(camera.get(3)) height = int(camera.get(4))firstFrame = None lastDec = None firstThresh = Nonefeature_params = dict( maxCorners = 100,qualityLevel = 0.3,minDistance = 7,blockSize = 7 )lk_params = dict( winSize = (15,15),maxLevel = 2,criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))color = np.random.randint(0,255,(100,3)) num = 0q_x = Queue.Queue(maxsize = 10) q_y = Queue.Queue(maxsize = 10)while True:(grabbed, frame) = camera.read()gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)gray = cv2.GaussianBlur(gray, (21, 21), 0)if firstFrame is None:firstFrame = graycontinue# 對兩幀圖像進行 absdiff 操作frameDelta = cv2.absdiff(firstFrame, gray)# diff 之后的圖像進行二值化thresh = cv2.threshold(frameDelta, 25, 255, cv2.THRESH_BINARY)[1]# 下面的是幾種不同的二值化的方法,感覺對我來說效果都差不多# thresh = cv2.adaptiveThreshold(frameDelta,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\# cv2.THRESH_BINARY,11,2)# thresh = cv2.adaptiveThreshold(frameDelta,255,cv2.ADAPTIVE_THRESH_MEAN_C,\# cv2.THRESH_BINARY,11,2)thresh = cv2.dilate(thresh, None, iterations=2)# 識別角點p0 = cv2.goodFeaturesToTrack(thresh, mask = None, **feature_params)if p0 is not None:x_sum = 0y_sum = 0for i, old in enumerate(p0):x, y = old.ravel()x_sum += xy_sum += y# 計算出所有角點的平均值x_avg = x_sum / len(p0)y_avg = y_sum / len(p0)# 寫入固定長度的隊列if q_x.full():# 如果隊列滿了,就計算這個隊列中元素的增減情況qx_list = list(q_x.queue)key = 0diffx_sum = 0for item_x in qx_list:key +=1if key < 10:# 下一個元素減去上一個元素diff_x = item_x - qx_list[key]diffx_sum += diff_x# 加和小于0,表明隊列中的元素在遞增if diffx_sum < 0:print "left"cv2.putText(frame, "some coming form left", (100,100), 0, 0.5, (0,0,255),2)else:print "right"print x_avgq_x.get()q_x.put(x_avg)cv2.putText(frame, str(x_avg), (300,100), 0, 0.5, (0,0,255),2)frame = cv2.circle(frame,(int(x_avg),int(y_avg)),5,color[i].tolist(),-1)cv2.imshow("Security Feed", frame)firstFrame = gray.copy()camera.release() cv2.destroyAllWindows()總的來講作為一個圖像處理的小白,不斷地折騰和嘗試,終于搞出了自己想要的東西,OpenCV絕對是喜歡折騰的人必要掌握的一個庫了,以后肯定還會繼續(xù)研究這塊東西。
from: https://segmentfault.com/a/1190000003804820 https://segmentfault.com/a/1190000003804835 https://segmentfault.com/a/1190000003804867
總結(jié)
以上是生活随笔為你收集整理的Python-OpenCV 处理视频(三)(四)(五): 标记运动轨迹 运动检测 运动方向判断的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python-OpenCV 处理视频(一
- 下一篇: Python 内部:可调用对象是如何工作