首先看下效果:
首先对于纯红色的印章提取,也就是通过提取红色 的像素然后得到印章的结果,然后通过在红色的图像中寻找圆圈检测来进行圈定印章的位置
源码位置: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);// 将十六进制颜色值转换为RGBAconst 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数据URLlet 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>