使用opencvjs提取圖片中的紅色印章

乌拉小考發表於2024-10-26

首先看下效果:

首先對於純紅色的印章提取,也就是透過提取紅色 的畫素然後得到印章的結果,然後透過在紅色的影像中尋找圓圈檢測來進行圈定印章的位置
原始碼位置:https://github.com/xxss0903/extractstamp

  • 第一步是提取紅色內容
function extractStampWithColorToOpencvMat(
    img,
    setColor = "#ff0000"
) {
  if (cvReady) {
    // 獲取圖片的寬高
    const imgWidth = img.width;
    const imgHeight = img.height;
    console.log("圖片寬度:", imgWidth, "圖片高度:", imgHeight);
    let src = cv.imread(img);
    let dst = new cv.Mat();
    let mask = new cv.Mat();

    // 轉換為HSV顏色空間
    cv.cvtColor(src, dst, cv.COLOR_RGBA2RGB);
    cv.cvtColor(dst, dst, cv.COLOR_RGB2HSV);

    // 定義紅色的HSV範圍
    // 低值範圍 (0-10)
    let lowRedA = new cv.Mat(dst.rows, dst.cols, dst.type(), [0, 50, 50, 0]);
    let highRedA = new cv.Mat(dst.rows, dst.cols, dst.type(), [10, 255, 255, 255]);

    // 高值範圍 (170-180)
    let lowRedB = new cv.Mat(dst.rows, dst.cols, dst.type(), [170, 50, 50, 0]);
    let highRedB = new cv.Mat(dst.rows, dst.cols, dst.type(), [180, 255, 255, 255]);

    // 建立掩碼
    let maskA = new cv.Mat();
    let maskB = new cv.Mat();
    cv.inRange(dst, lowRedA, highRedA, maskA);
    cv.inRange(dst, lowRedB, highRedB, maskB);

    // 合併掩碼
    cv.add(maskA, maskB, mask);

    // 將十六進位制顏色值轉換為RGBA
    const dstColor = hexToRgba(setColor);
    console.log("dstColor:", dstColor);

    // 建立帶有 alpha 通道的目標影像
    let result = new cv.Mat(src.rows, src.cols, cv.CV_8UC4, [0, 0, 0, 0]);

    // 建立指定顏色的影像(帶有 alpha 通道)
    let colorMat = new cv.Mat(src.rows, src.cols, cv.CV_8UC4, [
      ...dstColor.slice(0, 3),
      255,
    ]);

    // 使用掩碼將提取的區域設定為指定顏色,非提取區域保持透明
    colorMat.copyTo(result, mask);

    // 釋放記憶體
    src.delete();
    dst.delete();
    mask.delete();
    maskA.delete();
    maskB.delete();
    lowRedA.delete();
    highRedA.delete();
    lowRedB.delete();
    highRedB.delete();
    colorMat.delete();
    return result;
  } else {
    console.error("OpenCV.js 未載入");
    return img;
  }
}
  • 第二步,提取圓形印章,然後裁剪下來
function extractCirclesWithCvMat(cvMat) {
  let dst = new cv.Mat();
  // 轉換為灰度圖
  cv.cvtColor(cvMat, dst, cv.COLOR_RGBA2GRAY);
  // 應用高斯模糊以減少噪聲
  cv.GaussianBlur(dst, dst, new cv.Size(5, 5), 2, 2);
  
  let croppedStamps = [];
  // 檢測圓形
  let circles = detectCircles(dst);
  console.log("circles:", circles);
  circles.forEach((circle) => {
    console.log("draw circle:", circle);
    croppedStamps.push(cropAndDownloadCircle(cvMat, circle));
  });
  // 釋放記憶體
  dst.delete();
  
  return croppedStamps;
}
  • 第三步,裁剪圓形印章
function cropAndDownloadCircle(cvMat, circle) {
  // 定義縮放因子,使裁剪範圍比圓形大一些
  const scaleFactor = 1.2;
  // 計算新的半徑和尺寸
  let newRadius = circle.radius * scaleFactor;
  let size = Math.round(newRadius * 2);

  // 建立一個新的Mat來儲存裁剪後的影像
  let croppedMat = new cv.Mat();
  let rect = new cv.Rect(
    Math.round(circle.x - newRadius),
    Math.round(circle.y - newRadius),
    size,
    size
  );

  // 確保裁剪區域在影像範圍內
  rect.x = Math.max(0, Math.min(rect.x, cvMat.cols - rect.width));
  rect.y = Math.max(0, Math.min(rect.y, cvMat.rows - rect.height));
  rect.width = Math.min(rect.width, cvMat.cols - rect.x);
  rect.height = Math.min(rect.height, cvMat.rows - rect.y);

  // 裁剪影像
  croppedMat = cvMat.roi(rect);

  // 建立圓形掩碼
  let mask = new cv.Mat.zeros(size, size, cv.CV_8UC1);
  let center = new cv.Point(size / 2, size / 2);
  cv.circle(mask, center, Math.round(newRadius), new cv.Scalar(255, 255, 255), -1);

  // 應用掩碼
  let result = new cv.Mat();
  cv.bitwise_and(croppedMat, croppedMat, result, mask);

  // 將結果轉換為PNG資料URL
  let imgData = new ImageData(new Uint8ClampedArray(result.data), result.cols, result.rows);
  let canvas = document.createElement('canvas');
  canvas.width = result.cols;
  canvas.height = result.rows;
  let ctx = canvas.getContext('2d');
  ctx.putImageData(imgData, 0, 0);
  let dataURL = canvas.toDataURL("image/png");

  // 釋放記憶體
  croppedMat.delete();
  mask.delete();
  result.delete();

  return dataURL;
}

注意需要引入opencv.js,可以在index.html中引入
<script src="https://docs.opencv.org/4.x/opencv.js" type="text/javascript"></script>

相關文章