node识别图片缺口位置偏差太多,而且图片颜色也被改变了


const Jimp = require('jimp');
const cv = require('opencv4nodejs');

function pil_to_cv2(img) {
  const buffer = Buffer.from(img.bitmap.data);
  const imgMat = cv.imdecode(buffer);
  return imgMat;
}

function cv2_open(img, flag = null) {
  if (Buffer.isBuffer(img)) {
    img = cv.imdecode(img);
  } else if (typeof img === 'string') {
    img = cv.imread(img);
  } else if (img instanceof cv.Mat) {
    img = img;
  } else if (img instanceof Jimp) {
    img = pil_to_cv2(img);
  } else {
    throw new Error(`无法解析的图片类型: ${typeof img}`);
  }
  if (flag !== null) {
    img = img.cvtColor(flag);
  }
  return img;
}

async function get_distance(bg, tp, save_path = null) {
  // 读取图片并转为JPEG格式
  const bg_jimp = await Jimp.read(bg);
  const tp_jimp = await Jimp.read(tp);
  const bg_buffer = await bg_jimp.getBufferAsync(Jimp.MIME_JPEG);
  const tp_buffer = await tp_jimp.getBufferAsync(Jimp.MIME_JPEG);

  // 读取图片
  const bg_gray = cv2_open(bg_buffer, cv.COLOR_BGR2GRAY);
  const tp_gray = cv2_open(tp_buffer, cv.COLOR_BGR2GRAY);
  
  // 边缘检测
  const tp_grayCanny = tp_gray.canny(255, 255);
  const bg_grayCanny = bg_gray.canny(255, 255);
  
  // 目标匹配
  const result = bg_gray.matchTemplate(tp_gray, cv.TM_CCOEFF_NORMED);
  
  // 解析匹配结果
  const { minValue, maxValue, minLoc, maxLoc } = result.minMaxLoc();
  
  const distance = maxLoc.x;
  if (save_path || im_show) {
    // 需要绘制的方框高度和宽度
    const [tp_height, tp_width] = tp_gray.sizes;
    
    // 矩形左上角点位置
    const x = maxLoc.x;
    const y = maxLoc.y;
    
    // 矩形右下角点位置
    const _x = x + tp_width;
    const _y = y + tp_height;
    
    // 绘制矩形
    const bg_img = cv2_open(bg_buffer);
    bg_img.drawRectangle(new cv.Rect(x, y, tp_width, tp_height), new cv.Vec3(0, 0, 255), 2);
    
    // 保存缺口识别结果到背景图
    if (save_path) {
      const savePath = save_path.replace(/\.(\w+)$/, `.${distance}.$1`);
      await new Jimp({
        width: bg_img.cols,
        height: bg_img.rows,
        data: bg_img.getData()
      }).writeAsync(savePath);
    }
    
    // 显示缺口识别结果
    // if (im_show) {
    //   imshow(bg_img);
    // }
  }
  
  return distance;
}

(async () => {
  const d = await get_distance(
    './bg.jpg',
    './tp.png',
    './bg.jpg'
  );
  console.log(d);
})();

img

img

img

这段代码识别出来的缺口位置为什么偏差这么多~而且图片颜色也改变掉了

根据你的描述,效果图和代码来看,可能出现了以下问题:

  1. 颜色被改变:在读取 Jimp 图片后,进行了 JPEG 格式编码,在编码过程中可能会对颜色产生影响。可以尝试直接读取 PNG 格式图片,或者将 Jimp.MIME_JPEG 改为 Jimp.MIME_PNG

  2. 缺口位置偏差太多:在目标匹配时使用的是 cv.TM_CCOEFF_NORMED 方法,它只能粗略地识别缺口的大致位置。如果需要更准确地识别缺口位置,可以尝试使用模板匹配方法中的其他算法,比如 cv.TM_CCORRcv.TM_SQDIFF 等,并调整阈值等参数以获得更好的匹配结果。

