iT邦幫忙

0

請問opencv.js中的Template Matching問題?

請問js版本中的
Template Matching
一張圖中有好幾個一樣的物件
有辦法匹配多個物件將多個物件識別出來嗎?
我看範例是只有識別出一個
我自己試也只能匹配出一個

但是我看python的版本
可以辨認匹配多個物件
範例程式也有提供

如果要用js版本的話要如何寫呢?
我實在是想不到要如何寫
我其實是有想照著python的程式改成js
前面大概都看得懂大概在看嘛
就是後半部比較不知道在做什麼
而且我看python版本的範例有用到

import cv2 as cv
import numpy as np

第一個本來好像是opencv的
第二個是數學計算時需要用的程式庫
那我用js去做等於是要自己寫那個感覺很困難的計算??

網頁上的範例

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img_rgb = cv.imread('mario.png')
img_gray = cv.cvtColor(img_rgb, cv.COLOR_BGR2GRAY)
template = cv.imread('mario_coin.png',0)
w, h = template.shape[::-1]
res = cv.matchTemplate(img_gray,template,cv.TM_CCOEFF_NORMED)
threshold = 0.8
loc = np.where( res >= threshold)
for pt in zip(*loc[::-1]):
    cv.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0,0,255), 2)
cv.imwrite('res.png',img_rgb)
let src = cv.imread('imageCanvasInput');
let templ = cv.imread('templateCanvasInput');
let dst = new cv.Mat();
let mask = new cv.Mat();
cv.matchTemplate(src, templ, dst, cv.TM_CCOEFF, mask);
let result = cv.minMaxLoc(dst, mask);
let maxPoint = result.maxLoc;
let color = new cv.Scalar(255, 0, 0, 255);
let point = new cv.Point(maxPoint.x + templ.cols, maxPoint.y + templ.rows);
cv.rectangle(src, maxPoint, point, color, 2, cv.LINE_8, 0);
cv.imshow('canvasOutput', src);
src.delete(); dst.delete(); mask.delete();

2 個回答

0
jwaiting
iT邦新手 5 級 ‧ 2019-12-16 17:51:42

import cv2 as cv
第一個是include opencv2 另外幫它取個別名 叫cv
之後在乎叫這個函式庫的時候直接用cv即可

import numpy as np
numpy 則是python用來處理 數值的函示庫

您的問題具體可以參考以下解答
https://answers.opencv.org/question/165740/template-matching-multiple-objects/

重點在於您的程式碼中cv.minMaxLoc(dst, mask);
result.maxLoc;
選取到唯一的最佳解
所以才會只有一個最佳的物件被偵測出來

物件偵測這一部分我沒有實際做過
所以可能還有其他細節可能還要請您再嘗試嘗試

我的想法是
python的結果是在res那個變數中
js版本的結果是在dst那個變數中

python是用np.where把結果中所有>=閥值0.8的東西用for印出來

但js的卻不行用.where把結果比對出來
而且
js版本的dst結果

python的res結果
的內容
好像不能確定這兩個東西是不是一樣的?

因為js的matchTemplate中第三個參數dst就是outputarray
理論上吐出來的東西應該會有比對的資料

但是minMaxLoc永遠只能抓到最佳解
但是我一個大圖裡有好幾個重複的圖
我想將每個重複的圖都抓出來

只是我不知道有怎麼抓到那個範圍
然後把東西像是在python那樣用np.where撈出來

2
fillano
iT邦超人 1 級 ‧ 2019-12-23 18:22:03

如果用console.log觀察一下cv.Mat結構就會發現他有幾個重要的資料:

  1. cols
  2. rows
  3. data
  4. data8S
  5. data16U
  6. data16S
  7. data32S
  8. data32F
  9. data64F

做完cv.matchTemplate()的結果就是放在這些地方,看起來是data32F。(因為資料筆數跟cols*row一樣,而且minMaxLoc的結果也跟數值相符)你用兩個for迴圈遍蒞,x要小於cols,y要小於rows,就可以得到每一個數值的座標。根據你用的tm方法,給一個閥值來篩選,就可以得到多筆結果。

