[Python作品]Python程設初體驗 ~之~ OpenCV分析監視器影像抓老鼠

哈小弟好像很久沒有寫部落格了~(っ・Д・)っ

體驗了將近3年特別而難得的經驗後,如今又站在新的分岔路上煩惱了。
也許人生就是由希望與絕望交織而成的交響詩篇吧XD
無論發生什麼事,能有機緣體驗一切走一遭,也許是件好事呢
不過照這個話題再寫下去就要變落落長廢話大叔了,總之下個小結吧

小結:小弟待業中 乾....



前情提要分隔線



小弟家裡一直有幾位老住戶,住得很舒服很愜意。
牠們的名字叫做波波↓

  
(圖一) 小鼠波波@廚房
Key錯圖了,迪●尼請不要吉小弟(´・ω・`)
小鼠波波相關介紹:https://mamilove.com.tw/groupbuy/1245



為了悉心養育小鼠波波,小弟家裡背後付出了極大的代價...


  • 原本骯髒不堪雜物滿滿的老家,成為波波愛的小窩和遊樂場(捉迷藏?)

  • 家父三不五時會使用的廚房成為波波的美食天堂,波波也用滿滿的油腳印跟米田共回饋給家父(會不會有鼠疫@@)

  • 舍妹的吶喊(所以搬走了)

  • 放一堆黏鼠板希望亂槍打鳥,導致老家室內景觀無法直視(還會踩到黏鼠板阿乾~~)









《孫子兵法》
孫子曰:「知己知彼,百戰不殆;不知彼而知己,一勝一負;不知彼,不知己,每戰必殆。」



雖身為待業廢宅,小弟仍然保持一顆熱血而向上的心。回到老家看到問題,這裡好歹是從小成長的地方,總還是要想想辦法解決啊!所以總之先來研究一下小鼠波波的生態、活動路徑、活躍時間、了解波波性格等等。剛好小弟手邊也有監視器,就先從波波覓食處 【廚房】 先偵測起嘍~
因為近年的家用監視器\攝影機幾乎都有內建移動物體偵測,把偵測靈敏度調最高,監視器就會按設定時段、條件等等自動錄下物體移動的片段。
結果也著實讓人欣慰(如圖一),真的錄到了豪開心啊~!

捕捉畫面成功後,下一步要了解波波的移動路徑、活動範圍、時間等等。剛好家裡有第2台監視器(到底為啥要買這麼多監視器啊),就放在廚房鄰接的 【客廳】 嘍~為了捕捉波波移動路徑,小弟把監視器放在廣角可以拍到整個客廳的位置,設置完後放置一晚期待結果。但是……




(圖二) 希望捕捉小鼠波波動態的監視器視角


一.個.鬼.影.都.沒.拍.到

※沒可能的!?已經設定偵測靈敏度最高還拍不到動靜!?※



原來……因為監視器是有偵測到物體移動才錄影,而波波太小隻偵測不到,既然偵測不到就不會把畫面錄下來了RRRRRRRRRR(崩潰)

這樣一來,如果小弟要把當時波波的畫面也錄下來,就要全時段不間斷錄像,然後錄完就要像大樓管理員一樣盯著監視器畫面不放、看有沒有閒雜人等進出。這樣小弟每天看監視器畫面就飽了啊!(/‵Д′)/~ ╧╧ (再崩潰)





正文分隔線





既然監視器內建的移動物體偵測不敷小弟使用,那不如就自己來搞個偵測更靈敏的版本嘍~小弟最初構想是:用監視器連續錄製特定時段(例如PM6:00~AM7:00),然後再用相關的影片分析軟體來抓畫面的極微小變化

本來也有研究Tracker,但發現不合用;而且也找不到其他相關的套裝分析軟體。後來稍微上網查了一下,發現最近G. T. Wang大的部落格有寫個很不錯的方法:基於Python掛載OpenCV,可以做為監視器影片偵測、也可應用在雲端監視器實時分析,後續也可以再多加想要的分析等等,彈性度高+可再擴充功能的優點,真的蠻不錯的。詳見連結[1][2]。

資料來源:
[1] Python 與 OpenCV 實作移動偵測程式教學,打造智慧型監視器
https://blog.gtwang.org/programming/opencv-motion-detection-and-tracking-tutorial/
[2] OpenCV 擷取網路攝影機串流影像,處理並寫入影片檔案教學
https://blog.gtwang.org/programming/opencv-webcam-video-capture-and-file-write-tutorial/



然而因為小弟愚笨,也沒寫過Python,光一開始就遇到不知怎麼安裝Python到掛載OpenCV、以及選用合適的Python IDE(整合開發環境)……還好網路資源豐富,隨手一撈就是巨量資源阿哈哈哈~(迷之聲:要認真學似乎還是買書打底為上,google為輔,千萬別亂腦補阿…QQ)。靠著google大神無比神奇的力量,總算搞定安裝的問題了,安裝方法請參見連結[3][4]。另外小弟最後主要先安裝的IDE是選PyCharm,因為包山包海我洗翻~安裝方法請參見連結[5];至於其他不錯的IDE如Sublime, Vim, Spyder....等等,各自都有獨到之處跟適合的應用背景(ex:簡潔快速\網頁用\數值分析等等),不過小弟也完全沒試過,也沒辦法做啥評論嘍~覺得哪位夫人比較適合自己,可以多看其他網友評論多多了解一下~

安裝方法:
[3] Windows下Python安装OpenCV详细步骤
https://blog.csdn.net/u010128736/article/details/52713204
[4] 最简单方法:windows平台下python安装opencv,即实现import cv2功能
https://blog.csdn.net/lyj_viviani/article/details/59482602
[5] Pycharm及python安裝詳細教程
https://read01.com/zh-tw/EKnxLJ.html#.WsoUype-nb0







經過幾天的努力後,總算搞出一個可以跑的py程式拉~~猴開桑~~(ノ>ω<)ノ

Python與PyCharm介面(圖三) 執行Python程式碼下的樣貌


這邊小弟特別說明一下程式中有import(匯入)的幾個Lib(函式庫):


  1. 從G. T. Wang大的範本來看,程序至少要匯入"cv2", "numpy"這兩種函式庫,目的是要匯入OpenCV
    以進一步對影片做處理,例如存儲、模糊化、找區域、上繪圖線…等等(詳見底下"使用迴圈分析所有影片"部分程式碼)

  2. 因為小弟有一堆監視器錄製的影片要分析(至少幾百個以上),只能用些方法列出要分析的影片清單拉。
    方法也是上網搜索到的,至少要匯入"os"函式庫(詳見底下"搜尋影片檔案"部分程式碼)

  3. "List"(序列)是因為裡面的程式碼需用到需要儲存一堆檔名跟路徑在videoFile、videoFN等變數裡,
    PyCharm就自己偵測到語法問題並在前面自動加入import嘍

  4. 為了讓程式在print(印出字串)時,在某些特定階段能有些許時間暫停,所以小弟多匯入"time"函式庫。
    但……………這功能其實完全可以拿掉இдஇ(爆)



實際使用上,只要把要分析的影片檔丟到 "E:\!video!\input" 底下(可以自行修改程式碼)再跑程式後,就會自動把分析並縮減時間的所有影片存到 "E:\!video!\output" 嘍~偵測的靈敏度、要忽略的監視器畫面中時間數字部分等等,可以在"忽略太小的區域""忽略監視器時間區域"部分的程式碼做修改歐~

除了OpenCV分析影片以外,小弟主要還用到了一些函式跟程式寫法,詳見連結[6][7][8]。

資料來源:
[6] 一小時Python入門-part 1
https://hellolynn.hpd.io/2017/01/18/%E4%B8%80%E5%B0%8F%E6%99%82python%E5%85%A5%E9%96%80-part-1/
[7] 在Python中列出目录中的所有文件
https://taizilongxu.gitbooks.io/stackoverflow-about-python/content/39/README.html
[8] python字符串字串查找 find和index方法
http://outofmemory.cn/code-snippet/6682/python-string-find-or-index







以下是小弟拼湊出來的拼裝程式碼拉…
話說小弟完全是大外行,編程習慣也不太好,所以有什麼可以改進的地方還請各位先進多多賜教嘍(,,・ω・,,)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# 匯入OpenCV, Numpy...等功能
from typing import List
import cv2
import numpy as np
import os
from os import listdir
from os.path import isfile, join
import time

# 搜尋影片檔案
print('讀取相關影片檔:')
loadpath: str = 'E:\!video!\input'  # 影片來源資料夾
loadfnext: List[str] = ['.av', '.mp4', '.avi']  # 要過濾的影片副檔名,只要OpenCV支援的可以自己加
onlyfiles = [f for f in listdir(loadpath) if isfile(join(loadpath, f))]  # 只列表文件,把子目錄濾掉
videoFile: List[str] = []
videoFN: List[str] = []
for i in range(0, len(onlyfiles)):  # 從列表文件篩選影片副檔名,選出影片檔
    for j in range(0, len(loadfnext)):
        if onlyfiles[i].rfind(loadfnext[j]) != -1:
            videoFile.append(loadpath + '\\' + onlyfiles[i])
            videoFN.append(onlyfiles[i].replace(loadfnext[j], ''))
if len(videoFile) == 0:  # 若找不到任何影片檔,就結束程式
    print('找不到任何影片,結束程序...')
    quit()
for k in range(0, len(videoFile)):  # 顯示找到的影片檔
    print(videoFile[k])
print('共找到', len(videoFile), '個影片')

# 設定輸出目錄
savepath: str = 'E:\!video!\output'  # 影片輸出資料夾
if not os.path.exists(savepath):  # 自動建立目錄
    os.makedirs(savepath)

# 使用迴圈分析所有影片
time.sleep(1)
print('\n\n==============================================\n')
print('開始分析影片....\n')
for i in range(0, len(videoFile)):  # 解析影片
    cap = cv2.VideoCapture(videoFile[i])  # 開啟影片檔
    width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)  # 取得畫面尺寸-寬
    height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)  # 取得畫面尺寸-高
    print('序列: ', i, '\n影片位址: ', videoFile[i], '\n影片寬: ', int(width), '\n影片高: ', int(height), sep='')
    fourcc = cv2.VideoWriter_fourcc(*'XVID')  # 使用 XVID 編碼
    out = cv2.VideoWriter(savepath + '\\' + videoFN[i] + '.avi', fourcc, 16,
                          (int(width), int(height)))  # 建VideoWriter物件並輸出影片
    area = width * height  # 計算畫面面積
    ret, frame = cap.read()  # 初始化平均畫面
    blrlen = 2  # 模糊處理長寬
    mrphlen = 2  # 去除雜訊處理長寬
    avg = cv2.blur(frame, (blrlen, blrlen))  # 平均畫面模糊處理
    avg_float = np.float32(avg)  # 平均畫面轉浮點數
    frameCnt = 0  # 影片畫格計數器
    while cap.isOpened():
        ret, frame = cap.read()  # 讀取一幅影格
        frameCnt = frameCnt + 1
        if not ret:  # 若讀取至影片結尾,則跳出
            break
        blur = cv2.blur(frame, (blrlen, blrlen))  # 模糊處理
        # cv2.imshow('blur', blur)  # 解除註解可看畫面
        diff = cv2.absdiff(avg, blur)  # 計算目前影格與平均影像的差異值
        gray = cv2.cvtColor(diff, cv2.COLOR_BGR2GRAY)  # 將圖片轉為灰階
        # cv2.imshow('gray', gray)  # 解除註解可看畫面
        ret, thresh = cv2.threshold(gray, 10, 255, cv2.THRESH_BINARY)  # 篩選出變動程度大於門檻值的區域
        # cv2.imshow('thresh', thresh)  # 解除註解可看畫面
        kernel = np.ones((mrphlen, mrphlen), np.uint8)  # 使用型態轉換函數去除雜訊
        thresh = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
        thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=2)
        # cv2.imshow('thresh', thresh)  # 解除註解可看畫面
        cntImg, cnts, _ = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)  # 產生等高線
        movecatch = [0, False]
        for c in cnts:
            (x, y, w, h) = cv2.boundingRect(c)  # 計算等高線的外框範圍
            if cv2.contourArea(c) &lt; 30:  # 忽略太小的區域
                continue
            elif x + w &lt; 500 and y + h &lt; 55:  # 忽略監視器時間區域,米家(470,50),海爾(500,55)
                continue
            else:
                movecatch[0] = movecatch[0] + 1
                movecatch[1] = True
            cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 1)  # 篩選出的區域畫出方形外框
        cv2.drawContours(frame, cnts, -1, (255, 0, 255), 1)  # 畫出等高線(除錯用)
        # if frameCnt % 5 == 1:  # 解除註解可看畫面
        #     cv2.imshow('frame', frame)  # 顯示偵測結果影像
        if frameCnt % 500 == 0:
            print('&gt;&gt; 目前分析到影格[', frameCnt, '] &lt;&lt;', sep='')
        if movecatch[1]:  # 秀出該影格抓到的動態區域數
            print('影格=', frameCnt, ', 區塊=', movecatch[0], sep='')
            out.write(frame)  # 若有動靜則寫入影格
        if cv2.waitKey(1) &amp; 0xFF == ord('q'):  # 若顯示過程鍵盤按下"q"鍵則終止while迴圈
            break
        cv2.accumulateWeighted(blur, avg_float, 0.1)  # 更新平均影像
        avg = cv2.convertScaleAbs(avg_float)
        # cv2.imshow('avg', avg)  # 解除註解可看畫面
    print('\n')
    time.sleep(0.1)
cap.release()  # 釋放所有讀取中影片
cv2.destroyAllWindows()  # 關閉所有預覽畫面
print('\n\n==============================================\n')
print('影片分析完成!\n')






(動畫一) 分析影片去除不需要的影格後結果展現



最後讓人流淚的結果如以上影片(只是冰山一角阿@@)。
看來,波波們活得挺好的,每天幾乎都有牠們的蹤跡。
但,這只是一個開端而已……小弟跟小鼠波波們之間的戰爭與愛恨情仇,還要繼續演下去…………



◢▆▅▄▃崩╰(〒皿〒)╯潰▃▄▅▇◣

4 則留言:

匿名 提到...

版主是用哪個型號的攝影機?

Keaton [凱特] 提到...

小弟我用的是海爾無線監控攝像頭一體機1080p(沒有英文型號),之前在大陸工作時買的
順道一提,不管是用哪種型號的攝影機,都不會影響程序的執行喔~

匿名 提到...

版主~請問要怎麼把攝影機跟python結合...攝影機接上電源跟USB線後就好嗎?
(這邊只LIVE時候)

Keaton [凱特] 提到...

這個功能小弟還沒有玩過,不過要live實時偵測除了攝影機接PC,在程式碼上也要做修改喔
例如程式碼最開頭要加上cap = cv2.VideoCapture(1)跟迴圈偵測畫面的程序
詳情可以參考以下這篇網誌:
https://blog.gtwang.org/programming/opencv-webcam-video-capture-and-file-write-tutorial/

張貼留言