feat: add extract red circle stamp

This commit is contained in:
xxss0903 2024-10-25 17:30:11 +08:00
parent 135f207b5c
commit e575ecdd74
2 changed files with 71 additions and 81 deletions

View File

@ -36,7 +36,6 @@ function initOpenCV(callback) {
*/ */
function extractStampWithColorImpl( function extractStampWithColorImpl(
img, img,
extractColor = "#ff0000",
setColor = "#ff0000" setColor = "#ff0000"
) { ) {
if (cvReady) { if (cvReady) {
@ -52,65 +51,23 @@ function extractStampWithColorImpl(
cv.cvtColor(src, dst, cv.COLOR_RGBA2RGB); cv.cvtColor(src, dst, cv.COLOR_RGBA2RGB);
cv.cvtColor(dst, dst, cv.COLOR_RGB2HSV); cv.cvtColor(dst, dst, cv.COLOR_RGB2HSV);
// 将提取颜色转换为HSV // 定义红色的HSV范围
const extractColorRGB = hexToRgba(extractColor); // 低值范围 (0-10)
const extractColorHSV = cv.matFromArray(1, 1, cv.CV_8UC3, [ let lowRedA = new cv.Mat(dst.rows, dst.cols, dst.type(), [0, 50, 50, 0]);
extractColorRGB[0], let highRedA = new cv.Mat(dst.rows, dst.cols, dst.type(), [10, 255, 255, 255]);
extractColorRGB[1],
extractColorRGB[2],
]);
cv.cvtColor(extractColorHSV, extractColorHSV, cv.COLOR_RGB2HSV);
const hsvValues = extractColorHSV.data32F;
// 定义提取颜色的HSV范围 // 高值范围 (170-180)
let lowColor, highColor; let lowRedB = new cv.Mat(dst.rows, dst.cols, dst.type(), [170, 50, 50, 0]);
if (extractColor.toLowerCase() === "#ff0000") { let highRedB = new cv.Mat(dst.rows, dst.cols, dst.type(), [180, 255, 255, 255]);
// 红色的HSV范围
lowColor = new cv.Mat(dst.rows, dst.cols, dst.type(), [0, 100, 100, 0]);
highColor = new cv.Mat(
dst.rows,
dst.cols,
dst.type(),
[10, 255, 255, 255]
);
} else if (extractColor.toLowerCase() === "#00ff00") {
// 绿色的HSV范围
lowColor = new cv.Mat(dst.rows, dst.cols, dst.type(), [60, 100, 100, 0]);
highColor = new cv.Mat(
dst.rows,
dst.cols,
dst.type(),
[80, 255, 255, 255]
);
} else if (extractColor.toLowerCase() === "#000000") {
// 黑色的HSV范围
lowColor = new cv.Mat(dst.rows, dst.cols, dst.type(), [0, 0, 0, 0]);
highColor = new cv.Mat(
dst.rows,
dst.cols,
dst.type(),
[180, 255, 30, 255]
);
} else {
// 其他颜色(包括随机颜色如#00ffff使用动态计算的范围
const hue = hsvValues[0];
const hueRange = 10; // 色相范围,可以根据需要调整
lowColor = new cv.Mat(dst.rows, dst.cols, dst.type(), [
Math.max(0, hue - hueRange),
100,
100,
0,
]);
highColor = new cv.Mat(dst.rows, dst.cols, dst.type(), [
Math.min(180, hue + hueRange),
255,
255,
255,
]);
}
// 创建掩码 // 创建掩码
cv.inRange(dst, lowColor, highColor, mask); 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 // 将十六进制颜色值转换为RGBA
const dstColor = hexToRgba(setColor); const dstColor = hexToRgba(setColor);
@ -139,9 +96,12 @@ function extractStampWithColorImpl(
src.delete(); src.delete();
dst.delete(); dst.delete();
mask.delete(); mask.delete();
lowColor.delete(); maskA.delete();
highColor.delete(); maskB.delete();
extractColorHSV.delete(); lowRedA.delete();
highRedA.delete();
lowRedB.delete();
highRedB.delete();
colorMat.delete(); colorMat.delete();
result.delete(); result.delete();
return dataURL; return dataURL;
@ -152,19 +112,18 @@ function extractStampWithColorImpl(
} }
/** /**
* 提取指定颜色的印章 * 提取色的印章
* @param file 图片文件 * @param file 图片文件
* @param extractColor 提取的颜色
* @param setColor 设置的颜色比如提取红色设置红色那么能够进行对印章的填充 * @param setColor 设置的颜色比如提取红色设置红色那么能够进行对印章的填充
* @param isCircle 是否是圆形如果是圆形那么会进行圆形的裁剪否则进行椭圆的裁剪 * @param isCircle 是否是圆形如果是圆形那么会进行圆形的裁剪否则进行椭圆的裁剪
* @returns * @returns
*/ */
function extractStampWithFile(file, extractColor, setColor, isCircle = true) { function extractStampWithFile(file, setColor, isCircle = true) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const img = new Image(); const img = new Image();
let distImgList = []; let distImgList = [];
img.onload = async () => { img.onload = async () => {
let dstImg = extractStampWithColorImpl(img, extractColor, setColor); let dstImg = extractStampWithColorImpl(img, setColor);
let debugCircle = true; let debugCircle = true;
if (debugCircle) { if (debugCircle) {
// 将base64的图像数据转换为Image对象 // 将base64的图像数据转换为Image对象
@ -180,8 +139,10 @@ function extractStampWithFile(file, extractColor, setColor, isCircle = true) {
const resultRedImg = await base64ToImage(dstImg); const resultRedImg = await base64ToImage(dstImg);
// 提取圆圈并获取结果 // 提取圆圈并获取结果
distImgList = extractCircles(resultRedImg, isCircle); distImgList = extractCircles(resultRedImg, isCircle);
}
resolve(distImgList); resolve(distImgList);
} else {
resolve([dstImg])
}
}; };
img.onerror = (error) => { img.onerror = (error) => {
console.error("图片加载失败", error); console.error("图片加载失败", error);
@ -200,7 +161,7 @@ function detectCircles(dst) {
// 创建一个新的Mat对象来存储检测到的圆形 // 创建一个新的Mat对象来存储检测到的圆形
let circles = new cv.Mat(); let circles = new cv.Mat();
// 计算最小和最大半径,用于限制检测到的圆形大小 // 计算最小和最大半径,用于限制检测到的圆形大小
let minRadius = Math.min(dst.rows, dst.cols) * 0.05; // 最小半径为图像最小边的5% let minRadius = Math.min(dst.rows, dst.cols) * 0.03; // 最小半径为图像最小边的5%
let maxRadius = Math.min(dst.rows, dst.cols) * 0.5; // 最大半径为图像最小边的50% let maxRadius = Math.min(dst.rows, dst.cols) * 0.5; // 最大半径为图像最小边的50%
// 使用Hough变换检测圆形 // 使用Hough变换检测圆形
cv.HoughCircles( cv.HoughCircles(
@ -208,8 +169,8 @@ function detectCircles(dst) {
circles, circles,
cv.HOUGH_GRADIENT, cv.HOUGH_GRADIENT,
1, // 两个圆心之间的最小距离 1, // 两个圆心之间的最小距离
dst.rows / 4, // 检测圆心之间的最小距离 dst.rows / 8, // 检测圆心之间的最小距离
100, // 检测圆形的阈值 200, // 修改检测圆形的阈值为200
50, // 检测圆形的阈值 50, // 检测圆形的阈值
minRadius, // 检测圆形的最小半径 minRadius, // 检测圆形的最小半径
maxRadius // 检测圆形的最大半径 maxRadius // 检测圆形的最大半径
@ -226,13 +187,14 @@ function detectCircles(dst) {
radius: circles.data32F[i * 3 + 2] // 半径 radius: circles.data32F[i * 3 + 2] // 半径
}); });
} }
console.log("detectedCircles:", detectedCircles, maxRadius, minRadius, dst.rows, dst.cols);
// 根据半径大小对检测到的圆形进行排序,确保最大的圆形排在前面 // 根据半径大小对检测到的圆形进行排序,确保最大的圆形排在前面
detectedCircles.sort((a, b) => b.radius - a.radius); detectedCircles.sort((a, b) => b.radius - a.radius);
// 释放内存 // 释放内存
circles.delete(); circles.delete();
// 返回最大的3个圆形 // 返回最大的3个圆形
return detectedCircles.slice(0, 3); return detectedCircles.slice(0, 6);
} }
@ -393,13 +355,13 @@ function extractRedStampWithColor(img, color = primaryColor) {
cv.cvtColor(src, dst, cv.COLOR_RGBA2RGB); cv.cvtColor(src, dst, cv.COLOR_RGBA2RGB);
cv.cvtColor(dst, dst, cv.COLOR_RGB2HSV); cv.cvtColor(dst, dst, cv.COLOR_RGB2HSV);
// 定义红色的HSV范围 // 定义红色和暗红色的HSV范围
let lowRedA = new cv.Mat(dst.rows, dst.cols, dst.type(), [0, 100, 100, 0]); let lowRedA = new cv.Mat(dst.rows, dst.cols, dst.type(), [0, 100, 100, 0]);
let highRedA = new cv.Mat( let highRedA = new cv.Mat(
dst.rows, dst.rows,
dst.cols, dst.cols,
dst.type(), dst.type(),
[10, 255, 255, 255] [50, 255, 255, 255]
); );
let lowRedB = new cv.Mat( let lowRedB = new cv.Mat(
dst.rows, dst.rows,
@ -419,6 +381,8 @@ function extractRedStampWithColor(img, color = primaryColor) {
let maskB = new cv.Mat(); let maskB = new cv.Mat();
cv.inRange(dst, lowRedA, highRedA, maskA); cv.inRange(dst, lowRedA, highRedA, maskA);
cv.inRange(dst, lowRedB, highRedB, maskB); cv.inRange(dst, lowRedB, highRedB, maskB);
cv.add(maskA, maskB, mask); cv.add(maskA, maskB, mask);
// 将十六进制颜色值转换为RGB // 将十六进制颜色值转换为RGB
@ -480,3 +444,4 @@ window.initOpenCV = initOpenCV;
window.extractRedStampWithFile = extractRedStampWithFile; window.extractRedStampWithFile = extractRedStampWithFile;
window.extractRedStamp = extractRedStamp; window.extractRedStamp = extractRedStamp;
window.extractStampWithFile = extractStampWithFile; window.extractStampWithFile = extractStampWithFile;

View File

@ -7,17 +7,21 @@
<script src="https://docs.opencv.org/4.x/opencv.js" type="text/javascript"></script> <script src="https://docs.opencv.org/4.x/opencv.js" type="text/javascript"></script>
<script src="extractStamp.js"></script> <script src="extractStamp.js"></script>
<style> <style>
#imageContainer { #imageContainer, #originalImageContainer {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 10px; gap: 15px;
margin-top: 20px; margin-top: 20px;
} }
#imageContainer img { #imageContainer img, #originalImageContainer img {
max-width: 200px; max-width: 300px;
max-height: 200px; max-height: 300px;
object-fit: contain; object-fit: contain;
border: 1px solid #ddd; border: 1px solid #ddd;
transition: transform 0.3s ease;
}
#imageContainer img:hover, #originalImageContainer img:hover {
transform: scale(1.1);
} }
#loading { #loading {
display: none; display: none;
@ -36,6 +40,9 @@
<body> <body>
<h1>选择图片</h1> <h1>选择图片</h1>
<input type="file" id="imageInput" accept="image/*" onchange="selectImage()"> <input type="file" id="imageInput" accept="image/*" onchange="selectImage()">
<h2>原图:</h2>
<div id="originalImageContainer"></div>
<h2>提取的印章:</h2>
<div id="imageContainer"></div> <div id="imageContainer"></div>
<div id="loading">处理中,请稍候...</div> <div id="loading">处理中,请稍候...</div>
<script> <script>
@ -50,7 +57,8 @@
console.log('file', file); console.log('file', file);
if (file && isReady) { if (file && isReady) {
showLoading(); showLoading();
extractStampWithFile(file, '#ff0000', '#ff0000', true).then(dstImgList => { displayOriginalImage(file);
extractStampWithFile(file, '#ff0000', true).then(dstImgList => {
console.log('提取红色印章成功', dstImgList); console.log('提取红色印章成功', dstImgList);
displayImages(dstImgList); displayImages(dstImgList);
hideLoading(); hideLoading();
@ -65,6 +73,21 @@
} }
} }
function displayOriginalImage(file) {
const reader = new FileReader();
reader.onload = function(e) {
const container = document.getElementById('originalImageContainer');
container.innerHTML = ''; // 清空之前的内容
const img = document.createElement('img');
img.src = e.target.result;
img.alt = '原图';
img.style.width = '300px';
img.style.height = '300px';
container.appendChild(img);
}
reader.readAsDataURL(file);
}
function displayImages(imageList) { function displayImages(imageList) {
const container = document.getElementById('imageContainer'); const container = document.getElementById('imageContainer');
container.innerHTML = ''; // 清空之前的内容 container.innerHTML = ''; // 清空之前的内容
@ -72,6 +95,8 @@
const img = document.createElement('img'); const img = document.createElement('img');
img.src = imgSrc; img.src = imgSrc;
img.alt = `提取的印章 ${index + 1}`; img.alt = `提取的印章 ${index + 1}`;
img.style.width = '100px';
img.style.height = '100px';
container.appendChild(img); container.appendChild(img);
}); });
} }