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 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
|
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Lucas-Kanade Optical Flow Example</title>
<link href="js_example_style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h2>Lucas-Kanade Optical Flow Example</h2>
<p>
Click <b>Start/Stop</b> button to start or stop the video.<br>
The <b>videoInput</b> is a <video> element used as input.
The <b>canvasOutput</b> is a <canvas> element used as output.<br>
To decide the points, we use <b>cv.goodFeaturesToTrack()</b>. We take the first frame, detect some Shi-Tomasi corner points in it, then we iteratively track those points using <b>cv.calcOpticalFlowPyrLK</b>.<br>
The code of <textarea> will be executed when video is started.<br>
You can modify the code to investigate more.
</p>
<div>
<div class="control"><button id="startAndStop" disabled>Start</button></div>
<textarea class="code" rows="29" cols="100" id="codeEditor" spellcheck="false">
</textarea>
</div>
<p class="err" id="errorMessage"></p>
<div>
<table cellpadding="0" cellspacing="0" width="0" border="0">
<tr>
<td>
<video id="videoInput" width="320" height="240" muted></video>
</td>
<td>
<canvas id="canvasOutput" width="320" height="240" ></canvas>
</td>
<td></td>
<td></td>
</tr>
<tr>
<td>
<div class="caption">videoInput</div>
</td>
<td>
<div class="caption">canvasOutput</div>
</td>
<td></td>
<td></td>
</tr>
</table>
</div>
<script src="https://webrtc.github.io/adapter/adapter-5.0.4.js" type="text/javascript"></script>
<script src="utils.js" type="text/javascript"></script>
<script id="codeSnippet" type="text/code-snippet">
let video = document.getElementById('videoInput');
let cap = new cv.VideoCapture(video);
// parameters for ShiTomasi corner detection
let [maxCorners, qualityLevel, minDistance, blockSize] = [30, 0.3, 7, 7];
// parameters for lucas kanade optical flow
let winSize = new cv.Size(15, 15);
let maxLevel = 2;
let criteria = new cv.TermCriteria(cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 0.03);
// create some random colors
let color = [];
for (let i = 0; i < maxCorners; i++) {
color.push(new cv.Scalar(parseInt(Math.random()*255), parseInt(Math.random()*255),
parseInt(Math.random()*255), 255));
}
// take first frame and find corners in it
let oldFrame = new cv.Mat(video.height, video.width, cv.CV_8UC4);
cap.read(oldFrame);
let oldGray = new cv.Mat();
cv.cvtColor(oldFrame, oldGray, cv.COLOR_RGB2GRAY);
let p0 = new cv.Mat();
let none = new cv.Mat();
cv.goodFeaturesToTrack(oldGray, p0, maxCorners, qualityLevel, minDistance, none, blockSize);
// Create a mask image for drawing purposes
let zeroEle = new cv.Scalar(0, 0, 0, 255);
let mask = new cv.Mat(oldFrame.rows, oldFrame.cols, oldFrame.type(), zeroEle);
let frame = new cv.Mat(video.height, video.width, cv.CV_8UC4);
let frameGray = new cv.Mat();
let p1 = new cv.Mat();
let st = new cv.Mat();
let err = new cv.Mat();
const FPS = 30;
function processVideo() {
try {
if (!streaming) {
// clean and stop.
frame.delete(); oldGray.delete(); p0.delete(); p1.delete(); err.delete(); mask.delete();
return;
}
let begin = Date.now();
// start processing.
cap.read(frame);
cv.cvtColor(frame, frameGray, cv.COLOR_RGBA2GRAY);
// calculate optical flow
cv.calcOpticalFlowPyrLK(oldGray, frameGray, p0, p1, st, err, winSize, maxLevel, criteria);
// select good points
let goodNew = [];
let goodOld = [];
for (let i = 0; i < st.rows; i++) {
if (st.data[i] === 1) {
goodNew.push(new cv.Point(p1.data32F[i*2], p1.data32F[i*2+1]));
goodOld.push(new cv.Point(p0.data32F[i*2], p0.data32F[i*2+1]));
}
}
// draw the tracks
for (let i = 0; i < goodNew.length; i++) {
cv.line(mask, goodNew[i], goodOld[i], color[i], 2);
cv.circle(frame, goodNew[i], 5, color[i], -1);
}
cv.add(frame, mask, frame);
cv.imshow('canvasOutput', frame);
// now update the previous frame and previous points
frameGray.copyTo(oldGray);
p0.delete(); p0 = null;
p0 = new cv.Mat(goodNew.length, 1, cv.CV_32FC2);
for (let i = 0; i < goodNew.length; i++) {
p0.data32F[i*2] = goodNew[i].x;
p0.data32F[i*2+1] = goodNew[i].y;
}
// schedule the next one.
let delay = 1000/FPS - (Date.now() - begin);
setTimeout(processVideo, delay);
} catch (err) {
utils.printError(err);
}
};
// schedule the first one.
setTimeout(processVideo, 0);
</script>
<script type="text/javascript">
let utils = new Utils('errorMessage');
utils.loadCode('codeSnippet', 'codeEditor');
let streaming = false;
let videoInput = document.getElementById('videoInput');
let startAndStop = document.getElementById('startAndStop');
startAndStop.addEventListener('click', () => {
if (!streaming) {
utils.clearError();
videoInput.play().then(() => {
onVideoStarted();
});
} else {
videoInput.pause();
videoInput.currentTime = 0;
onVideoStopped();
}
});
function onVideoStarted() {
streaming = true;
startAndStop.innerText = 'Stop';
videoInput.height = videoInput.width * (videoInput.videoHeight / videoInput.videoWidth);
utils.executeCode('codeEditor');
}
function onVideoStopped() {
streaming = false;
startAndStop.innerText = 'Start';
}
videoInput.addEventListener('ended', () => {
onVideoStopped();
});
utils.loadOpenCv(() => {
videoInput.addEventListener('canplay', () => {
startAndStop.removeAttribute('disabled');
});
videoInput.src = 'box.mp4';
});
</script>
</body>
</html>
|