#define online using DocumentFormat.OpenXml.Spreadsheet; using OpenCvSharp; using OpenCvSharp.XImgProc; using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; namespace GeBoShi.SysCtrl { public static class OpencvUtils { public static int image_width = 2048; public static int image_height = 2048; #region 图像预处理 public static Mat Resize(Mat mat, int width, int height, out int xw) { OpenCvSharp.Size dsize = new OpenCvSharp.Size(width, height); //Mat mat2 = new Mat(); //ResizeUniform(mat, dsize, out mat2, out xw); xw = (width - mat.Cols) / 2; Mat mat2 = new Mat(height, width, MatType.CV_8UC3, new Scalar(114, 114, 114)); Rect roi = new Rect((width - mat.Cols) / 2, (height - mat.Rows) / 2, mat.Cols, mat.Rows); mat.CopyTo(new Mat(mat2, roi)); return mat2; } /// /// 等比例缩放 /// /// /// /// /// public static int ResizeUniform(Mat src, Size dst_size, out Mat dst, out int xw) { xw = 0; int w = src.Cols; int h = src.Rows; int dst_w = dst_size.Width; int dst_h = dst_size.Height; //std::cout << "src: (" << h << ", " << w << ")" << std::endl; dst = new Mat(dst_h, dst_w, MatType.CV_8UC3, new Scalar(114, 114, 114)); float[] ratio = new float[2]; float ratio_src = w * 1.0f / h; float ratio_dst = dst_w * 1.0f / dst_h; int tmp_w = 0; int tmp_h = 0; if (ratio_src > ratio_dst) { tmp_w = dst_w; tmp_h = (int)(dst_w * 1.0f / w) * h; ratio[0] = (float)w / (float)tmp_w; ratio[1] = (float)h / (float)tmp_h; } else if (ratio_src < ratio_dst) { tmp_h = dst_h; tmp_w = (int)((dst_h * 1.0f / h) * w); ratio[0] = (float)w / (float)tmp_w; ratio[1] = (float)h / (float)tmp_h; } else { Cv2.Resize(src, dst, dst_size); ratio[0] = (float)w / (float)tmp_w; ratio[1] = (float)h / (float)tmp_h; return 0; } //std::cout << "tmp: (" << tmp_h << ", " << tmp_w << ")" << std::endl; Mat tmp = new Mat(); Cv2.Resize(src, tmp, new Size(tmp_w, tmp_h)); unsafe { if (tmp_w != dst_w) { //高对齐,宽没对齐 int index_w = (int)((dst_w - tmp_w) / 2.0); xw = index_w; //std::cout << "index_w: " << index_w << std::endl; for (int i = 0; i < dst_h; i++) { Buffer.MemoryCopy(IntPtr.Add(tmp.Data, i * tmp_w * 3).ToPointer(), IntPtr.Add(dst.Data, i * dst_w * 3 + index_w * 3).ToPointer(), tmp_w * 3, tmp_w * 3); } } else if (tmp_h != dst_h) { //宽对齐, 高没有对齐 int index_h = (int)((dst_h - tmp_h) / 2.0); //std::cout << "index_h: " << index_h << std::endl; Buffer.MemoryCopy(tmp.Data.ToPointer(), IntPtr.Add(dst.Data, index_h * dst_w * 3).ToPointer(), tmp_w * tmp_h * 3, tmp_w * tmp_h * 3); } else { } } return 0; } public static Mat ResizeMat(Mat mat, int width, int height) { OpenCvSharp.Size dsize = new OpenCvSharp.Size(width, height); Mat mat2 = new Mat(); mat2 = mat.Resize(dsize); //Cv2.Resize(mat, mat2, dsize); return mat2; } /// /// 计算合理宽幅 /// /// 多个相机图像总宽(外部去除重合部分) /// public static int GetWidthForResize(int sumWidth) { //保证计算8x2 16个小图 int count = (int)Math.Round(sumWidth * 1.0f / image_width, 0); count = 8; return count * image_width; //int count = sumWidth / image_width; ////int remainder = sumWidth % image_width; //if (count % 2 == 0) // return count * image_width; //else // return count * image_width+ image_width; } /// /// 裁切指定区域 /// /// /// /// /// /// /// public static Mat CutImage(Mat mat, int x, int y, int width, int height) { Rect roi = new Rect(x, y, width, height); return new Mat(mat, roi).Clone(); } #endregion #region 裁边 /// /// 裁边 /// /// /// /// /// /// public static Mat getMaxInsetRect2(Mat mat_rgb, bool isLeft, int marginHoleWidth, out int marginWidth) { int bian = 3500; Rect Roi; if (!isLeft) Roi = new Rect(mat_rgb.Width - bian, 0, bian, mat_rgb.Height); else Roi = new Rect(0, 0, bian, mat_rgb.Height); int type = isLeft ? 1 : 0; int len = 0; if(!ConfMgr.Instance.SysConfigParams.OpenAIEdge) len = EdgeClipping2(mat_rgb, type, Roi, isLeft); else len = EdgeClipping3(mat_rgb, type, Roi, isLeft); #if false //Mat mat_rgb = new Mat("E:\\CPL\\测试代码\\边缘检测\\test\\test\\test\\img\\19.bmp"); Mat image_gray = new Mat(); Cv2.CvtColor(mat_rgb, image_gray, ColorConversionCodes.BGR2GRAY); //cvtColor(image_RGB, image, COLOR_RGB2GRAY); int height = image_gray.Rows; int width = image_gray.Cols; // 算法定义:取均分5段图片的五条横线,经过一系列处理之后,二值化,找到沿边位置,然后取均值作为直边,在缩进一段有针眼的位置 // 定义每段的行数 int num_rows = 5; int segment_height = height / num_rows - 1; // 定义空数组保存结果 int[] total = new int[num_rows]; // 平均截取5行数据并处理图像 for (int i = 0; i < num_rows; i++) { // 截取当前行的图像 int start_row = i * segment_height; Rect roi = new Rect(0, start_row, width, 1); Mat current_segment = image_gray.Clone(roi); // 对当前行的图像进行平滑处理 Mat smoothed_image = new Mat(); Cv2.GaussianBlur(current_segment, smoothed_image, new Size(5, 1), 0); // 计算当前行的灰度直方图 Mat absolute_histo = new Mat(); Cv2.CalcHist(new Mat[] { smoothed_image }, new int[] { 0 }, new Mat(), absolute_histo, 1, new int[] { 256 }, new Rangef[] { new Rangef(0, 256) }); Cv2.GaussianBlur(current_segment, smoothed_image, new Size(19, 1), 0); // 对图片进行分割i+1 //double otsu_threshold; //threshold(smoothed_image, smoothed_image, 0, 255, THRESH_BINARY + THRESH_OTSU, &otsu_threshold); Cv2.Threshold(smoothed_image, smoothed_image, 0, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu); // 使用形态学操作进行孔洞填充 Mat kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(25, 1)); Mat filled_image = new Mat(); Cv2.MorphologyEx(smoothed_image, filled_image, MorphTypes.Close, kernel); // 取较长的一个值作为皮革的宽度 int num_255 = Cv2.CountNonZero(filled_image); int length_t = (num_255 > width / 2) ? num_255 : width - num_255; total[i] = (length_t); API.OutputDebugString($"getMaxInsetRect2: 【{i + 1}】{length_t}={num_255}|{width}"); } // 取平均值作为宽度 int length = (int)total.Average(); marginWidth = width-length; #endif if (len > 0) { int length = len; //(len > mat_rgb.Width / 2) ? len : mat_rgb.Width - len; if (isLeft) marginWidth = length; else marginWidth = mat_rgb.Width - length; // 判断数据是否异常,判断当前线段的宽度是否大于设定像素的偏差 //int abnormal_pxl = 200; //for (int i = 0; i < num_rows; i++) //{ // if (Math.Abs(total[i] - length) > abnormal_pxl) // throw new Exception("数据异常,当段图片的宽度有问题!"); //} //右侧相机,拍摄产品,边缘位于右侧判断,缩进100像素,去点针眼 //Cv2.Line(mat_rgb, new Point(length - 100, 0), new Point(length - 100, height), new Scalar(255, 0, 0), 20); ////左侧相机,拍摄产品,边缘位于左侧判断,缩进100像素,去点针眼 //Cv2.Line(mat_rgb, new Point(width - length + 100, 0), new Point(width - length + 100, height), new Scalar(0, 255, 0), 20); //int decWidth = width - length + marginHoleWidth; //if (isLeft) // return cutImage(mat_rgb, decWidth, 0, width- decWidth, height); //else // return cutImage(mat_rgb, 0, 0, width - decWidth, height); //API.OutputDebugString($"getMaxInsetRect2:margin={marginWidth},length={length}({marginHoleWidth}),isLeft={isLeft},mat_rgb={mat_rgb.Width}*{mat_rgb.Height},w={length - marginHoleWidth},h={mat_rgb.Height}"); #if online if (isLeft) return CutImage(mat_rgb, length + marginHoleWidth, 0, mat_rgb.Width - length - marginHoleWidth, mat_rgb.Height); else return CutImage(mat_rgb, 0, 0, length - marginHoleWidth, mat_rgb.Height); #else if (isLeft) { Cv2.Line(mat_rgb, new Point(length + marginHoleWidth, 0), new Point(length + marginHoleWidth, mat_rgb.Height), new Scalar(255, 0, 0), 20); return mat_rgb; } else { Cv2.Line(mat_rgb, new Point(length - marginHoleWidth, 0), new Point(length - marginHoleWidth, mat_rgb.Height), new Scalar(0, 255, 0), 20); return mat_rgb; } #endif } else { marginWidth = 0; if (isLeft) return CutImage(mat_rgb, bian + marginHoleWidth, 0, mat_rgb.Width - bian - marginHoleWidth, mat_rgb.Height); else return CutImage(mat_rgb, 0, 0, mat_rgb.Width - bian - marginHoleWidth, mat_rgb.Height); } } /// /// 寻边算法 /// /// /// /// /// /// private static int EdgeClipping2(Mat image, int FindType, Rect Roi, bool IsLeft) { Mat mat_rgb = image.Clone(Roi); int height = mat_rgb.Rows; int width = mat_rgb.Cols; int sf = 10; //缩放比例 int pix = 5; //获取均值区域长宽像素 int pointNum = 15; //获取找遍点数 int offsetGray = 5; //二值化偏差 //按比例缩放 int sf_height = height / sf; int sf_width = width / sf; Cv2.Resize(mat_rgb, mat_rgb, new Size(sf_width, sf_height), 0, 0, InterpolationFlags.Linear); //mat_rgb = mat_rgb.Resize(new Size(sf_width, sf_height)); int[] maxlev = new int[3] { 3, 1, 0 }; int[] srlev = new int[3] { 29, 25, 11 }; int length_t = 0; Mat[] lineImg = new Mat[3]; List lines = new List(); List total_t = new List(); for (int lv = 0; lv < 3; lv++) { lines.Clear(); total_t.Clear(); Mat himg = mat_rgb.Clone(); //mat_rgb = mat_rgb.PyrMeanShiftFiltering(10, 27, 3); Cv2.PyrMeanShiftFiltering(himg, himg, 10, srlev[lv], maxlev[lv]);//10,17; //分通道处理 Mat[] mv = Cv2.Split(himg); for (int cht = 0; cht < 3; cht++) { //转灰度图 //mat_rgb = mat_rgb.CvtColor(ColorConversionCodes.BGR2GRAY); Mat image_gray = new Mat(); image_gray = mv[cht]; //image_gray.ImWrite($"image_gray{cht}.jpg"); Mat image_Canny = new Mat(); Cv2.Canny(image_gray, image_Canny, 32, 64, 3); //image_Canny.ImWrite($"image_Canny{cht}.jpg"); var lins = Cv2.HoughLinesP(image_Canny, 1, Math.PI / 360, 0, 20, 10); lineImg[cht] = new Mat(new Size(himg.Cols, himg.Rows), MatType.CV_8UC1, new Scalar()); lines.Add(lins.Length); foreach (var item in lins) { var fdang = Math.Atan2((item.P2.Y - item.P1.Y), (item.P2.X - item.P1.X)); var ang = Math.Abs(fdang * (180 / Math.PI)); if (ang > 60 && ang < 120) Cv2.Line(lineImg[cht], item.P1.X, item.P1.Y, item.P2.X, item.P2.Y, new Scalar(255, 255, 255), 2); } //lineImg[cht].ImWrite($"image_Canny2_{cht}.jpg"); //mat_rgb = mat_rgb.Canny(32, 64); if (lins.Length >= 1) break; } if (lines.Count > 0 && lines.Max() >= 1) break; } //二值化 Mat image_Otsu = new Mat(); int hDis = sf_height / (pointNum + 2); //去除边缘两点 #if false //二值算法 List LeftAvg = new List(); List RightAvg = new List(); //double thb = Cv2.Threshold(image_gray, image_Otsu, 0, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu); #region 多点获取二值化均值 for (int i = 0; i < pointNum; i++) { Rect roiLeft = new Rect(0, hDis + hDis * i, pix, pix); Mat current_segmentL = image_gray.Clone(roiLeft); //Scalar ttr = current_segmentL.Mean(); LeftAvg.Add(current_segmentL.Mean().Val0); Rect roiRight = new Rect(sf_width - pix, hDis + hDis * i, pix, pix); Mat current_segmentR = image_gray.Clone(roiRight); RightAvg.Add(current_segmentR.Mean().Val0); } double thres = 0; if (IsLeft) { if (LeftAvg.Average() > RightAvg.Average()) thres = RightAvg.Max() + offsetGray; else thres = RightAvg.Min() - offsetGray; } else { if (LeftAvg.Average() > RightAvg.Average()) thres = LeftAvg.Min() - offsetGray; else thres = LeftAvg.Max() + offsetGray; } //double thres = (RightAvg.Average() + )/2; #endregion #endif #if false double min, max; image_gray.MinMaxLoc(out min, out max); double thres = (min + max) / 2; #endif #if false //二值化图片 //Cv2.Threshold(image_gray, image_Otsu, 0, 255, ThresholdTypes.Otsu); double thb = Cv2.Threshold(image_gray, image_Otsu, thres, 255, ThresholdTypes.Binary); image_Otsu.ImWrite("Otsu1.jpg"); Cv2.MedianBlur(image_Otsu, image_Otsu, 21); image_Otsu.ImWrite("Otsu2.jpg"); endTime = DateTimeOffset.Now; Console.WriteLine("灰度图二值化(ms): " + (endTime - startTime).TotalMilliseconds.ToString("0.000")); startTime = DateTimeOffset.Now; #else /* image_Otsu = image_Canny; */ #endif int findex = lines.FindIndex(x => x == lines.Max()); image_Otsu = lineImg[findex]; // 定义空数组保存结果 int[] total = new int[pointNum]; total_t = new List(); //bool isLeft = FindType == 0 ? true : false; // 平均截取pointNum行数据并处理图像 for (int i = 0; i < pointNum; i++) { // 截取当前行的图像 Rect roi = new Rect(0, hDis + hDis * i, sf_width, 1); //Mat current_segment = image_Otsu.Clone(roi); Mat current_segment = image_Otsu.Clone(roi); #if false #region 预处理 // 对当前行的图像进行平滑处理 Mat smoothed_image2 = new Mat(); Cv2.GaussianBlur(current_segment, smoothed_image2, new Size(5, 1), 0); // 计算当前行的灰度直方图 Mat absolute_histo2 = new Mat(); Cv2.CalcHist(new Mat[] { smoothed_image2 }, new int[] { 0 }, new Mat(), absolute_histo2, 1, new int[] { 256 }, new Rangef[] { new Rangef(0, 256) }); Cv2.GaussianBlur(current_segment, smoothed_image2, new Size(9, 1), 0); // 对图片进行分割 //double otsu_threshold; //threshold(smoothed_image, smoothed_image, 0, 255, THRESH_BINARY + THRESH_OTSU, &otsu_threshold); double otsu_threshold2 = Cv2.Threshold(smoothed_image2, smoothed_image2, 0, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu); // 使用形态学操作进行孔洞填充 Mat kernel3 = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(5, 1)); Mat filled_image3 = new Mat(); Cv2.MorphologyEx(smoothed_image2, filled_image3, MorphTypes.Close, kernel3); #endregion #else //Mat filled_image3 = current_segment.Clone(); Mat filled_image3 = current_segment; #endif #if true //从左到右判断边和从右到左判断边 int numX = 0; byte tempVal = 0; if (!IsLeft) { tempVal = filled_image3.At(0, 0); //filled_image3. for (int j = 0; j < filled_image3.Cols; j++) { if (filled_image3.At(0, j) != tempVal) { numX = j; break; } } } else { tempVal = filled_image3.At(0, filled_image3.Cols - 1); for (int j = filled_image3.Cols - 1; j >= 0; j--) { if (filled_image3.At(0, j) != tempVal) { numX = j; break; } } } //int numX = 0; //byte tempVal = 0; //unsafe //{ // byte* ptr = (byte*)filled_image3.Data; // if (isLeft) // { // tempVal = ptr[0]; // for (int j = 0; j < filled_image3.Cols; j++) // { // if (ptr[j] != tempVal) // { // numX = j; // break; // } // } // } // else // { // tempVal = ptr[filled_image3.Cols - 1]; // //tempVal = filled_image3.At(0, filled_image3.Cols - 1); // for (int j = filled_image3.Cols - 1; j >= 0; j--) // { // if (ptr[j] != tempVal) // { // numX = j; // break; // } // } // } //} #else int numX = Cv2.CountNonZero(filled_image3); #endif //int length_t = (numX > (sf_width / 2)) ? numX :sf_width - numX; length_t = numX; total[i] = (length_t); if (length_t > 0) total_t.Add(length_t); current_segment.Dispose(); } // 取平均值作为宽度 int length = 0; if (total_t.Count > 0) { length = (int)total_t.Average(); //乘上换算系数还原 length = length * sf + Roi.X; } //endTime = DateTimeOffset.Now; //Console.WriteLine("计算边(ms): " + (endTime - startTime).TotalMilliseconds.ToString("0.000")); // 判断数据是否异常,判断当前线段的宽度是否大于设定像素的偏差 //int abnormal_pxl = 100 / 4; //for (int i = 0; i < pointNum; i++) //{ // if (Math.Abs(total[i] - length) > abnormal_pxl) // Console.WriteLine("数据异常!"); // //出现数据异常,当段图片的宽度有问题 //} //if ((length > 6520 && length < 6530) || (length > 1570 && length < 1590)) // ; //else // ; mat_rgb.Dispose(); //himg.Dispose(); //image_gray.Dispose(); //image_Canny.Dispose(); //image_Otsu.Dispose(); return length; } private static StructuredEdgeDetection _edgeDetect; public static void LoadEdgeMode() { if(_edgeDetect == null) _edgeDetect = OpenCvSharp.XImgProc.CvXImgProc.CreateStructuredEdgeDetection("model.yml"); } /// /// 模型寻边 /// /// /// /// /// /// private static int EdgeClipping3(Mat image, int FindType, Rect Roi, bool IsLeft) { Mat mat_rgb = image.Clone(Roi); int height = mat_rgb.Rows; int width = mat_rgb.Cols; int sf = 10; //缩放比例 int pix = 5; //获取均值区域长宽像素 int pointNum = 15; //获取找遍点数 int offsetGray = 5; //二值化偏差 int length_t = 0; List lines = new List(); List total_t = new List(); //按比例缩放 double sf_height = height / sf; double sf_width = width / sf; Cv2.Resize(mat_rgb, mat_rgb, new Size(sf_width, sf_height), 0, 0, InterpolationFlags.Linear); Mat himg = new Mat(); Mat edgeimg = new Mat(); Cv2.CvtColor(mat_rgb, edgeimg, ColorConversionCodes.BGR2RGB); Mat edges = new Mat(); edgeimg.ConvertTo(edgeimg, MatType.CV_32F, 1 / 255.0); if(_edgeDetect == null) LoadEdgeMode(); //Cv2.Normalize(edgeimg, edgeimg, 1.0, 0, NormTypes.L2, -1); _edgeDetect.DetectEdges(edgeimg, edges); Mat image_Otsu = new Mat(); int hDis = (int)sf_height / (pointNum + 2); //去除边缘两点 edges.ConvertTo(image_Otsu, MatType.CV_8U, 255.0); Cv2.Threshold(image_Otsu, image_Otsu, 0, 255, ThresholdTypes.Otsu); // 定义空数组保存结果 int[] total = new int[pointNum]; // 平均截取pointNum行数据并处理图像 for (int i = 0; i < pointNum; i++) { // 截取当前行的图像 Rect roi = new Rect(0, hDis + hDis * i, (int)sf_width, 1); Mat current_segment = image_Otsu.Clone(roi); //Mat filled_image3 = current_segment.Clone(); Mat filled_image3 = current_segment; #if true //从左到右判断边和从右到左判断边 int numX = 0; int tm = 0; byte tempVal = 0; bool findOne = false; if (!IsLeft) { tempVal = filled_image3.At(0, 0); //filled_image3. for (int j = 0; j < filled_image3.Cols; j++) { if (filled_image3.At(0, j) != tempVal) { if (!findOne) { tm = j; findOne = true; tempVal = filled_image3.At(0, j); } else { //numX = j; numX = (tm + j) / 2; break; } } } } else { tempVal = filled_image3.At(0, filled_image3.Cols - 1); for (int j = filled_image3.Cols - 1; j >= 0; j--) { if (filled_image3.At(0, j) != tempVal) { if (!findOne) { tm = j; findOne = true; tempVal = filled_image3.At(0, j); } else { //numX = j; numX = (tm + j) / 2; break; } } } } #else int numX = Cv2.CountNonZero(filled_image3); #endif //length_t = (numX > (sf_width / 2)) ? numX :(int)(sf_width - numX); length_t = numX; total[i] = (length_t); if (length_t > 0) total_t.Add(length_t); } // 取平均值作为宽度 int length = 0; if (total_t.Count > 0) { length = (int)total_t.Average(); if (IsLeft) length = length - ConfMgr.Instance.SysConfigParams.Crop_offset; else length = length + ConfMgr.Instance.SysConfigParams.Crop_offset; //乘上换算系数还原 length = length * sf + Roi.X; } return length; } #endregion #region 合并 /// /// 合并MAT(宽高必需一致) /// /// /// /// public static Mat MergeImage_sameSize(Mat[] mats, bool isHorizontal = true) { Mat matOut = new Mat(); if (isHorizontal) Cv2.HConcat(mats, matOut);//横向拼接 else Cv2.VConcat(mats, matOut);//纵向拼接 return matOut; } #endregion } }