用網路攝影機測量心跳

pondahai
7 min readApr 23, 2019

--

應用光的穿透特性可以測量流經軟組織的血管血液流量變化,這是很直覺的量測方法,也常見於現今生理監視器的量測原理中。

上次在做python opencv 網路攝影機程式練習的時候,突發奇想把手指貼在網路攝影機想看看能不能收到手指血管的脈動,為了讓脈動信號更明顯,一開始還有打光輔助。

利用手機打光穿透手指後讓網路攝影機接收
程式捕捉到的畫面一片通紅

經過觀察,因為在程式中只捕捉畫面中間的像素變化,而這樣的變化量太少,於是想到統計上的技巧,把整個畫面做平均那變化的量就可以累積起來。後來程式經過一番修改之後,以下說明程式的內容。

這次材料只需要準備電腦一部、網路攝影機一隻即可。

電腦上需要安裝python3,作業系統必須是ubuntu或是mac

首先從原始碼(在這裡)看所需的python套件:

import argparse #用來剖析命令列選項
import cv2 #用來處理影像輸入
import numpy as np #用來處理陣列資料
import matplotlib.pyplot as plt #用來繪製數據圖表
from scipy.signal import detrend #沒用到~

接著有幾個副程式。因為程式一開始實驗的時候,是把捕捉到的像素三原色變化程度直接描繪在畫面空間中,因此準備了一個畫線副程式,後來因為改用matplotlib來繪製圖表,因此這邊的副程式就沒有再動它。另外這組程式有設計命令列參數,用來指定網路攝影機的裝置編號,目前在mac電腦上是預設為 /dev/video0。

def parse_args(): #剖析命令列參數的副程式
def line(x0, x1, y0, y1, func): #畫線副程式

下面是主程式開始的地方。

if __name__ == "__main__": #主程式由此去

下面這一行是用來設定數據圖表的配置,他會配置縱列3個圖表,整個圖表視窗的大小是8英吋x8英吋。

    fig, ax = plt.subplots(3,figsize=(8,8),linewidth=0.1)

下面這一行負責處理從程式啟動時由命令列送來的參數。副程式會將參數的設定結果寫入指定的變數中,如果參數格式有誤則會列印出參數說明。當然這部分該有什麼參數以及說明的內容都是人為設計進去的。

    args = parse_args()

接下來這一行啟動opencv的影像擷取API。啟動的對象裝置來自於上面的參數指定。

    cap = cv2.VideoCapture(args.video_dev)

接著將捕捉畫面設定小一點,以利資料處理的速度。

    cap.set(cv2.CAP_PROP_FRAME_WIDTH, 300)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 300)

接著設定圖表區的格式。這次設計的系統行為將擷取到的畫面像素們做平均後取得的三原色R,G,B們的分量。並且用陣列變數分別存放起來,然後會經過一個簡單的信號處理將信號維持在0的上下值做變化,這樣一來信號呈現的波形才能夠集中在圖表中的原點橫軸線上。所以圖表區會設定三個區域分別顯示R,G,B的變化量。

    li1, = ax[0].plot(xx, yy, color='r')
ax[0].set_xlim(0,DATA_LENGTH)
ax[0].set_ylim(-255,255)
li2, = ax[1].plot(xx, yy, color='g')
ax[1].set_xlim(0,DATA_LENGTH)
ax[1].set_ylim(-255,255)
li3, = ax[2].plot(xx, yy, color='b')
ax[2].set_xlim(0,DATA_LENGTH)
ax[2].set_ylim(-255,255)

接下來會進入程式主迴圈。並且每次回圈開始時就去擷取一個圖框(frame)的畫面。

    while(True):
ret, frame = cap.read()

接著把畫面中的所有像素的r,g,b成分分開,個別取平均之後再存入一個陣列變數avg_color

            b, g, r = cv2.split(frame)
avg_color = [np.average(b),np.average(g),np.average(r)]

把一整個畫面frame平均成三原色的變數avg_color後,存入一個大陣列中,目的是為了呈現波形的樣子。這個陣列的長度代表波形的長度,一但存滿就把最舊資料踢出然後存入新資料,這樣就會形成波形動畫的效果。

            drawDataList.append(avg_color)
if len(drawDataList) > DATA_LENGTH :
drawDataList.pop(0)

由於信號微弱,因此在呈現時需要放大,但又由於圖表區域有限,因此需要一個機制將波形穩定在圖表原點軸線上,所以這裡使用一種最簡單的直流移除演算法,也是看作是一種高通濾波方法(因為直流也是一種低頻訊號,將直流濾除就是高通):將新數值減去過去片段數值的平均。下面第一行是將三原色資料從大陣列中分別取出來,然後個別做高通濾波。

            result = np.reshape(drawDataList,(len(drawDataList),3))
red = 255 - result[:,2]
red_filtered = red - np.average(red[-32:])
#
green = 255 - result[:,1]
green_filtered = green - np.average(green[-32:])
#
blue = 255 - result[:,0]
blue_filtered = blue - np.average(blue[-32:])

最後將三原色的資料陣列放入圖表物件中,就完成資料顯示的準備。

            li1.set_xdata(np.arange(len(red_filtered)))
li1.set_ydata(red_filtered*100)
li2.set_xdata(np.arange(len(green_filtered)))
li2.set_ydata(green_filtered*100)
li3.set_xdata(np.arange(len(blue_filtered)))
li3.set_ydata(blue_filtered*100)

呼叫matplotlib的pause函式可令繪圖區進行繪製。

            plt.pause(0.001)

下列影片呈現首次捕捉到心跳的波形以及後續將信號處理最佳化的結果。

以上。

--

--

pondahai
pondahai

Written by pondahai

有容乃大 海納百川 我是彭大海 以前是工程師 現在不是工程師 贊助鏈結: https://www.paypal.me/pondahai

No responses yet