用 Python 和 OpenCV 检测和跟踪运动对象
這個該死的家伙。我就知道他偷了我最后一罐啤酒!
對于一個男人來講,這些話永遠都不該說。但是當我關(guān)上冰箱門的時候,我憤怒地嘆息,感到厭惡,自言自語地說了這些。
你看,我花了12個小時寫了這篇將要發(fā)表的文章《PyImageSearch Gurus course》。我的腦子都糊掉了,像個半熟的攤雞蛋一樣,幾乎要從耳朵里流出來了。當我深夜決定結(jié)束工作的時候,我只想放松一下,看看我最愛的電影——《侏羅紀公園》。同時喝著來自 Smuttynose 的最好的 IPA 冰啤,Smuttynose 是近來我非常喜歡的一家酒廠。
但是,昨天晚上來串門的該死的 James 喝掉了我最后一罐啤酒。
好吧,據(jù)稱。
我并不能證明任何我的猜測。實際上,我并沒有親眼看到他喝我的啤酒,因為我埋頭于筆記本電腦中,手指在鍵盤上跳動,興奮地敲擊出教程和文章。但是我感覺他就是嫌疑犯。他是我唯一會喝 IPA 的(前)朋友。
所以我做了一件任何男人都會做的事。
我在櫥柜頂上安裝了一個樹莓派,來探測看他是不是打算再次偷啤酒。
過分了?
也許吧。
但是,我很看重我的啤酒。而且如果 James 再次嘗試偷我的啤酒的話,我會逮他個正著。
一篇關(guān)于運動檢測的系列文章(分為兩部分)
做一個用于家庭監(jiān)控的運動檢測和追蹤系統(tǒng),分兩部分,本文是第一篇。
本文接下來的部分,將會詳細介紹如何使用計算機視覺技術(shù)來建立一個用于家庭監(jiān)控的基礎(chǔ)的運動檢測和追蹤系統(tǒng)。本例對預(yù)先錄制的視頻和網(wǎng)絡(luò)攝像頭的實時數(shù)據(jù)流都可以工作;然而,我們將會在我們的筆記本/桌面電腦上進行開發(fā)。
在本系列的第二部分中,我會向你展示如何升級代碼,使其可以在樹莓派和camera board上工作,以及如何擴展家庭監(jiān)控系統(tǒng),來捕捉任何檢測到的運動,并且上傳到你的個人Dropbox中。
也許到了最后,我們可以把 James 抓個正著。
一點關(guān)于背景移除的內(nèi)容
背景移除是很多計算機視覺應(yīng)用的關(guān)鍵內(nèi)容。我們通過它來計算經(jīng)過收費站的汽車個數(shù)。我們通過它來計算進進出出一間商店的人的個數(shù)。
同時我們使用它來進行運動檢測。
在本文開始寫代碼之前,讓我告訴你,OpenCV 里有很多很多方法來進行運動檢測、追蹤和分析。有一些非常簡單,而另外一些非常復(fù)雜。兩個初級的方法是某種形式的基于混合高斯模型的前景和背景分割:
在新版本的 OpenCV 中,我們有基于貝葉斯(概率)的前景和背景分割,是 Godbehere 等人在2012年的文章中實現(xiàn)的,《Visual Tracking of Human Visitors under Variable-Lighting Conditions for a Responsive Audio Art Installation》,我們可以在cv2.createBackgroundSubtractorGMG?中找到它的實現(xiàn)(然而我們需要等OpenCV 3的到來,才能使用它的全部功能。)
所有這些方法都涉及到從前景中分離背景(它們甚至提供相應(yīng)的機制來讓我們辨別實際運動和陰影及關(guān)照的細微改變)!
為什么這一點特別重要?為什么我們這么在意哪個像素屬于前景哪個像素屬于背景?
在運動檢測中,我們會做出如下的假設(shè):
我們視頻流中的背景在連續(xù)的視頻幀內(nèi),多數(shù)時候應(yīng)該是靜止不變的,因此如果我們可以建立背景模型,我們的就可以監(jiān)視到顯著的變化。如果發(fā)生了顯著的變化,我們就可以檢測到它——通常這些變化和我們視頻中的運動有關(guān)。
顯然在現(xiàn)實世界中,我們這個假設(shè)比較容易失效。因為陰影、反色、光照條件以及環(huán)境中可能發(fā)生的其他變化,我們的背景可能會看上去變得非常不同,這會讓我們的算法失效。所以為什么最成功的背景移除/前景檢測系統(tǒng)需要固定安裝的相機以及控制光照條件。
上面我提到的方法,盡管非常強大,但同時計算非常耗時。而且我們最終的目標是在本系列的最后,把該系統(tǒng)部署在樹莓派上,因此我們最好可以堅持使用簡單的方法。我們將在未來的文章中回到這些強大的方法上,但是目前我們將保持簡單和高效。
用 Python 和 OpenCV 進行基礎(chǔ)的運動檢測和追蹤
好了,準備好幫助我開發(fā)一個家用監(jiān)視系統(tǒng)來抓住那個偷啤酒的混蛋了么? 打開編輯器,新建一個文件,命名為?motion_detector.py,然后讓我們開始寫代碼吧。
Python| 123456789101112131415161718192021222324 | # 導(dǎo)入必要的軟件包import argparseimport datetimeimport imutilsimport timeimport cv2# 創(chuàng)建參數(shù)解析器并解析參數(shù)ap = argparse.ArgumentParser()ap.add_argument("-v", "--video", help="path to the video file")ap.add_argument("-a", "--min-area", type=int, default=500, help="minimum area size")args = vars(ap.parse_args())# 如果video參數(shù)為None,那么我們從攝像頭讀取數(shù)據(jù)if args.get("video", None) is None:????camera = cv2.VideoCapture(0)????time.sleep(0.25)# 否則我們讀取一個視頻文件else:????camera = cv2.VideoCapture(args["video"])# 初始化視頻流的第一幀firstFrame = None |
2-6行導(dǎo)入了我們必要的軟件包。這些看上去都很熟悉,除了imutils這個包,它提供了一組由我編寫的非常方便的函數(shù),來讓我們更簡單的進行圖像處理。如果你還沒有安裝?imutils到你的系統(tǒng),你可以通過pip來安裝:pip install imutils
下一步,我們在9-12行解析了命令行參數(shù)。我們定義了兩個選項。第一個,--video,是可選的。它會指定一個路徑,指向一個預(yù)先錄制好的視頻文件,我們可以檢測該視頻中的運動。如果你不提供視頻的路徑,那么OpenCV會從你的攝像頭中來檢測運動。
我們同時還定義了--min-area,它表示一個圖像區(qū)域被看做實際運動的最小尺寸(以像素為單位)。正如我接下來要講的那樣,我們會發(fā)現(xiàn)圖像中比較小的區(qū)域變化會比較顯著,可能是因為噪點或是光線的變化。在實際中,這些小區(qū)域并不是實際的運動——所以我們定義一個最小的尺寸來對付和過濾掉這些假陽性(false-positives)結(jié)果。
15-21行獲取一個我們攝像機對象的引用。在這個例子中,沒有提供視頻路徑(15-17行),我們會取得一個攝像頭的引用。如果提供了一個視頻文件路徑,那么我們會在20-21行建立一個指向它的指針。
最后,我們以一個變量來結(jié)束這段代碼,這個變量是firstFrame。 能猜到firstFrame?是什么嗎?
假設(shè):視頻的第一幀不會包含運動,而僅僅是背景——因此我們可以使用第一幀來建立背景模型。?顯然我們此處建立的假設(shè)有些太大了。但是再說一次,我們的目標是要在樹莓派上運行這個系統(tǒng),所以我們不能做的太復(fù)雜。正如你會在本文的結(jié)果一節(jié)所看到的那樣,當有人在屋里走動的時候,我們可以輕易的檢測到運動并追蹤他們。
Python| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | # 遍歷視頻的每一幀 while True: ????# 獲取當前幀并初始化occupied/unoccupied文本 ????(grabbed, frame) = camera.read() ????text = "Unoccupied" ????# 如果不能抓取到一幀,說明我們到了視頻的結(jié)尾 ????if not grabbed: ????????break ????# 調(diào)整該幀的大小,轉(zhuǎn)換為灰階圖像并且對其進行高斯模糊 ????frame = imutils.resize(frame, width=500) ????gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) ????gray = cv2.GaussianBlur(gray, (21, 21), 0) ????# 如果第一幀是None,對其進行初始化 ????if firstFrame is None: ????????firstFrame = gray ????????continue |
現(xiàn)在我們已經(jīng)獲取了視頻文件/攝像頭數(shù)據(jù)流的引用,我們可以在第一行(原文第27行)開始遍歷每一幀了。
調(diào)用camera.read()為我們返回一個2元組。元組的第一個值是grabbed,表明是否成功從緩沖中讀取了frame。元組的第二個值就是frame它本身。
我們同時還定義了一個叫做?text?的字符串,并對其進行初始化來表明我們正在監(jiān)控的這個房間“沒有被占領(lǐng)”(Unoccupied)。如果這個房間確實有活動,我們可以更新這個字符串。
在這個例子中,如果沒有成功從視頻文件中讀取一幀,我們會在10-11行(原文35-36行)跳出循環(huán)。
我們可以開始處理幀數(shù)據(jù)并準備進行運動分析(15-17行)。我們首先會調(diào)整它的大小到500像素寬——沒有必要去直接處理視頻流中的大尺寸,原始圖像。我們同樣會把圖片轉(zhuǎn)換為灰階圖像,因為彩色數(shù)據(jù)對我們的運動檢測算法沒有影響。最后,我們會使用高斯模糊來平滑我們的圖像。
認識到即使是相鄰幀,也不是完全相同的這一點很重要!
由于數(shù)碼相機傳感器的微小變化,沒有100%相同的兩幀數(shù)據(jù)——一些像素肯定會有不同的強度值。也就是說,我們需要,并應(yīng)用高斯平滑對一個11X11的區(qū)域的像素強度進行平均。這能幫我們?yōu)V除可能使我們運動檢測算法失效的高頻噪音。
正如我在上面提到的,我們需要通過某種方式對我們的圖像進行背景建模。再一次的,我們會假設(shè)視頻的第一幀不包含任何運動,它是一個很好的例子,表明我們的背景是如何的。如果firstFrame沒有初始化,我們會把它保存然后繼續(xù)處理視頻的下一幀。(20-22行)
這里有一個關(guān)于示例視頻第一幀的例子:
上面這一幀滿足我們的假設(shè),視頻的第一幀僅僅是一個靜止的背景——沒有運動。
有了這個靜止的背景圖片,我們已經(jīng)準備好實時運動檢測和追蹤了:
Python| 123456789101112131415161718192021 | # 計算當前幀和第一幀的不同????frameDelta = cv2.absdiff(firstFrame, gray)????thresh = cv2.threshold(frameDelta, 25, 255, cv2.THRESH_BINARY)[1]????# 擴展閥值圖像填充孔洞,然后找到閥值圖像上的輪廓????thresh = cv2.dilate(thresh, None, iterations=2)????(cnts, _) = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,????????cv2.CHAIN_APPROX_SIMPLE)????# 遍歷輪廓????for c in cnts:????????# if the contour is too small, ignore it????????if cv2.contourArea(c) < args["min_area"]:????????????continue????????# compute the bounding box for the contour, draw it on the frame,????????# and update the text????????# 計算輪廓的邊界框,在當前幀中畫出該框????????(x, y, w, h) = cv2.boundingRect(c)????????cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)????????text = "Occupied" |
現(xiàn)在我們已經(jīng)從firstFrame變量對背景進行了建模,我們可以利用它來計算起始幀和視頻流數(shù)據(jù)中后續(xù)新幀之間的不同。
計算兩幀的不同是一個簡單的減法,我們使用兩方相應(yīng)的像素強度差的絕對值。(第二行)
delta = |background_model – current_frame|
兩幀差值圖例如下:
注意到圖片的背景是如何變?yōu)楹谏摹H欢?#xff0c;包含運動的區(qū)域(比如包含我自己走過房間動作的區(qū)域)會更亮一些。這以為這兩幀差值大的地方是圖片中發(fā)生移動的區(qū)域。
我們隨后在第3行對frameDelta進行閥值化來顯示圖片中像素強度值有顯著變化的區(qū)域。如果差值小于25,我丟棄該像素將其設(shè)置為黑色(例如,背景)。如果差值大于25,我們將其設(shè)定為白色(例如,前景)。閥值化的差值圖片如下:
再一次,注意到圖片的背景是黑色的,而前景(運動發(fā)生的位置)是白色的。 有了這個閥值化的圖片,只要簡單的進行實施輪廓檢測來找到白色區(qū)域的外輪廓線(第7行)
我們在第14行開始對輪廓線進行遍歷,在15行濾掉小的,不相關(guān)的輪廓。 如果輪廓面積比我們提供的--min-area值大,我們會在前景和移動區(qū)域畫邊框線。(23-25行)。我們同樣會更新text狀態(tài)字符串來表示這個房間”被占領(lǐng)“(Occupied)了
Python| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | # draw the text and timestamp on the frame ????# 在當前幀上寫文字以及時間戳 ????cv2.putText(frame, "Room Status: {}".format(text), (10, 20), ????????cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2) ????cv2.putText(frame, datetime.datetime.now().strftime("%A %d %B %Y %I:%M:%S%p"), ????????(10, frame.shape[0] - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.35, (0, 0, 255), 1) ????顯示當前幀并記錄用戶是否按下按鍵 ????cv2.imshow("Security Feed", frame) ????cv2.imshow("Thresh", thresh) ????cv2.imshow("Frame Delta", frameDelta) ????key = cv2.waitKey(1) & 0xFF ????# 如果q鍵被按下,跳出循環(huán) ????if key == ord("q"): ????????break # 清理攝像機資源并關(guān)閉打開的窗口 camera.release() cv2.destroyAllWindows() |
11-13行顯示了我的工作成果,運行我們可以在視頻中看到是否檢測到了運動,使用幀差值和閥值圖像我們可以調(diào)試我們的腳本。
注意:如果你下載了本文的源代碼并打算應(yīng)用到你自己的視頻文件上,你可能需要改變cv2.threshold?的值和--min-area?參數(shù)來獲得你所在光照環(huán)境下的最佳效果。
最后,22行和23行清理并釋放了視頻流的指針。
結(jié)果
顯然,我要確定我們的運動監(jiān)測系統(tǒng)可以在James那個偷酒賊再次造訪的之前能夠正常工作——我們將在本系列第二篇文章中談到他。為了測試我們使用Python和OpenCV搭建的運動監(jiān)測系統(tǒng),我錄制了兩個視頻文件。
第一個文件是example_01.mp4?,監(jiān)視了我公寓的正門,當門被打開時完成檢測。第二個文件是example_02.mp4?使用安裝在櫥柜上的樹莓派錄制的。它監(jiān)控廚房和客廳,當有人在其中走動的時候完成檢測。
讓我們給我們簡單的探測器一次嘗試的機會,打開終端并執(zhí)行下面指令:
Python| 1 | $ python motion_detector.py --video videos/example_01.mp4 |
下圖是一個 gif 圖,顯示來自探測器的一些靜止幀數(shù)據(jù)。
注意到在門被打開前沒有進行運動檢測——然后我們可以檢測到我自己從門中走過。你可以在這里看到全部視頻:
http://www.youtube.com/embed/fi4LORwk8Fc?feature=oembed
現(xiàn)在,我安裝在用于監(jiān)視廚房和客廳的攝像機表現(xiàn)如何呢?然我們一探究竟。輸入下面命令:
Python| 1 | $ python motion_detector.py --video videos/example_02.mp4 |
來自第二個視頻文件的結(jié)果樣本如下:
同樣,這里是我們運動檢測結(jié)果的完整視頻:
http://www.youtube.com/embed/36j238XtcIE?feature=oembed
正如你看到的,我們的運動檢測系統(tǒng)盡管非常簡單,但表現(xiàn)還不錯!我們可以正常檢測到我進入客廳和離開房間。
然而,現(xiàn)實來講,結(jié)果還遠遠談不上完美。盡管只有一個人在屋內(nèi)走動,我們卻得到了多個外框——這和理想狀態(tài)相差甚遠。而且我可以看到,微小的光線變化,比如陰影和墻面反射,都觸發(fā)了假陽性的運動檢測結(jié)果。
為了解決這些問題,我們依靠OpenCV中更加強大的背景移除方法,這些方法對陰影和少量的反射進行了處理。(我將在未來的文章中談到這些更為先進的背景移除/前景檢測方法)
但是于此同時,請考慮一下我們的最終目標
這個系統(tǒng),盡管是在我們的筆記本/臺式機系統(tǒng)上開發(fā)的,卻是為了要部署在樹莓派上,樹莓派的計算資源非常有限。因此,我們需要讓我們的運動檢測方法保持簡單和快速。我們的運動檢測系統(tǒng)并不完美,很不幸這是一個不利的方面,但是對于我們特定的項目,它仍然能夠很好的完成工作。
最后,如果你想要利用你的攝像頭的原始視頻流來進行運動檢測,空著--video選項即可。
Python| 1 | $ python motion_detector.py |
小結(jié)
通過本文,我們已經(jīng)認識到我的朋友James是一個偷酒賊。真是個混蛋啊!
為了能抓他個人贓并獲,我們決定使用Python和OpenCV建立一個運動檢測和追蹤系統(tǒng)。這個系統(tǒng)可以獲取視頻流并分析它們獲取運動。考慮到我們所使用的方法,能夠得到可以接受的監(jiān)測結(jié)果。
最終目標是要把本系統(tǒng)部署在樹莓派上,因此我們沒有依賴OpenCV中一些比較先進的背景移除方法。相反,我們依賴一個簡單,但合理高效的假設(shè)——視頻的第一幀僅僅包含我們想要建模的背景,而不包括其他任何東西。
在這個假設(shè)下,我們可以實施背景移除,檢測圖片中的運動,在檢測到運動的區(qū)域畫出輪廓框。
在這個關(guān)于運動檢測系列文章的第二部分,我們會更新代碼使其在樹莓派上運行。
我們同樣會集成Dropbox API,允許我們監(jiān)控家用監(jiān)控系統(tǒng)并且當我們的系統(tǒng)檢測到運動時,獲取實時更新數(shù)據(jù)。
by??伯樂在線?-?艾凌風
from:?http://python.jobbole.com/81593/
總結(jié)
以上是生活随笔為你收集整理的用 Python 和 OpenCV 检测和跟踪运动对象的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 不知道被谁删了微信好友?用 Python
- 下一篇: 用Python Pandas处理亿级数据