看更多先前的回應...收起先前的回應...
fillano iT邦超人 1 級 ‧ 2019-12-25 11:51:05 檢舉

補充一下,遍歷時用Mat.floatPtr(col, row)方法來取值比較準確,不要直接操作Mat.data32F

fillano iT邦超人 1 級 ‧ 2019-12-25 16:20:38 檢舉

再補充一下,python的那個例子,用的方法是TM_CCOEFF_NORMED,他會把結果的數值分布到0...1之間,所以可以用0.8作為閥值來過濾。不是這樣處理的話,即使知道最大值及他的座標,也無法確定是否是最佳解。

fillano iT邦超人 1 級 ‧ 2019-12-25 16:24:39 檢舉

既然你想where,還是寫個where:

<!doctype html>
<html lang="en">

<head>
	<style>
		.container {
			width: 100%;
		}

		.panel {
			float: left;
			width: 45%;
		}

		div#errorpanel {
			width: 100%;
		}
	</style>
</head>

<body>
	<div class="container">
		<div class="panel">
			<canvas id="srcC"></canvas>
			<br>
			<input type="file" id="srcI" disabled />
			<br>
			<canvas id="tmpC"></canvas>
			<br>
			<input type="file" id="tmpI" disabled />
		</div>
		<div class="panel">
			<canvas id="resC"></canvas>
			<br>
			<button id="go" disabled>go</button>
		</div>
	</div>
	<div class="container">
		<div id="errorpanel">
		</div>
	</div>
	<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
	<script src="3.4.9/utils.js"></script>
	<script src="3.4.9/opencv.js"></script>
	<script>
		$(() => {
			if (cv.getBuildInformation) {
				console.log("cv ready...");
				show(cv);
			} else {
				cv['onRuntimeInitialized'] = () => {
					console.log("cv ready...");
					show(cv);
				}
			}
			let util = new Utils('errorpanel');
			util.addFileInputHandler('srcI', 'srcC');
			util.addFileInputHandler('tmpI', 'tmpC');
			$('#go').click(() => {
				let src = cv.imread('srcC');
				let templ = cv.imread('tmpC');
				let dst = new cv.Mat();
				let mask = new cv.Mat();
				//cv.matchTemplate(src, templ, dst, cv.TM_CCOEFF, mask);
				cv.matchTemplate(src, templ, dst, cv.TM_CCOEFF_NORMED, mask);
				//let result = minMaxLocs(dst);
				let result = num(dst).where(v => v > 0.8);
				let color = new cv.Scalar(255, 0, 0, 255);
				//result.max_loc.forEach(p => {
				result.forEach(p => {
					let br = new cv.Point(p.x + templ.cols, p.y + templ.rows);
					cv.rectangle(src, p, br, color, 2, cv.LINE_8, 0);
				});
				cv.imshow('resC', src);
				src.delete();
				dst.delete();
				mask.delete();
				templ.delete();
			});
		});

		function num(mat) {
			return {
				where: cb => {
					let loc = [];
					for (let y = 0; y < mat.rows; y++) {
						for (let x = 0; x < mat.cols; x++) {
							if (cb(mat.floatAt(y, x))) loc.push(new cv.Point(x, y));
						}
					}
					return loc;
				}
			};
		}

		function minMaxLocs(mat) {
			let max_loc = [];
			let min_loc = [];
			let max = Number.MIN_VALUE;
			let min = Number.MAX_VALUE;
			for (let y = 0; y < mat.rows; y++) {
				for (let x = 0; x < mat.cols; x++) {
					let _t = mat.floatAt(y, x);
					if (_t > max) {
						max = _t;
						max_loc = [];
					}
					if (_t < min) {
						min = _t;
						min_loc = [];
					}
					if (Math.abs(max - _t) === 0) {
						max_loc.push(new cv.Point(x, y));
					}
					if (Math.abs(min - _t) === 0) {
						min_loc.push(new cv.Point(x, y));
					}
				}
			}
			return {
				min: min,
				min_loc: min_loc,
				max: max,
				max_loc: max_loc
			};
		}

		function show() {
			$('#srcI').removeAttr('disabled');
			$('#tmpI').removeAttr('disabled');
			$('#go').removeAttr('disabled');
		}
	</script>
