1.引用import cv from '@techstark/opencv-js';
2.vue代码
java
<div class="historyLeft2">
<div style="relative: display" v-for="(item, index) in dataReturns3">
<el-row>
<el-col :span="16">
<canvas v-if="index === 0" ref="myCanvas" width="230" height="230"></canvas>
</el-col>
<el-col :span="8" style="text-align: left;">
<el-button
v-if="tiancongflag==0"
type="success"
style="margin-top:10px; margin-left:15px; font-size:14px;"
size="small"
@click="tiancong"
round
>填充缺陷</el-button>
<el-button
v-if="tiancongflag === 1"
type="warning"
style="margin-top:10px;margin-left:15px;font-size:14px;"
size="small"
@click="tiancong"
round
>取消填充</el-button>
</el-col>
</el-row>
</div>
</div>
3.js代码
java
processBase64Image(base64Data) {
const img = new Image();
img.onload = () => {
if(this.tiancongflag == 1){
this.processImage(img);
}else{
this.processImage2(img);
}
};
console.log(444)
img.src = base64Data;
},
processImage(img) {
console.log(img)
this.$nextTick(() => {
const canvas = this.$refs.myCanvas;
const ctx = canvas[0].getContext('2d');
var canvasWidth = canvas[0].width;
var canvasHeight = canvas[0].height;
var imgWidth = img.width;
var imgHeight = img.height;
var imgYOffset = 20;
// 计算宽高比和缩放比例
var scaledWidth = 0;
var scaledHeight = 0;
if (imgWidth > imgHeight) {
scaledWidth = 190;
scaledHeight = Math.floor(scaledWidth * (imgHeight/imgWidth));
} else {
scaledHeight = 210;
scaledWidth = Math.floor(scaledHeight * (imgWidth/imgHeight));
}
// 清除 Canvas
// ctx.clearRect(0, 0, canvasWidth, canvasHeight);
ctx.fillStyle = '#081c31';
ctx.strokeStyle = '#081c31';
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
var x = Math.floor((210 - scaledWidth) / 2);
var y = Math.floor((210 - scaledHeight) / 2);
ctx.drawImage(img, x, y+imgYOffset, scaledWidth, scaledHeight);
// 读取图像
const src = cv.imread(canvas[0]);
// 创建一个目标矩阵用于灰度图像
var gray = new cv.Mat();
cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY, 0);
//它创建了一个矩形区域(ROI,Region of Interest)并从灰度图像中提取该区域
let rect = new cv.Rect(x,y+imgYOffset,scaledWidth,scaledHeight);
gray = gray.roi(rect);
// 创建 MatVector 并添加灰度图像
const matVector = new cv.MatVector();
matVector.push_back(gray);
// 计算直方图
const histSize = [256]; // 直方图的大小
const ranges = [0, 256]; // 像素值范围
const hist = new cv.Mat(); // 直方图结果
const channels = [0]; // 通道索引
const mask = new cv.Mat(); // 掩码
cv.calcHist(matVector, channels, mask, hist, histSize, ranges);
// 找到直方图的波峰
const histData = hist.data32F; // 获取直方图数据
let maxVal = 0;
let maxIdx = 0;
for (let i = 0; i < histSize[0]; i++) {
if (histData[i] > maxVal) {
maxVal = histData[i];
maxIdx = i;
}
}
// 波峰 ±150 的范围
const lowerBound = maxIdx - 25;
const upperBound = maxIdx + 25;
console.log("222")
console.log(maxIdx);
// 创建一个红色的图像
const redImage = new cv.Mat(src.rows, src.cols, src.type(), [255, 0, 0, 255]);
for (let i = y+imgYOffset; i < y+imgYOffset+scaledHeight; i++) {
for (let j = x; j < x+scaledWidth; j++) {
const pixel = src.ucharPtr(i, j)[1];
if (pixel < lowerBound || pixel > upperBound) {
// 将不在波峰 ±150 范围内的像素变为红色
src.ucharPtr(i, j)[0] = 255; // R
src.ucharPtr(i, j)[1] = 0; // G
src.ucharPtr(i, j)[2] = 0; // B
}
}
}
// 显示结果
cv.imshow(canvas[0], src);
// 释放内存
src.delete();
gray.delete();
matVector.delete();
hist.delete();
mask.delete();
redImage.delete();
// result.delete();
// 绘制刻度线
const scaleLineY = y+20; // 刻度线距离顶部的距离(原为20,增加5个单位)
const scaleLineLength = scaledWidth; // 刻度线的长度
const scaleLineX =x; // 刻度线的起始X坐标
ctx.strokeStyle = '#fbf321'; // 刻度线颜色
ctx.lineWidth = 2; // 刻度线宽度
ctx.beginPath();
ctx.moveTo(scaleLineX, scaleLineY);
ctx.lineTo(scaleLineX + scaleLineLength, scaleLineY);
ctx.stroke();
// 绘制刻度标记(可选)
const numTicks = 10; // 刻度标记的数量
const tickLength = 5; // 刻度标记的长度
const middleTickLength = 10; // 中间刻度标记的长度
const tickSpacing = scaleLineLength / numTicks; // 刻度标记之间的间距
for (let i = 0; i <= numTicks; i++) {
const tickX = scaleLineX + i * tickSpacing;
const isMiddleTick = i === numTicks / 2; // 判断是否为中间刻度
const currentTickLength = isMiddleTick ? middleTickLength : tickLength; // 如果是中间刻度,使用更长的长度
ctx.beginPath();
ctx.moveTo(tickX, scaleLineY);
ctx.lineTo(tickX, scaleLineY - currentTickLength); // 刻度线向上绘制
ctx.stroke();
// 在最左面标注"0"
if (i === 0) {
ctx.fillStyle = '#ffffff'; // 文本颜色
ctx.font = '12px Arial'; // 字体大小和样式
ctx.textAlign = 'left'; // 文本左对齐
ctx.textBaseline = 'bottom'; // 文本底部对齐
ctx.fillText('0', tickX, scaleLineY - currentTickLength - 1); // 在刻度线上方标注"0"
}
}
// 标注图像长度
ctx.fillStyle = '#ffffff'; // 文本颜色
ctx.font = '12px Arial'; // 字体大小和样式
ctx.textAlign = 'right'; // 文本右对齐
ctx.textBaseline = 'middle'; // 文本垂直居中对齐
// 在刻度线最右面标注图像长度
const labelX = scaleLineX + scaleLineLength + 20; // 文本的X坐标(刻度线最右端 + 10个单位)
const labelY = scaleLineY - 11; // 文本的Y坐标(在刻度线上方,原为15,增加5个单位)
if (this.flagf == 1) {
ctx.fillText(`${(imgWidth * 0.098).toFixed(2)}mm`, labelX, labelY);
} else {
ctx.fillText(`${(imgWidth * 0.103).toFixed(2)}mm`, labelX, labelY);
}
// 右侧刻度线的位置和长度
const scaleLineXRight = scaleLineX + scaledWidth; // 右侧刻度线的X坐标(图片右侧偏移20像素)
const scaleLineYRight = y+20; // 右侧刻度线的Y坐标(从顶部开始)
var scaleLineLengthRight = 0;
if (imgHeight - imgWidth > 30) {
scaleLineLengthRight = scaledHeight; // 右侧刻度线的长度(与图片高度相同)
} else {
scaleLineLengthRight = scaledHeight; // 右侧刻度线的长度(与图片高度相同)
}
// 绘制右侧刻度线
ctx.strokeStyle = '#fbf321'; // 刻度线颜色
ctx.lineWidth = 2; // 刻度线宽度
ctx.beginPath();
ctx.moveTo(scaleLineXRight, scaleLineYRight);
ctx.lineTo(scaleLineXRight, scaleLineYRight + scaleLineLengthRight);
ctx.stroke();
// 绘制右侧刻度标记(可选)
const numTicksRight = 10; // 刻度标记的数量
const tickLengthRight = 5; // 刻度标记的长度
const middleTickLengthRight = 10; // 中间刻度标记的长度
const tickSpacingRight = scaleLineLengthRight / numTicksRight; // 刻度标记之间的间距
for (let i = 0; i <= numTicksRight; i++) {
const tickY = scaleLineYRight + i * tickSpacingRight;
const isMiddleTick = i === numTicksRight / 2; // 判断是否为中间刻度
const currentTickLength = isMiddleTick ? middleTickLengthRight : tickLengthRight; // 如果是中间刻度,使用更长的长度
ctx.beginPath();
ctx.moveTo(scaleLineXRight, tickY);
ctx.lineTo(scaleLineXRight + currentTickLength, tickY); // 刻度线向右绘制
ctx.stroke();
// 在最上面的刻度位置显示 "0"
if (i === 0) {
ctx.fillStyle = '#ffffff'; // 文本颜色
ctx.font = '12px Arial'; // 字体大小和样式
ctx.textAlign = 'left'; // 文本左对齐
ctx.textBaseline = 'middle'; // 文本垂直居中对齐
ctx.fillText('0', scaleLineXRight + currentTickLength + 5, tickY); // 在刻度线右侧绘制 "0"
}
}
// 标注图像高度
ctx.fillStyle = '#ffffff'; // 文本颜色
ctx.font = '12px Arial'; // 字体大小和样式
ctx.textAlign = 'center'; // 文本居中对齐
ctx.textBaseline = 'middle'; // 文本垂直居中对齐
// 在右侧刻度线最下面标注图像高度
const labelXRight = scaleLineXRight + 30; // 文本的X坐标(刻度线右侧偏移15像素)
const labelYRight = scaleLineYRight + scaleLineLengthRight - 10; // 文本的Y坐标(在刻度线最下面)
if (this.flagf == 1) {
ctx.fillText(`${(imgHeight * 0.098).toFixed(2)}mm`, labelXRight, labelYRight); // 绘制文本
} else {
ctx.fillText(`${(imgHeight * 0.103).toFixed(2)}mm`, labelXRight, labelYRight); // 绘制文本
}
});
},
processImage2(img) {
this.$nextTick(() => {
console.log("ppsasasa")
const canvas = this.$refs.myCanvas;
const ctx = canvas[0].getContext('2d');
var canvasWidth = canvas[0].width;
var canvasHeight = canvas[0].height;
var imgWidth = img.width;
var imgHeight = img.height;
var imgYOffset = 20;
// 计算宽高比和缩放比例
var scaledWidth = 0;
var scaledHeight = 0;
if (imgWidth > imgHeight) {
scaledWidth = 190;
scaledHeight = Math.floor(scaledWidth * (imgHeight/imgWidth));
} else {
scaledHeight = 210;
scaledWidth = Math.floor(scaledHeight * (imgWidth/imgHeight));
}
// 清除 Canvas
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
ctx.fillStyle = '#081c31';
ctx.strokeStyle = '#081c31';
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
var x = Math.floor((210 - scaledWidth) / 2);
var y = Math.floor((210 - scaledHeight) / 2);
ctx.drawImage(img, x, y+imgYOffset, scaledWidth, scaledHeight);
// 读取图像
const src = cv.imread(canvas[0]);
// // 创建一个目标矩阵用于灰度图像
// const gray = new cv.Mat();
// cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY, 0);
// // 创建一个二值化图像,将白色区域分离出来
// const binary = new cv.Mat();
// cv.threshold(gray, binary, 170, 255, cv.THRESH_BINARY);
// // // 将二值化图像作为掩码,将红色图像应用到白色区域
// const result = new cv.Mat();
// // 将原始图像中非白色区域保留
// cv.bitwise_not(binary, binary);
// cv.bitwise_and(src, src, result, binary);
// // 显示结果
// cv.imshow(canvas[0], result);
// // 释放内存
// src.delete();
// gray.delete();
// binary.delete();
// result.delete();
// 绘制刻度线
const scaleLineY = y+20; // 刻度线距离顶部的距离(原为20,增加5个单位)
const scaleLineLength = scaledWidth; // 刻度线的长度
const scaleLineX =x; // 刻度线的起始X坐标
ctx.strokeStyle = '#fbf321'; // 刻度线颜色
ctx.lineWidth = 2; // 刻度线宽度
ctx.beginPath();
ctx.moveTo(scaleLineX, scaleLineY);
ctx.lineTo(scaleLineX + scaleLineLength, scaleLineY);
ctx.stroke();
// 绘制刻度标记(可选)
const numTicks = 10; // 刻度标记的数量
const tickLength = 5; // 刻度标记的长度
const middleTickLength = 10; // 中间刻度标记的长度
const tickSpacing = scaleLineLength / numTicks; // 刻度标记之间的间距
for (let i = 0; i <= numTicks; i++) {
const tickX = scaleLineX + i * tickSpacing;
const isMiddleTick = i === numTicks / 2; // 判断是否为中间刻度
const currentTickLength = isMiddleTick ? middleTickLength : tickLength; // 如果是中间刻度,使用更长的长度
ctx.beginPath();
ctx.moveTo(tickX, scaleLineY);
ctx.lineTo(tickX, scaleLineY - currentTickLength); // 刻度线向上绘制
ctx.stroke();
// 在最左面标注"0"
if (i === 0) {
ctx.fillStyle = '#ffffff'; // 文本颜色
ctx.font = '12px Arial'; // 字体大小和样式
ctx.textAlign = 'left'; // 文本左对齐
ctx.textBaseline = 'bottom'; // 文本底部对齐
ctx.fillText('0', tickX, scaleLineY - currentTickLength - 1); // 在刻度线上方标注"0"
}
}
// 标注图像长度
ctx.fillStyle = '#ffffff'; // 文本颜色
ctx.font = '12px Arial'; // 字体大小和样式
ctx.textAlign = 'right'; // 文本右对齐
ctx.textBaseline = 'middle'; // 文本垂直居中对齐
// 在刻度线最右面标注图像长度
const labelX = scaleLineX + scaleLineLength + 20; // 文本的X坐标(刻度线最右端 + 10个单位)
const labelY = scaleLineY - 11; // 文本的Y坐标(在刻度线上方,原为15,增加5个单位)
if (this.flagf == 1) {
ctx.fillText(`${(imgWidth * 0.098).toFixed(2)}mm`, labelX, labelY);
} else {
ctx.fillText(`${(imgWidth * 0.103).toFixed(2)}mm`, labelX, labelY);
}
// 右侧刻度线的位置和长度
const scaleLineXRight = scaleLineX + scaledWidth; // 右侧刻度线的X坐标(图片右侧偏移20像素)
const scaleLineYRight = y+20; // 右侧刻度线的Y坐标(从顶部开始)
var scaleLineLengthRight = 0;
if (imgHeight - imgWidth > 30) {
scaleLineLengthRight = scaledHeight; // 右侧刻度线的长度(与图片高度相同)
} else {
scaleLineLengthRight = scaledHeight; // 右侧刻度线的长度(与图片高度相同)
}
// 绘制右侧刻度线
ctx.strokeStyle = '#fbf321'; // 刻度线颜色
ctx.lineWidth = 2; // 刻度线宽度
ctx.beginPath();
ctx.moveTo(scaleLineXRight, scaleLineYRight);
ctx.lineTo(scaleLineXRight, scaleLineYRight + scaleLineLengthRight);
ctx.stroke();
// 绘制右侧刻度标记(可选)
const numTicksRight = 10; // 刻度标记的数量
const tickLengthRight = 5; // 刻度标记的长度
const middleTickLengthRight = 10; // 中间刻度标记的长度
const tickSpacingRight = scaleLineLengthRight / numTicksRight; // 刻度标记之间的间距
for (let i = 0; i <= numTicksRight; i++) {
const tickY = scaleLineYRight + i * tickSpacingRight;
const isMiddleTick = i === numTicksRight / 2; // 判断是否为中间刻度
const currentTickLength = isMiddleTick ? middleTickLengthRight : tickLengthRight; // 如果是中间刻度,使用更长的长度
ctx.beginPath();
ctx.moveTo(scaleLineXRight, tickY);
ctx.lineTo(scaleLineXRight + currentTickLength, tickY); // 刻度线向右绘制
ctx.stroke();
// 在最上面的刻度位置显示 "0"
if (i === 0) {
ctx.fillStyle = '#ffffff'; // 文本颜色
ctx.font = '12px Arial'; // 字体大小和样式
ctx.textAlign = 'left'; // 文本左对齐
ctx.textBaseline = 'middle'; // 文本垂直居中对齐
ctx.fillText('0', scaleLineXRight + currentTickLength + 5, tickY); // 在刻度线右侧绘制 "0"
}
}
// 标注图像高度
ctx.fillStyle = '#ffffff'; // 文本颜色
ctx.font = '12px Arial'; // 字体大小和样式
ctx.textAlign = 'center'; // 文本居中对齐
ctx.textBaseline = 'middle'; // 文本垂直居中对齐
// 在右侧刻度线最下面标注图像高度
const labelXRight = scaleLineXRight + 30; // 文本的X坐标(刻度线右侧偏移15像素)
const labelYRight = scaleLineYRight + scaleLineLengthRight - 10; // 文本的Y坐标(在刻度线最下面)
if (this.flagf == 1) {
ctx.fillText(`${(imgHeight * 0.098).toFixed(2)}mm`, labelXRight, labelYRight); // 绘制文本
} else {
ctx.fillText(`${(imgHeight * 0.103).toFixed(2)}mm`, labelXRight, labelYRight); // 绘制文本
}
});
},