opencv 模板匹配_纯前端实现图片的模板匹配

基础介绍

模板匹配是指在当前图像A里寻找与图像B最相似的部分,本文中将图像A称为模板图像,将图像B称为搜索匹配图像。

引言:一般在Opencv里实现此种功能非常方便:直接调用

result = cv2.matchTemplate(templ, search, method)
  • templ 为原始图像
  • search 为搜索匹配图像,它的尺寸必须小于或等于原始图像
  • method 表示匹配方式

method一般取值:

type CompareWay =
  | "CV_TM_SQDIFF"
  | "CV_TM_SQDIFF_NORMED"
  | "CV_TM_CCORR"
  | "CV_TM_CCORR_NORMED"
  | "CV_TM_CCOEFF"
  | "CV_TM_CCOEFF_NORMED";

当然这里我们不是主要讲Opencv的api的,只是单独提出来,说明在前端实现对应的算法,就能进行模板匹配。

比如以CV_TM_SQDIFF算法为例:

  1. 遍历的起始坐标从原图A的左数第1个像素值开始
  2. 以搜索匹配B图的大小(w * h)匹配比较原图上对应空间上(w * h)的像素值
  3. 依次进行A图右移一像素去匹配B图,直到A图右侧(w)小于B图的w,然后换行再匹配
  4. 重复进行到A图距离底部不支持h大于B图的高度
  5. 最后找出最小误差值

我们的目标是实现这两张图的匹配:

68186518be9bbb6dd9b91fc666674665.png

903f972db171d97807781b664b3336e7.png

这里实现对应的js算法

/**
 * 差值平方和匹配 CV_TM_SQDIFF
 * @param template 匹配的图片灰度值[x,x,x,...] w * h 长度的灰度图片数据
 * @param search 搜索的图片灰度值[x,x,x,...] w * h 长度的灰度图片数据
 * @param tWidth 匹配图片的width
 * @param tHeight 匹配图片的height
 * @param sWidth 搜索图片的width
 * @param sHeight 搜索图片的height
 */
const cvTmSqDiff = (template, search, tWidth, tHeight, sWidth, sHeight) => {
  let minValue = Infinity;
  let x = -1;
  let y = -1;
  for (let th = 0; th < tHeight; th += 1) {
    for (let tW = 0; tW < tWidth; tW += 1) {
      if (tW + sWidth > tWidth || th + sHeight > tHeight) {
        continue;
      }
      let sum = 0;
      for (let sH = 0; sH < sHeight; sH += 1) {
        for (let sW = 0; sW < sWidth; sW += 1) {
          const tValue = template[(th + sH) * tWidth + tW + sW];
          const sValue = search[sH * sWidth + sW];
          sum += (tValue - sValue) * (tValue - sValue);
        }
      }
      if (minValue > sum) {
        minValue = sum;
        x = tW;
        y = th;
      }
      if (sum === 0) {
        return { x, y };
      }
    }
  }
  return { x, y };
};

因此根据上述算法的可行性,我们可以先将A图和B图进行RGB值转Gary值: 借鉴OpenCV中的转换方式

Gray = 0.299*r + 0.587*g + 0.114*b

再将转换好A图和B图的灰度值进行匹配比较:

const {x, y} = cvTmSqDiff(template, search, tWidth, tHeight, sWidth, sHeight);

得到的xy则是在原图A上的对应匹配成功的坐标,加上对应B图的大小,我们则可以在原图的基础上画出一个矩形框表示匹配的区域:

d0f396c119bf3566bde7b6d2e7ec725f.png

前端分步实现

上面大概讲了匹配的大致实现思路,下面开始正式的js代码实现:

  • 1、加载原图A和原图B
Promise.all([imgLoader("./lena.png"), imgLoader("./search.png")]).then(
    (values: any) => {
        ...
    }
);
  • 2、得到图片数据的rgb值,并转化为灰度值
Promise.all([getImageData(values[0]), getImageData(values[1])]).then(
    (dataValues: any) => {
        const model = rgbToGary(dataValues[0]);
        const search = rgbToGary(dataValues[1]);
        ...
    }
);
  • 3、获取对应的匹配坐标
const posi = getTemplatePos(
    model,
    search,
    dataValues[0].width,
    dataValues[0].height,
    dataValues[1].width,
    dataValues[1].height,
    "CV_TM_CCOEFF_NORMED"
);
  • 4、绘制原图和匹配到矩形框
const canvas = document.createElement("canvas");
canvas.width = dataValues[0].width;
canvas.height = dataValues[0].height;
const ctx = canvas.getContext("2d");

ctx.drawImage(values[0], 0, 0);
ctx.strokeStyle = "red";
ctx.strokeRect(
    posi.x,
    posi.y,
    dataValues[1].width,
    dataValues[1].height
);
document.body.appendChild(canvas);

上述所用的的函数imgLoader getImageData rgbToGary getTemplatePos 都可以在这里找到xy-imageloader

也可以npm安装:npm i xy-imageloader

完整代码

import imgLoader, { getImageData, rgbToGary } from "xy-imageloader";
import { getTemplatePos } from "xy-imageloader/lib/utils";
Promise.all([imgLoader("./lena.png"), imgLoader("./search.png")]).then(
    (values: any) => {
        Promise.all([getImageData(values[0]), getImageData(values[1])]).then(
            (dataValues: any) => {
            const model = rgbToGary(dataValues[0]);
            const search = rgbToGary(dataValues[1]);
            const posi = getTemplatePos(
                model,
                search,
                dataValues[0].width,
                dataValues[0].height,
                dataValues[1].width,
                dataValues[1].height,
                "CV_TM_CCOEFF_NORMED"
            );
            const canvas = document.createElement("canvas");
            canvas.width = dataValues[0].width;
            canvas.height = dataValues[0].height;
            const ctx = canvas.getContext("2d");

            ctx.drawImage(values[0], 0, 0);
            ctx.strokeStyle = "red";
            ctx.strokeRect(
                posi.x,
                posi.y,
                dataValues[1].width,
                dataValues[1].height
            );
            document.body.appendChild(canvas);
            }
        );
    }
);

附算法思想:

* CV_TM_SQDIFF

50e23ddd5abf5b13e5fb25cbcb62439b.png
  • CV_TM_SQDIFF_NORMED

47faa5ec554b94cefe3a55370061d69c.png
  • CV_TM_CCORR

58e537f812ed3b67e4df005165883b02.png
  • CV_TM_CCORR_NORMED

3ad8e85db5967aa4c07196228dfba720.png
  • CV_TM_CCOEFF

0670d0baee66d3b1b6498669099cdf62.png
  • CV_TM_CCOEFF_NORMED

d990aec49e7c6fa91de660dd5edcd186.png
算法具体实现可参考 xy-imageloader
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值