思路:
第一步:直方圖
第二步:獲得直方圖的波峰
第三步:波峰勝負10,高于或低于變紅色
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); // 繪制文本}});},
當然還有一些屬性看官網了解下,只要什么時候用什么就可以