之前在Python上實現了最基礎的手勢方向的判定功能,因爲整體項目是WEB端的,所以需要向WEB平臺遷移...
通過查閱資料,opencv.js官方文檔還是給的比較全的,不過裏面部分函數的輸出實在是不太能看的明白....在後面實現的時候帶來的很大的困擾...甚至一度影響了進度..
1.頁面準備
在WEB頁面的攝像頭獲取基本邏輯和Python中的差不多,只不過在html中需要<video>和<canvas>兩個標籤,這兩個都是H5中的新標籤,<video> 標籤定義視頻,比如電影片段或其他視頻流而<canvas>標籤定義圖形,比如圖表和其他圖像。需要注意的是<canvas> 標籤只是圖形容器,必須使用腳本來繪製圖形.
所以一個基本的用於獲取攝像頭視頻流並展示的html頁面如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Video Capture Example</title>
</head>
<body>
<video id="videoInput" height=240 width=320></video>
<canvas id="canvasFrame" height=240 width=320 ></canvas>
<canvas id="FirstFrame" height=240 width=320 ></canvas>
<canvas id="canvasOutput" height=240 width=320 ></canvas>
<script src="js代碼邏輯" type="text/javascript"></script>
</body>
</html>
2.視頻捕獲
清楚了<video>和<canvas>兩個基本標籤以後,我們就可以開始寫js的邏輯了.
最簡單的視頻獲取如下:
主要是基本參數設置.
let video = document.getElementById("videoInput");
navigator.mediaDevices.getUserMedia({ video: true, audio: false })
.then(function (stream) {
video.height = HEIGHT;
video.width = WIDTH;
video.srcObject = stream;
video.play();
}).catch(function (err) {
console.log("An error occured! " + err);
});
3.視頻輸出
在獲取了視頻以後,我們後續的步驟是一系列的視頻圖像處理和輸出,所以首先解決輸出問題.
輸出的邏輯主要是獲取到Canvas,然後通過canvas容器得到上下文,drawImage.
對於processVideo中的內容,則是一個循環的調用,從而實現持續的視頻流輸出.否則只是輸出了單幀.
let canvasFrame = document.getElementById("canvasFrame"); // canvasFrame is the id of <canvas>
let context = canvasFrame.getContext("2d");
let src = new cv.Mat(height, width, cv.CV_8UC4);
let dst = new cv.Mat(height, width, cv.CV_8UC1);
const FPS = 30;
function processVideo() {
let begin = Date.now();
context.drawImage(video, 0, 0, width, height);
src.data.set(context.getImageData(0, 0, width, height).data);
cv.cvtColor(src, dst, cv.COLOR_RGBA2GRAY);
cv.imshow("canvasOutput", dst); // canvasOutput is the id of another <canvas>;
// schedule next one.
let delay = 1000/FPS - (Date.now() - begin);
setTimeout(processVideo, delay);
}
// schedule first one.
setTimeout(processVideo, 0);
結果展示:
上圖是輸入,下圖是輸出,無任何區別.
4.視頻處理
在清楚了基本的視頻流輸入輸出以後,就按照之前Python中處理的邏輯遷移過來(這個過程是十分辛苦心酸的...在Python中的函數調用返回以及文檔都十分全面,網絡上的各類解答也很詳細,然而到了opencv.js以後...除了官方文檔,其他的信息和資料幾乎沒有,而且官方文檔的輸入輸出以及一些特殊的數據形式和python的區別還是不小的...用的時候十分頭疼)
4.1 基本處理
var frame = new cv.Mat(HEIGHT, WIDTH, cv.CV_8UC4);
cap.read(frame);
// cv.bilateralFilter(frame, frame, 5, 50, 100,cv.BORDER_DEFAULT) // 雙邊濾波 gg
cv.flip(frame,frame,1) // 反轉
var gray = new cv.Mat();
cv.cvtColor(frame, gray, cv.COLOR_RGBA2GRAY);
var ksize = new cv.Size(21,21);
cv.GaussianBlur(gray,gray, ksize ,0)
// console.log(firstFrame)
這部分主要是對圖像反轉,進行雙邊濾波,然後灰度化+高斯模糊,方便後面的幀間差分.
但是這個地方有一個問題一直沒解決,就是雙邊濾波! 參數的傳入是嚴格按照官方文檔寫的,但是報錯...(希望大家給出解決建議...)
___________________________________new
雙邊濾波問題解決,一個是順序問題.不知道爲什麼,必須要先灰度化才能做雙邊濾波 ,還有就是src和dst不能是同一個變量...
看來js和python之間還是有一些習慣上的差別..
4.2 幀間差分
每次記錄保存前一幀,然後和當前做幀間差分,隨後膨脹+二值化.這樣我們就可以捕獲當前畫面中正在移動的物體.
if(firstFrame == null){
console.log('enter')
firstFrame = gray;
setTimeout(processVideo,0)
}
cv.imshow("FirstFrame", frame);
// let frameDelta = new cv.Mat(HEIGHT, WIDTH, cv.CV_8UC1);
var frameDelta = new cv.Mat();
cv.absdiff(first Frame, gray, frameDelta) // 幀間差
var thresh = new cv.Mat();
cv.threshold(frameDelta,thresh, 25, 255, cv.THRESH_BINARY)
let M = new cv.Mat();
let anchor = new cv.Point(-1, -1);
cv.dilate(thresh,thresh,M,anchor, 2)
這部分完成後效果如下:
當畫面發生變化,也就是有運動物體時,如下:
4.3 變化追蹤判斷
這部分邏輯很清晰,就是利用 goodFeaturesToTrack尋找到輪廓的中心點,然後可以根據中心點變化判斷運動方向
然而因爲goodFeaturesToTrack在js中...輸出十分混亂且沒看懂,所以問題先留到後面
_____________________________update
通過分析輸出的mat矩陣,如下圖可以發現.數值的變化是有規律的,最後經過測試發現2 6分別代表x,y的輸出座標.
有了座標以後就好做了,只需要計算座標變化,計算出運動方向即可.