</body>

</html>

跑出來的結果...
單一:
https://ithelp.ithome.com.tw/upload/images/20191225/20000108FOk6MTIACY.png

多個結果:
https://ithelp.ithome.com.tw/upload/images/20191225/20000108rGReoGR970.png

jwaiting iT邦新手 5 級 ‧ 2019-12-30 16:11:32 檢舉

大大好猛 實戰派來著.....

感謝大大
我前幾天已經有解出來

我是把
data32F塞進
cols*rows大小的二維陣列
感覺用for迴圈跑比較陽春一點
圖比較大張的話變得很lag

另外想請問
閥值會因為圖片的關係
應該是會有一個最佳的設定值?
我的template是固定的

比如說我比對時
我的閥值
設定0.96的話
圖片一片紅
因為大於0.96的地方一大堆
尤其是如果圖片有一區色塊或線條

但是調成0.97的話剛剛好可以把所有相同處比對出來
但是我要怎麼知道閥值要設定成多少?

因為我根本不知道使用者
會拿什麼圖片跟我的template比對

所以如果要解決這個問題
是不是要讓閥值可以讓使用者調整?

或是讓使用者輸入當前圖片有幾個相同處
比如說有6個
讓程式只抓閥值前6大的陣列位置 i 和 k ?

let src = cv.imread(imgSrcElement);
				let templ = cv.imread(templateSrcElement);
				let dst = new cv.Mat();
				let mask = new cv.Mat();
				
				cv.matchTemplate(src, templ, dst, cv.TM_CCORR_NORMED, mask);
				
				let color = new cv.Scalar(255, 0, 0, 255);
				
				var newDst = [];
				var start = 0;
				var end = dst.cols;
				
				for (var i = 0; i < dst.rows; i++) {

					newDst[i] = [];
					for (var k = 0; k < dst.cols; k++) {
						
						newDst[i][k] = dst.data32F[start];
						
						if (newDst[i][k] > 0.97 ) {
							
							let maxPoint = {
								"x": k,
								"y": i
							}
							let point = new cv.Point(k + templ.cols, i + templ.rows);
							cv.rectangle(src, maxPoint, point, color, 1, cv.LINE_8, 0);
						}
						start++;
					}
					start = end;
					end = end + dst.cols;
				}
				cv.imshow('canvasOutput', src);

如圖
0.97時
https://ithelp.ithome.com.tw/upload/images/20191231/20113281EADPMQXPFp.png

但是0.96時
https://ithelp.ithome.com.tw/upload/images/20191231/201132815G6U304MYT.png

另外請問大大utils.js是哪一個js的函式庫
我google到的好像都報錯

fillano iT邦超人 1 級 ‧ 2019-12-31 17:40:59 檢舉

其實看一下opencv.js範例的source,裡面就有。最快的方式是從文件裡面抓:https://docs.opencv.org/

下載某個版本的文件然後解開,opencv.js跟utils.js都在根目錄。(我試著在windows編譯都不成功,所以只找已經編譯好的)

閥值設多少比較好,其實我也不那麼清楚...你得知道它的演算法。另外,恐怕圖片本身的複雜度也會有關係。如果不想用閥值,我有寫一個minMaxLocs函數,它同樣是取最大及最小值的位置,只是結果可能是多個(如果有相同的值)。

演算法可以參考matchTemplate的文件:https://docs.opencv.org/3.4.9/df/dfb/group__imgproc__object.html

我要發表回答

立即登入回答