js应用opencv
1.引用import cv from ‘@techstark/opencv-js’;
2.vue代码
<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-buttonv-if="tiancongflag==0"type="success" style="margin-top:10px; margin-left:15px; font-size:14px;"size="small"@click="tiancong"round>填充缺陷</el-button><el-buttonv-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代码
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; // Rsrc.ucharPtr(i, j)[1] = 0; // Gsrc.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));}// 清除 Canvasctx.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); // 绘制文本}});},