关于修改方案,你可以参考以下示例代码:

const Jimp = require('jimp');
const cv = require('opencv4nodejs');

/**
 * 将 Jimp 图片对象转换成 opencv 的 Mat 对象
 * @param {*} img Jimp 图片对象
 */
function pil_to_cv2(img) {
  const buffer = Buffer.from(img.bitmap.data);
  const imgMat = cv.imdecode(buffer);
  return imgMat;
}

/**
 * 打开一个图片文件并返回对应的 Mat 对象,支持 PNG 和 JPG 两种格式
 * @param {*} img 待打开的图片文件路径或者数据流(Buffer)
 * @param {*} flag Mat 对象颜色空间转换标识
 */
function cv2_open(img, flag = null) {
  if (Buffer.isBuffer(img)) {         // 如果 img 是 Buffer
    img = cv.imdecode(img);           // 直接解码为 Mat 对象
  } else if (typeof img === 'string') { // 如果是图片路径字符串
    img = cv.imread(img);             // 读取图片得到 Mat 对象
  } else if (img instanceof cv.Mat) {   // 如果已经是 Mat 对象了
    img = img;
  } else if (img instanceof Jimp) {      // 如果是 Jimp 图片对象
    img = pil_to_cv2(img);
  } else {
    throw new Error(`无法解析的图片类型: ${typeof img}`);
  }
  if (flag !== null) {                  // 判断是否需要进行颜色空间变换
    img = img.cvtColor(flag);
  }
  return img;
}

/**
 * 检测缺口位置并进行相关操作
 * @param {*} bg 背景图路径或数据流
 * @param {*} tp 缺口模板图路径或数据流
 * @param {*} save_path 结果保存路径
 * @returns 返回检测出的缺口位置
 */
async function get_distance(bg, tp, save_path = null) {
  // 读取图片并转为 PNG 格式
  const bg_jimp = await Jimp.read(bg);
  const tp_jimp = await Jimp.read(tp);
  const bg_buffer = await bg_jimp.getBufferAsync(Jimp.MIME_PNG);  // 使用 PNG 格式,避免颜色被修改
  const tp_buffer = await tp_jimp.getBufferAsync(Jimp.MIME_PNG);
  // 将图片转换成 Mat 对象
  const bg_gray = cv2_open(bg_buffer, cv.COLOR_BGR2GRAY);
  const tp_gray = cv2_open(tp_buffer, cv.COLOR_BGR2GRAY);
  // 边缘检测
  const tp_grayCanny = tp_gray.canny(100, 200);          // 调整宽松的边缘检测阈值
  const bg_grayCanny = bg_gray.canny(100, 200);
  // 目标匹配
  const result = bg_gray.matchTemplate(tp_gray, cv.TM_COEFF_NORMED);
  // 解析匹配结果
  const { minValue, maxValue, minLoc, maxLoc } = result.minMaxLoc();
  const distance = maxLoc.x;
  if (save_path) {
    // 需要绘制的方框高度和宽度
    const [tp_height, tp_width] = tp_gray.sizes;
    // 矩形左上角点位置
    const x = maxLoc.x;
    const y = maxLoc.y;
    // 矩形右下角点位置
    const _x = x + tp_width;
    const _y = y + tp_height;
    // 绘制矩形
    const bg_img = cv2_open(bg_buffer);
    bg_img.drawRectangle(new cv.Rect(x, y, tp_width, tp_height), new cv.Vec3(0, 0, 255), 2);
    // 保存结果到文件
    const savePath = save_path.replace(/\.(\w+)$/, `.${distance}.$1`);
    cv.imwrite(savePath, bg_img);
  }
  return distance;
}

// 测试用例
(async () => {
  const d = await get_distance(
    './bg.jpg',
    './tp.png',
    './result.png'
  );
  console.log(d);
})();

在这个示例中,我们对代码进行了以下修改:

  1. 将图片读取和输出时使用