在本教程中,您將學習如何通過Flask和Python使用OpenCV將視頻從網絡攝像頭流式傳輸到網頁瀏覽器/HTML頁面。
您的車被偷過嗎?
我的車在週末就被偷了。讓我告訴您,我很生氣。
我不能透露太多的細節,因為這個案件還在調查中,但以下是我可以告訴您的:
大約六個月前,我和妻子從康涅狄格州的諾沃克搬到了賓夕法尼亞州的費城。我有一輛車,我不經常開,但還是留著它以備不時之需。
我們小區很難找到停車的地方,所以我需要一個車庫。
我聽說有個車庫,就報名了,開始把車停在那裡。
直到上個星期天。
我和妻子來到車庫取車。我們打算開車到馬里蘭去看望我的父母,吃一些螃蟹(馬里蘭的螃蟹很有名)。
我走到我的車旁,取下車衣。
我立馬就蒙圈了——這不是我的車。
我的車#$&@去哪裡了?
幾分鐘後我就意識到一個現實——我的車被偷了。
在過去的一週中,我為即將出版的《樹莓派電腦視覺》一書正在做的工作被打斷了——我一直在與停車場的主人、費城警察局和我車上的GPS跟蹤服務打交道,想弄清楚到底發生了什麼。
在事情解決之前,我不能公開透露任何細節,但是讓我告訴您,我正埋頭處理警察報告、律師信件、還有保險索賠等一大堆的文件。
我希望下個月這個問題能得到解決——我討厭分心,尤其是讓我遠離我最喜歡做的事情——教計算機視覺和深度學習。
我成功地利用我的挫折啟發了一篇新的安全相關的計算機視覺博客帖子。
在這篇文章中,我們將學習如何使用Flask和OpenCV將視頻流式傳輸到網頁瀏覽器中。
您可以在不到5分鐘的時間內將此係統部署到樹莓派上:
- 簡單地安裝所需的包/軟件並啟動腳本
- 然後打開您的計算機/智能手機瀏覽器,並導航到URL/IP地址,就可以看到傳輸過來的視頻了(並且確保您的任何東西沒有被偷)。
沒什麼比一個小視頻證據更能抓住小偷了。
當我繼續與警察、保險等處理文書工作時,您就可以開始用樹莓派相機武裝自己,無論您在哪裡生活和工作,都可以抓住壞人。
要學習如何使用OpenCV和Flask將視頻流式傳輸到一個網頁瀏覽器的HTML頁面,請繼續閱讀!
OpenCV——將視頻流式傳輸到網頁瀏覽器/HTML頁面
在本教程中,我們將首先討論Flask,它是一個用於Python編程語言的微型web框架。
我們將學習運動檢測的基本知識,以便我們可以將它應用到我們的項目中。我們將通過一個背景減法器來實現運動檢測。
在此基礎上,我們將Flask與OpenCV結合,這樣我們就能夠:
- 訪問來自樹莓派相機模塊或USB網絡攝像頭的幀。
- 處理幀並應用一個任意的算法(這裡我們將使用背景去除/運動檢測,但您也可以應用圖像分類,對象檢測等)。
- 將結果流式傳輸到一個網頁頁面/網頁瀏覽器。
此外,我們將涉及的代碼將能夠支持多個客戶端(即,不止一個人/網頁瀏覽器/標籤頁能同時訪問流媒體),這是您在網上看到的絕大多數例子都無法處理的。
把所有這些過程塊放在一起,我們就會得到一個能夠執行運動檢測的家庭監控系統,然後它會將視頻結果流式傳輸到您的網頁瀏覽器中。
讓我們開始吧!
Flask web框架
在本節中,我們將簡要討論Flask web框架以及如何在您的系統中安裝它。
Flask是一個非常流行的用Python編程語言編寫的微型web框架。
與Django一樣,Flask是使用Python構建web應用程序時最常見的web框架之一。
但是,與Django不同的是,Flask非常輕量,這使得使用它構建基本的web應用程序非常容易。
正如我們將在本節中看到的,我們只需要一小部分代碼就可以使用Flask實現實時流式傳輸視頻——其餘的代碼包括(1)OpenCV和訪問我們的視頻流,或者(2)確保我們的代碼是線程安全的,並且可以處理多個客戶端。
如果您需要在一個機器上安裝Flask,您只需要簡單地按以下命令進行操作:
安裝了它之後,您可以繼續安裝NumPy、OpenCV和imutils:
注意:如果您想要完整安裝包括“非免費”(專利)算法的OpenCV,那您一定要從源代碼來編譯OpenCV。
項目結構
在我們繼續之前,讓我們看看我們項目的目錄結構:
為了執行背景去除和運動檢測,我們將實現一個名為SingleMotionDetector的類——該類將位於singlemotiondetector.py文件中pyimagesearch的motion_detection子模塊中。
webstreaming.py文件將使用OpenCV訪問我們的網絡攝像頭,通過SingleMotionDetector執行運動檢測,然後通過Flask web框架將輸出幀提供給我們的網絡瀏覽器。
為了讓我們網絡瀏覽器能有東西顯示,我們需要使用HTML填充index.html的內容來提供接收到的視頻。我們只需要插入一些基本的HTML標記——Flask將實際處理將視頻流發送到我們的瀏覽器的事情。
實現一個基本的運動檢測器
通過使用背景去除進行運動檢測,我們可以檢測到我在椅子上的運動。
我們的運動檢測算法將通過背景減除的形式來檢測運動。
大多數背景去除算法的工作原理是:
- 累積相加前N幀的加權平均值
- 取當前幀並從幀的加權平均值中減去它
- 對去除的輸出進行閾值處理,以突出像素值差異較大的區域(“白色”表示前景,“黑色”表示背景)
- 應用基本的圖像處理技術如腐蝕和膨脹來消除噪聲
- 利用輪廓檢測提取運動區域
我們的運動檢測實現將位於SingleMotionDetector類中,該類可以在SingleMotionDetector .py中找到。
我們稱之為一個“單一運動檢測器”,因為其算法本身只對尋找單一的、最大的運動區域感興趣。
我們也可以很容易地擴展這個方法來處理多個運動區域。
讓我們來實現該運動檢測器。
打開singlemotiondetector.py文件,並插入以下代碼:
第2-4行處理所需的導入。
所有這些都是相當標準的,包括用於數字處理的NumPy、用於為我們提供方便函數的imutils和用於OpenCV綁定的cv2。
然後我們在第6行定義SingleMotionDetector類。這個類接受一個可選的參數accumWeight,它是用於累積加權平均值的因數。
accumWeight越大,在累積加權平均值時,背景(bg)被考慮的越少。
相反地,accumWeight越小,在計算平均值時考慮背景(bg)就會越多。
設置accumWeight=0.5會均勻地加權背景和前景——我經常建議使用這個設置作為起始值(然後您可以根據自己的實驗調整它)。
接下來,讓我們定義update方法,它將接受一個輸入幀並計算加權平均值:
為了防止我們的bg幀為None(這意味著update從未被調用),我們只需要存儲bg幀(第15-18行)。
否則,我們會計算輸入幀、現有背景bg和相應的accumWeight因子之間的加權平均值。
鑑於我們的背景bg,我們現在可以通過detect方法應用運動檢測:
detect方法需要一個參數和一個可選參數:
- image: 將要被應用運動檢測的輸入幀/圖像。
- tVal: 用於將一個特定像素標記為 “運動” 或 非運動的閾值。
給定我們的輸入image,我們計算該image和bg之間的絕對誤差(第27行)。
任何差異>tVal的像素位置都被設置為255(白色;前景),否則設置為0(黑色;背景)(28行)。
通過一系列的腐蝕和膨脹來消除噪聲和小的局部運動區域,否則這些區域會被認為是假陽性的(可能是由於反射或光線的快速變化)。
下一步是應用輪廓檢測提取任何運動區域:
第37-39行對我們的thresh圖像執行輪廓檢測。
然後,我們初始化兩組記賬變量,以跟蹤包含任何運動的位置(第40行和第41行)。這些變量將形成“邊界框”,它將告訴我們運動發生的位置。
最後一步是填充這些變量(假設運動存在於幀中,當然是這樣):
在43-45行中,我們檢查我們的輪廓列表是否為空。
如果是這種情況,那麼在幀中就找不到運動,我們就可以放心地忽略它。
否則,在幀中確實存在運動,所以我們就需要開始在輪廓線上進行循環(第48行)。
對於每個輪廓,我們計算其邊界框,然後更新我們的記賬變量(第47-53行),找到所有運動發生的最小和最大(x, y)座標。
最後,我們將邊界框位置返回給調用函數。
結合OpenCV和Flask
圖3:OpenCV和Flask (一個Python微型網絡框架)是涉及Raspberry Pi和類似硬件的網絡流媒體和視頻監控項目的完美搭檔。
讓我們繼續,將OpenCV和Flask結合起來,從一個視頻流(運行在樹莓派上)向一個網絡瀏覽器提供幀。
打開您項目結構中的webstreaming.py 文件,並插入如下代碼:
第2-12行處理我們需要的導入:
- 第2行 導入我們上邊實現的SingleMotionDetector類
- VideoStream類 (第3行 ) 將允許我們訪問我們的Raspberry Pi 相機模塊或 USB網絡攝像頭.
- 第4到6行處理導入我們需要的Flask包——我們將使用這些包呈現我們的index.html模板,並將其提供給客戶端。
- 第7行導入threading庫,確保我們可以支持併發 (比如,同時使用多個客戶端、網絡瀏覽器和選項卡)。
讓我們繼續執行一些初始化:
首先,我們在第17行初始化我們的outputFrame——這將是將提供給客戶端的幀(投遞運動檢測)。
然後,我們在第18行創建一個lock,它將在更新ouputFrame時被用來確保線程安全行為(即確保某個幀在更新時不被任何線程嘗試讀取)。
第21行初始化我們的Flask app本身,而第25-27行訪問我們的視頻流:
- 如果您正在使用一個USB網絡攝像頭, 您可以保持代碼不變。
- 否則,如果您正在使用一個RPi相機模塊,那您應該取消掉第25行的註釋,並將第26行註釋掉。
下一個函數index將會渲染我們的index.html模板並提供輸出視頻流:
這個函數非常簡單—它所做的就是在我們的HTML文件中調用Flask render_template。
我們將在下一章查看該index.html文件,因此,我們將推遲對此文件內容的進一步討論直到那個時候。
我們的下一個函數的功能是:
- 對我們視頻流中的幀進行循環
- 應用運動檢測
- 在outputFrame上繪製任何結果
而且,這個函數必須以線程安全的方式來執行所有這些操作,以確保支持併發。
現在讓我們看看這個函數:
我們的detection_motion函數接受單個參數frameCount,它是在SingleMotionDetector類中構建我們的背景bg所需的最小幀數:
- 如果我們沒有至少frameCount幀,我們將會繼續計算累計加權平均值
- 一旦frameCount達到了,我們將執行背景去除。
第37行獲取對三個變量的全局引用:
- vs: 我們實例化的VideoStream對象
- outputFrame: 將提供給客戶端的輸出幀
- lock: 在更新outputFrame之前我們必須獲得的線程鎖。
第41行使用一個accumWeight=0.1值來初始化我們的SingleMotionDetector 類,這意味著在計算加權平均值時,bg值的權重會更高。
第42行初始化到目前為止讀取的幀的total數——我們需要確保已經讀取了足夠多的幀來構建我們的背景模型。
從那裡,我們將能夠執行背景去除。
這些初始化完成後,我們現在可以開始對來自相機的幀進行循環:
第48行讀取來自我們相機的frame幀,而第49-51行執行預處理操作,包括:
- 調整寬度為400 px (我們的輸入幀越小, 數據量就越小,因此,我們的算法運行的就越快)。
- 轉換成灰階圖。
- 高斯模糊(來減少噪聲)。
然後,我們獲取當前時間戳並將其繪製在frame上(第54-57行)。
在進行一次最終檢查之後,我們就可以執行運動檢測:
在第62行中,我們確保我們至少讀取了frameCount幀來構建我們的背景去除模型。
如果是這樣,我們將應用我們運動檢測器的.detect運動,它將返回單個變量motion。
如果motion是None,那麼我們就知道當前frame中沒有發生運動。否則,如果motion不是None(第67行),那麼我們需要在frame上繪製運動區域的邊界框座標。
第76行更新我們的運動檢測背景模型,而第77行增加了迄今為止從攝像機讀取的幀的total數。
最後,第81行獲得支持線程併發所需的lock,而第82行設置outputFrame。
我們需要獲取鎖,以確保我們在試圖更新outputFrame變量時客戶機不會意外讀取它。
我們的下一個函數,generate,是一個Python生成器,用於將我們的outputFrame編碼為JPEG數據——現在讓我們來看看它:
第86行獲取對outputFrame和lock的全局引用,類似於detect_motion函數。
然後generate在第89行啟動一個無限循環,這個循環將一直持續到我們結束腳本。
在循環內部,我們:
- 首先獲取lock (第91行 ).
- 確保outputFrame非空(第94行 ), 如果一個幀被從攝像機傳感器中丟棄,那麼這種情況就有可能發生。
- 在第98行將frame編碼為一個JPEG 圖像——在這裡執行JPEG壓縮以減少網絡負載,並且確保幀的快速傳輸。
- 檢查成功flag是否失敗(第101行和102行),這意味著如果此JPEG壓縮過程失敗,我們就應該忽略該幀。
- 最後,將編碼的JPEG以一個字節數組提供給一個可以解析它的網絡瀏覽器。
在這麼短的代碼中做了這麼多工作,所以一定要檢查這個函數幾次,以確保您瞭解它是如何工作的。
下一個函數video_feed會調用我們的generate函數:
注意這個app.route函數簽名,就像上面的index函數一樣。
這個app.route簽名告訴Flask這個函數是一個URL端點,數據是從http://your_ip_address/video_feed提供的。
video_feed的輸出是實時運動檢測輸出,通過generate函數編碼為一個字節數組。您的網絡瀏覽器非常聰明,可以將這個字節數組作為一個實時輸出顯示在您的瀏覽器中。
我們最後的代碼塊處理解析命令行參數和啟動Flask應用程序的任務:
第118-125行處理解析命令行參數的任務。
這裡我們需要三個參數,包括:
- --ip: 您運行webstream.py文件的系統的IP地址。
- --port: Flask應用程序的運行端口號(對這個參數,您通常只需要提供一個值8000)。
- --frame-count: 在執行運動檢測之前用於累計和構建背景模型的幀數。默認情況下,我們使用32幀來構建背景模型。
第128-131行啟動一個線程用於執行運動檢測。
使用一個線程確保detect_motion函數可以安全地在後臺運行——它將不斷地運行和更新我們的outputFrame,以便我們可以為我們的客戶端提供任何運動檢測結果。
最後,第134和135行啟動Flask應用程序本身。
HTML頁面結構
正如我們在webstreaming.py中看到的,我們正在渲染一個名為index.html的HTML模板。
該模板本身由Flask 網絡框架進行填充,然後提供給網絡瀏覽器。
然後,您的網絡瀏覽器將生成的HTML呈現到您的屏幕上。
讓我們檢查一下index.html文件的內容:
我們可以看到,這是一個超級基礎的網頁;但是,請密切注意第7行——注意我們如何指示Flask動態呈現我們video_feed路徑的URL。
由於video_feed函數負責提供來自我們網絡攝像頭的幀,所以圖像的src將會被自動填充上我們的輸出幀。
然後,我們的網絡瀏覽器足夠智能,可以正確渲染網頁並提供實時視頻流。
將各部分整合在一起
現在我們已經寫完了我們項目的代碼,讓我們對其進行測試。
打開一個終端,執行以下命令:
正如您在視頻中看到的,我從多個瀏覽器打開了到Flask/OpenCV服務器的連接,每個瀏覽器都有多個選項卡。我甚至拿出我的iPhone,並打開了一些來自那裡的連接。服務器沒有跳過一個幀,持續地使用Flask和OpenCV可靠地提供幀。
加入嵌入式計算機視覺和深度學習革命!
我第一次彈吉他是在20年前,那時我還在上中學。我不太擅長,幾年後我就放棄了。回顧過去,我堅信我沒有堅持下去的原因是因為我沒有以一種實際的、動手操作的方式來學習。
相反,我的音樂老師一直向我腦子裡灌輸理論——但作為一個11歲的孩子,我只是想弄清楚我是否喜歡彈吉他,更不用說我是否想研究音樂背後的理論了。
大約一年半以前,我決定重新開始上吉他課。這一次,我特意找了一位能夠將理論與實踐相結合的老師,教我如何在學習理論技巧的同時演奏歌曲或即興重複樂段。
結果呢?我的手指速度比以往任何時候都快,我的節奏也很準,我可以不斷地惹惱我的妻子,在我的Les Paul上演奏Sweet Child of Mine。
我的觀點是,無論何時您在學習一項新技能時,無論是計算機視覺,還是使用樹莓派進行滲透,甚至是彈吉他,最快、最簡單的學習方法之一就是圍繞這項技能設計(小的)現實世界的項目,並嘗試解決它。
對於吉他來說,這意味著學習短的即興重複,這不僅教會了我真正的歌曲的一部分,也給了我一個寶貴的技巧(例如,掌握一種特定的五聲音階)。
在計算機視覺和圖像處理中,您的目標應該是思考一些小型項目,然後嘗試解決它們。不要太快就把事情複雜化,那是失敗的秘訣。
相反,您可以拿一本我寫的《樹莓派計算機視覺》書,讀一讀,把它作為您個人項目的一個啟動平臺。
當您讀完之後,回到那些最激勵您的章節,看看您能如何以某種方式擴展它們(即使只是將相同的技術應用到一個不同的場景中)。
解決您思考的小項目不僅會讓您對這個主題感興趣(因為您自己想到了它們),而且它們還會教您實踐技能。
今天的教程——運動檢測和流式傳輸到網絡瀏覽器——就是這樣一個迷您項目的一個很好的起點。我希望既然您已經閱讀了本教程,您已經對如何將這個項目擴展到您自己的應用程序進行了思考。
但是,如果您有興趣學習更多……
我的新書《樹莓派計算機視覺》中有40多個與嵌入式計算機視覺+物聯網(IoT)相關的項目。您可以根據書中的項目進行構建來解決家裡、公司甚至客戶的問題。每一個項目都有著重方向:
- 通過實踐進行學習
- 捲起您的袖子
- 對代碼和實現進行實際測試
- 使用樹莓派構建實際的、實用的項目
幾個精品項目包括:
- 晝夜野生動物監視
- 交通工具計數和車速檢測
- 深度學習分類、對象檢測和資源受限設備上的實例分割
- 手勢識別
- 基本的機器人導航
- 安全應用程序
- 教室出勤情況
- 等等更多!
本書還涵蓋了使用谷歌Coral和Intel Movidius NCS協作處理器(Hacker + Complete Bundles)的深度學習。當需要更多的深度學習能力時,我們還將引入NVIDIA Jetson Nano(Complete Bundle)。
如果您錯過了Kickstarter,您可以看看我的公告視頻:
您準備好和我一起學習計算機視覺和如何應用嵌入式設備(如樹莓派、Google Coral和NVIDIA Jetson Nano)了嗎?
如果是這樣,請使用下面的鏈接查看這本書!
總結
在本教程中,您學習瞭如何將幀從一個服務器機器流式傳輸到一個客戶端網絡瀏覽器。使用這個網絡流式傳輸程序,我們能夠構建一個基本的安全應用程序來監控我們房子裡的一個房間的運動。
背景去除是計算機視覺中最常用的一種方法。通常,這些算法的計算效率很高,這使得它們適合於資源受限的設備,如樹梅派。
在實現了我們的背景去除器後,我們將其與Flask 網絡框架結合,這使得我們能夠:
- 從RPi攝像機模塊/USB網絡攝像頭訪問幀。
- 對每個幀應用背景去除/運動檢測。
- 將結果流式傳輸到一個網頁頁面/網頁瀏覽器。
此外,我們的實現支持多個客戶端、瀏覽器或選項卡——這在大多數其它實現中都找不到。
無論何時您需要將幀從一個設備流式傳輸到一個網絡瀏覽器時,一定要使用此代碼作為一個模板/起點。
英文原文:https://www.pyimagesearch.com/2019/09/02/opencv-stream-video-to-web-browser-html-page
閱讀更多 Python部落 的文章