革博士程序V1仓库
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

464 строки
19 KiB

  1. using OpenCvSharp;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. namespace GeBoShi.SysCtrl
  8. {
  9. public static class OpencvUtils
  10. {
  11. public static int image_width = 2048;
  12. public static int image_height = 2048;
  13. #region 图像预处理
  14. public static Mat Resize(Mat mat, int width, int height, out int xw)
  15. {
  16. OpenCvSharp.Size dsize = new OpenCvSharp.Size(width, height);
  17. Mat mat2 = new Mat();
  18. //Cv2.Resize(mat, mat2, dsize);
  19. ResizeUniform(mat, dsize, out mat2, out xw);
  20. return mat2;
  21. }
  22. /// <summary>
  23. /// 等比例缩放
  24. /// </summary>
  25. /// <param name="src"></param>
  26. /// <param name="dst_size"></param>
  27. /// <param name="dst"></param>
  28. /// <returns></returns>
  29. public static int ResizeUniform(Mat src, Size dst_size, out Mat dst, out int xw)
  30. {
  31. xw = 0;
  32. int w = src.Cols;
  33. int h = src.Rows;
  34. int dst_w = dst_size.Width;
  35. int dst_h = dst_size.Height;
  36. //std::cout << "src: (" << h << ", " << w << ")" << std::endl;
  37. dst = new Mat(dst_h, dst_w, MatType.CV_8UC3, new Scalar(114, 114, 114));
  38. float[] ratio = new float[2];
  39. float ratio_src = w * 1.0f / h;
  40. float ratio_dst = dst_w * 1.0f / dst_h;
  41. int tmp_w = 0;
  42. int tmp_h = 0;
  43. if (ratio_src > ratio_dst)
  44. {
  45. tmp_w = dst_w;
  46. tmp_h = (int)(dst_w * 1.0f / w) * h;
  47. ratio[0] = (float)w / (float)tmp_w;
  48. ratio[1] = (float)h / (float)tmp_h;
  49. }
  50. else if (ratio_src < ratio_dst)
  51. {
  52. tmp_h = dst_h;
  53. tmp_w = (int)((dst_h * 1.0f / h) * w);
  54. ratio[0] = (float)w / (float)tmp_w;
  55. ratio[1] = (float)h / (float)tmp_h;
  56. }
  57. else
  58. {
  59. Cv2.Resize(src, dst, dst_size);
  60. ratio[0] = (float)w / (float)tmp_w;
  61. ratio[1] = (float)h / (float)tmp_h;
  62. return 0;
  63. }
  64. //std::cout << "tmp: (" << tmp_h << ", " << tmp_w << ")" << std::endl;
  65. Mat tmp = new Mat();
  66. Cv2.Resize(src, tmp, new Size(tmp_w, tmp_h));
  67. unsafe
  68. {
  69. if (tmp_w != dst_w)
  70. { //高对齐,宽没对齐
  71. int index_w = (int)((dst_w - tmp_w) / 2.0);
  72. xw = index_w;
  73. //std::cout << "index_w: " << index_w << std::endl;
  74. for (int i = 0; i < dst_h; i++)
  75. {
  76. 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);
  77. }
  78. }
  79. else if (tmp_h != dst_h)
  80. { //宽对齐, 高没有对齐
  81. int index_h = (int)((dst_h - tmp_h) / 2.0);
  82. //std::cout << "index_h: " << index_h << std::endl;
  83. 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);
  84. }
  85. else
  86. {
  87. }
  88. }
  89. return 0;
  90. }
  91. public static Mat ResizeMat(Mat mat, int width, int height)
  92. {
  93. OpenCvSharp.Size dsize = new OpenCvSharp.Size(width, height);
  94. Mat mat2 = new Mat();
  95. Cv2.Resize(mat, mat2, dsize);
  96. return mat2;
  97. }
  98. /// <summary>
  99. /// 计算合理宽幅
  100. /// </summary>
  101. /// <param name="sumWidth">多个相机图像总宽(外部去除重合部分)</param>
  102. /// <returns></returns>
  103. public static int GetWidthForResize(int sumWidth)
  104. {
  105. //保证计算8x2 16个小图
  106. int count = (int)Math.Round(sumWidth * 1.0f / image_width, 0);
  107. count = 8;
  108. return count * image_width;
  109. //int count = sumWidth / image_width;
  110. ////int remainder = sumWidth % image_width;
  111. //if (count % 2 == 0)
  112. // return count * image_width;
  113. //else
  114. // return count * image_width+ image_width;
  115. }
  116. /// <summary>
  117. /// 裁切指定区域
  118. /// </summary>
  119. /// <param name="mat"></param>
  120. /// <param name="x"></param>
  121. /// <param name="y"></param>
  122. /// <param name="width"></param>
  123. /// <param name="height"></param>
  124. /// <returns></returns>
  125. public static Mat CutImage(Mat mat, int x, int y, int width, int height)
  126. {
  127. Rect roi = new Rect(x, y, width, height);
  128. return new Mat(mat, roi).Clone();
  129. }
  130. #endregion
  131. #region 裁边
  132. /// <summary>
  133. /// 裁边
  134. /// </summary>
  135. /// <param name="mat_rgb"></param>
  136. /// <param name="isLeft"></param>
  137. /// <param name="marginHoleWidth"></param>
  138. /// <param name="marginWidth"></param>
  139. /// <returns></returns>
  140. public static Mat getMaxInsetRect2(Mat mat_rgb, bool isLeft, int marginHoleWidth, out int marginWidth)
  141. {
  142. int bian = 3500;
  143. Rect Roi;
  144. if (!isLeft)
  145. Roi = new Rect(mat_rgb.Width - bian, 0, bian, mat_rgb.Height);
  146. else
  147. Roi = new Rect(0, 0, bian, mat_rgb.Height);
  148. int type = isLeft ? 1 : 0;
  149. int len = EdgeClipping2(mat_rgb, type, Roi, isLeft);
  150. #if false
  151. //Mat mat_rgb = new Mat("E:\\CPL\\测试代码\\边缘检测\\test\\test\\test\\img\\19.bmp");
  152. Mat image_gray = new Mat();
  153. Cv2.CvtColor(mat_rgb, image_gray, ColorConversionCodes.BGR2GRAY);
  154. //cvtColor(image_RGB, image, COLOR_RGB2GRAY);
  155. int height = image_gray.Rows;
  156. int width = image_gray.Cols;
  157. // 算法定义:取均分5段图片的五条横线,经过一系列处理之后,二值化,找到沿边位置,然后取均值作为直边,在缩进一段有针眼的位置
  158. // 定义每段的行数
  159. int num_rows = 5;
  160. int segment_height = height / num_rows - 1;
  161. // 定义空数组保存结果
  162. int[] total = new int[num_rows];
  163. // 平均截取5行数据并处理图像
  164. for (int i = 0; i < num_rows; i++)
  165. {
  166. // 截取当前行的图像
  167. int start_row = i * segment_height;
  168. Rect roi = new Rect(0, start_row, width, 1);
  169. Mat current_segment = image_gray.Clone(roi);
  170. // 对当前行的图像进行平滑处理
  171. Mat smoothed_image = new Mat();
  172. Cv2.GaussianBlur(current_segment, smoothed_image, new Size(5, 1), 0);
  173. // 计算当前行的灰度直方图
  174. Mat absolute_histo = new Mat();
  175. Cv2.CalcHist(new Mat[] { smoothed_image }, new int[] { 0 }, new Mat(), absolute_histo, 1, new int[] { 256 }, new Rangef[] { new Rangef(0, 256) });
  176. Cv2.GaussianBlur(current_segment, smoothed_image, new Size(19, 1), 0);
  177. // 对图片进行分割i+1
  178. //double otsu_threshold;
  179. //threshold(smoothed_image, smoothed_image, 0, 255, THRESH_BINARY + THRESH_OTSU, &otsu_threshold);
  180. Cv2.Threshold(smoothed_image, smoothed_image, 0, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu);
  181. // 使用形态学操作进行孔洞填充
  182. Mat kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(25, 1));
  183. Mat filled_image = new Mat();
  184. Cv2.MorphologyEx(smoothed_image, filled_image, MorphTypes.Close, kernel);
  185. // 取较长的一个值作为皮革的宽度
  186. int num_255 = Cv2.CountNonZero(filled_image);
  187. int length_t = (num_255 > width / 2) ? num_255 : width - num_255;
  188. total[i] = (length_t);
  189. API.OutputDebugString($"getMaxInsetRect2: 【{i + 1}】{length_t}={num_255}|{width}");
  190. }
  191. // 取平均值作为宽度
  192. int length = (int)total.Average();
  193. marginWidth = width-length;
  194. #endif
  195. int length = (len > mat_rgb.Width / 2) ? len : mat_rgb.Width - len;
  196. marginWidth = mat_rgb.Width - length;
  197. // 判断数据是否异常,判断当前线段的宽度是否大于设定像素的偏差
  198. //int abnormal_pxl = 200;
  199. //for (int i = 0; i < num_rows; i++)
  200. //{
  201. // if (Math.Abs(total[i] - length) > abnormal_pxl)
  202. // throw new Exception("数据异常,当段图片的宽度有问题!");
  203. //}
  204. //右侧相机,拍摄产品,边缘位于右侧判断,缩进100像素,去点针眼
  205. //Cv2.Line(mat_rgb, new Point(length - 100, 0), new Point(length - 100, height), new Scalar(255, 0, 0), 20);
  206. ////左侧相机,拍摄产品,边缘位于左侧判断,缩进100像素,去点针眼
  207. //Cv2.Line(mat_rgb, new Point(width - length + 100, 0), new Point(width - length + 100, height), new Scalar(0, 255, 0), 20);
  208. //int decWidth = width - length + marginHoleWidth;
  209. //if (isLeft)
  210. // return cutImage(mat_rgb, decWidth, 0, width- decWidth, height);
  211. //else
  212. // return cutImage(mat_rgb, 0, 0, width - decWidth, height);
  213. //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}");
  214. if (isLeft)
  215. return CutImage(mat_rgb, mat_rgb.Width - length + marginHoleWidth, 0, length - marginHoleWidth, mat_rgb.Height);
  216. else
  217. return CutImage(mat_rgb, 0, 0, length - marginHoleWidth, mat_rgb.Height);
  218. }
  219. /// <summary>
  220. /// 寻边算法
  221. /// </summary>
  222. /// <param name="image"></param>
  223. /// <param name="FindType"></param>
  224. /// <param name="Roi"></param>
  225. /// <param name="IsLeft"></param>
  226. /// <returns></returns>
  227. public static int EdgeClipping2(Mat image, int FindType, Rect Roi, bool IsLeft)
  228. {
  229. DateTimeOffset startTime = DateTimeOffset.Now;
  230. Mat mat_rgb = image.Clone(Roi);
  231. int height = mat_rgb.Rows;
  232. int width = mat_rgb.Cols;
  233. int sf = 10; //缩放比例
  234. int pix = 5; //获取均值区域长宽像素
  235. int pointNum = 15; //获取找遍点数
  236. int offsetGray = 5; //二值化偏差
  237. //按比例缩放
  238. int sf_height = height / sf;
  239. int sf_width = width / sf;
  240. Cv2.Resize(mat_rgb, mat_rgb, new Size(sf_width, sf_height), 0, 0, InterpolationFlags.Linear);
  241. Mat himg = new Mat();
  242. himg = mat_rgb.Clone();
  243. DateTimeOffset endTime = DateTimeOffset.Now;
  244. //Console.WriteLine("图片缩小(ms): " + (endTime - startTime).TotalMilliseconds.ToString("0.000"));
  245. startTime = DateTimeOffset.Now;
  246. //滤过去除多余噪声
  247. //Cv2.EdgePreservingFilter(himg, himg, EdgePreservingMethods.NormconvFilter);
  248. //Cv2.PyrMeanShiftFiltering(himg, himg, 1, 2, 1);
  249. Cv2.PyrMeanShiftFiltering(himg, himg, 10, 17, 2);
  250. //himg.ImWrite("himg.jpg");
  251. endTime = DateTimeOffset.Now;
  252. //Console.WriteLine("滤过去除多余噪声(ms): " + (endTime - startTime).TotalMilliseconds.ToString("0.000"));
  253. startTime = DateTimeOffset.Now;
  254. //转灰度图
  255. Mat image_gray = new Mat();
  256. Cv2.CvtColor(himg, image_gray, ColorConversionCodes.BGR2GRAY);
  257. //image_gray.ImWrite("image_gray.jpg");
  258. Mat image_Canny = new Mat();
  259. Cv2.Canny(image_gray, image_Canny, 32, 64);
  260. //image_Canny.ImWrite("image_Canny.jpg");
  261. //二值化
  262. Mat image_Otsu = new Mat();
  263. int hDis = sf_height / (pointNum + 2); //去除边缘两点
  264. #if false //二值算法
  265. List<double> LeftAvg = new List<double>();
  266. List<double> RightAvg = new List<double>();
  267. //double thb = Cv2.Threshold(image_gray, image_Otsu, 0, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu);
  268. #region 多点获取二值化均值
  269. for (int i = 0; i < pointNum; i++)
  270. {
  271. Rect roiLeft = new Rect(0, hDis + hDis * i, pix, pix);
  272. Mat current_segmentL = image_gray.Clone(roiLeft);
  273. //Scalar ttr = current_segmentL.Mean();
  274. LeftAvg.Add(current_segmentL.Mean().Val0);
  275. Rect roiRight = new Rect(sf_width - pix, hDis + hDis * i, pix, pix);
  276. Mat current_segmentR = image_gray.Clone(roiRight);
  277. RightAvg.Add(current_segmentR.Mean().Val0);
  278. }
  279. double thres = 0;
  280. if (IsLeft)
  281. {
  282. if (LeftAvg.Average() > RightAvg.Average())
  283. thres = RightAvg.Max() + offsetGray;
  284. else
  285. thres = RightAvg.Min() - offsetGray;
  286. }
  287. else
  288. {
  289. if (LeftAvg.Average() > RightAvg.Average())
  290. thres = LeftAvg.Min() - offsetGray;
  291. else
  292. thres = LeftAvg.Max() + offsetGray;
  293. }
  294. //double thres = (RightAvg.Average() + )/2;
  295. #endregion
  296. #endif
  297. #if false
  298. double min, max;
  299. image_gray.MinMaxLoc(out min, out max);
  300. double thres = (min + max) / 2;
  301. #endif
  302. #if false //二值化图片
  303. //Cv2.Threshold(image_gray, image_Otsu, 0, 255, ThresholdTypes.Otsu);
  304. double thb = Cv2.Threshold(image_gray, image_Otsu, thres, 255, ThresholdTypes.Binary);
  305. image_Otsu.ImWrite("Otsu1.jpg");
  306. Cv2.MedianBlur(image_Otsu, image_Otsu, 21);
  307. image_Otsu.ImWrite("Otsu2.jpg");
  308. endTime = DateTimeOffset.Now;
  309. Console.WriteLine("灰度图二值化(ms): " + (endTime - startTime).TotalMilliseconds.ToString("0.000"));
  310. startTime = DateTimeOffset.Now;
  311. #else
  312. image_Otsu = image_Canny;
  313. #endif
  314. // 定义空数组保存结果
  315. int[] total = new int[pointNum];
  316. List<int> total_t = new List<int>();
  317. bool isLeft = FindType == 0 ? true : false;
  318. // 平均截取pointNum行数据并处理图像
  319. for (int i = 0; i < pointNum; i++)
  320. {
  321. // 截取当前行的图像
  322. Rect roi = new Rect(0, hDis + hDis * i, sf_width, 1);
  323. Mat current_segment = image_Otsu.Clone(roi);
  324. #if false
  325. #region 预处理
  326. // 对当前行的图像进行平滑处理
  327. Mat smoothed_image2 = new Mat();
  328. Cv2.GaussianBlur(current_segment, smoothed_image2, new Size(5, 1), 0);
  329. // 计算当前行的灰度直方图
  330. Mat absolute_histo2 = new Mat();
  331. Cv2.CalcHist(new Mat[] { smoothed_image2 }, new int[] { 0 }, new Mat(), absolute_histo2, 1, new int[] { 256 }, new Rangef[] { new Rangef(0, 256) });
  332. Cv2.GaussianBlur(current_segment, smoothed_image2, new Size(9, 1), 0);
  333. // 对图片进行分割
  334. //double otsu_threshold;
  335. //threshold(smoothed_image, smoothed_image, 0, 255, THRESH_BINARY + THRESH_OTSU, &otsu_threshold);
  336. double otsu_threshold2 = Cv2.Threshold(smoothed_image2, smoothed_image2, 0, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu);
  337. // 使用形态学操作进行孔洞填充
  338. Mat kernel3 = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(5, 1));
  339. Mat filled_image3 = new Mat();
  340. Cv2.MorphologyEx(smoothed_image2, filled_image3, MorphTypes.Close, kernel3);
  341. #endregion
  342. #else
  343. //Mat filled_image3 = current_segment.Clone();
  344. Mat filled_image3 = current_segment;
  345. #endif
  346. #if true
  347. //从左到右判断边和从右到左判断边
  348. int numX = 0;
  349. byte tempVal = 0;
  350. if (isLeft)
  351. {
  352. tempVal = filled_image3.At<byte>(0, 0);
  353. for (int j = 0; j < filled_image3.Cols; j++)
  354. {
  355. if (filled_image3.At<byte>(0, j) != tempVal)
  356. {
  357. numX = j;
  358. break;
  359. }
  360. }
  361. }
  362. else
  363. {
  364. tempVal = filled_image3.At<byte>(0, filled_image3.Cols - 1);
  365. for (int j = filled_image3.Cols - 1; j >= 0; j--)
  366. {
  367. if (filled_image3.At<byte>(0, j) != tempVal)
  368. {
  369. numX = j;
  370. break;
  371. }
  372. }
  373. }
  374. #else
  375. int numX = Cv2.CountNonZero(filled_image3);
  376. #endif
  377. //int length_t = (numX > (sf_width / 2)) ? numX :sf_width - numX;
  378. int length_t = numX;
  379. total[i] = (length_t);
  380. if (length_t > 0)
  381. total_t.Add(length_t);
  382. }
  383. // 取平均值作为宽度
  384. int length = 0;
  385. if(total_t.Count> 0)
  386. length = (int)total_t.Average();
  387. endTime = DateTimeOffset.Now;
  388. //Console.WriteLine("计算边(ms): " + (endTime - startTime).TotalMilliseconds.ToString("0.000"));
  389. // 判断数据是否异常,判断当前线段的宽度是否大于设定像素的偏差
  390. //int abnormal_pxl = 100 / 4;
  391. //for (int i = 0; i < pointNum; i++)
  392. //{
  393. // if (Math.Abs(total[i] - length) > abnormal_pxl)
  394. // Console.WriteLine("数据异常!");
  395. // //出现数据异常,当段图片的宽度有问题
  396. //}
  397. //乘上换算系数还原
  398. length = length * sf + Roi.X;
  399. return length;
  400. }
  401. #endregion
  402. #region 合并
  403. /// <summary>
  404. /// 合并MAT(宽高必需一致)
  405. /// </summary>
  406. /// <param name="mats"></param>
  407. /// <param name="isHorizontal"></param>
  408. /// <returns></returns>
  409. public static Mat MergeImage_sameSize(Mat[] mats, bool isHorizontal = true)
  410. {
  411. Mat matOut = new Mat();
  412. if (isHorizontal)
  413. Cv2.HConcat(mats, matOut);//横向拼接
  414. else
  415. Cv2.VConcat(mats, matOut);//纵向拼接
  416. return matOut;
  417. }
  418. #endregion
  419. }
  420. }