DetectVessel.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Drawing;
  4. using System.Linq;
  5. using System.Threading.Tasks;
  6. using Emgu.CV;
  7. using Emgu.CV.CvEnum;
  8. using Emgu.CV.Structure;
  9. using Emgu.CV.Util;
  10. using WingServerCommon.Log;
  11. namespace WingAIDiagnosisService.Carotid.Utilities
  12. {
  13. /// <summary>
  14. /// 可能是血管的边缘线
  15. /// </summary>
  16. public struct EdgeLines
  17. {
  18. public List<Point> UpperLinePoints;
  19. public List<Point> LowerLinePoints;
  20. }
  21. //检测血管
  22. public class DetectVessel
  23. {
  24. //相邻血管边缘差
  25. private const int DiffHeight = 5;
  26. //候选血管面积差
  27. private const int DiffArea = 200;
  28. /// <summary>
  29. /// 边缘点
  30. /// </summary>
  31. private struct EdgePoints
  32. {
  33. public Point StartPoint;
  34. public Point EndPoint;
  35. }
  36. /// <summary>
  37. /// 可能是血管的边缘线
  38. /// </summary>
  39. private struct VesselEdgeLines
  40. {
  41. public List<Point> StartLine;
  42. public List<Point> EndLine;
  43. //血管外接矩阵
  44. public Rectangle VesselRectangle;
  45. //血管高度方差
  46. public float HeightStd;
  47. //血管平均高度
  48. public float AvgHeight;
  49. //血管宽度
  50. public int VesselWidth;
  51. //血管面积
  52. public int Area;
  53. }
  54. /// <summary>
  55. /// 获得血管所在区域rect
  56. /// </summary>
  57. /// <param name="imgGray"></param>
  58. /// <returns></returns>
  59. public EdgeLines GetVessel(Image<Gray, byte> imgGray)
  60. {
  61. try
  62. {
  63. //血管最小高度 399 * 0.08
  64. var minVesselH = (int)(imgGray.Height * VesselConstParameter.MinVesslHeightRatio);
  65. //血管最小位置 399 * 0.1
  66. var minVesselPositionY = (int)(imgGray.Height * VesselConstParameter.MinVesselPositionRatio);
  67. //血管最小宽度 813 * 0.025
  68. var minVesselW = (int)(imgGray.Width * VesselConstParameter.MinVesselWidthRatio);
  69. using (var imageBri = ImagePreprocessing(imgGray, minVesselH))
  70. {
  71. //所有可能的上下血管端点
  72. var allEdgePoints = GetAllEdgePoints(imageBri);
  73. //寻找可能的血管边缘
  74. var allVesselLines = GetAllVesselEdgeLines(allEdgePoints, imageBri.Width, minVesselH);
  75. //计算相邻连通域间的黑色区域高度的方差和均值
  76. CalculationVesselLinesAttributes(allVesselLines);
  77. //血管筛选
  78. var vesselLineList = allVesselLines.Where(m =>
  79. m.VesselWidth > minVesselW && m.AvgHeight > minVesselH &&
  80. m.VesselRectangle.Top > minVesselPositionY).ToList();
  81. if (vesselLineList.Count == 0)
  82. {
  83. return new EdgeLines
  84. {
  85. UpperLinePoints = new List<Point>(),
  86. LowerLinePoints = new List<Point>()
  87. };
  88. }
  89. vesselLineList = FilterVesselByAiResult(vesselLineList).ToList();
  90. //得到最符合条件的血管边缘
  91. vesselLineList =vesselLineList.OrderByDescending(o => o.Area).ToList();
  92. //如果有两个以上候选
  93. if (vesselLineList.Count > 1)
  94. {
  95. //如果两个面积相差不大
  96. if (vesselLineList[0].Area - vesselLineList[1].Area < DiffArea)
  97. {
  98. //如果面积大的比面积小的血管高度方差大很多,则选第二个候选项作为血管
  99. if (vesselLineList[0].HeightStd - vesselLineList[1].HeightStd > VesselConstParameter.DiffStd)
  100. {
  101. return new EdgeLines
  102. {
  103. UpperLinePoints = vesselLineList[1].StartLine,
  104. LowerLinePoints = vesselLineList[1].EndLine
  105. };
  106. }
  107. }
  108. }
  109. return new EdgeLines
  110. {
  111. UpperLinePoints = vesselLineList[0].StartLine,
  112. LowerLinePoints = vesselLineList[0].EndLine
  113. };
  114. }
  115. }
  116. catch (Exception e)
  117. {
  118. Logger.WriteLineError("DetectVessel GetVessel error," + e.Message + "," + e.StackTrace);
  119. return new EdgeLines();
  120. }
  121. }
  122. private IEnumerable<VesselEdgeLines> FilterVesselByAiResult(IEnumerable<VesselEdgeLines> inputLines)
  123. {
  124. var vesselYEdge = GetModelVesselAndPlaque.Instance.GetVesselYEdge();
  125. var filteredVessel = inputLines.Where(m => m.VesselRectangle.Bottom > vesselYEdge.AvgTop && m.VesselRectangle.Top < vesselYEdge.AvgBottom).ToList();
  126. if (filteredVessel.Count > 0)
  127. {
  128. return filteredVessel;
  129. }
  130. return inputLines;
  131. }
  132. /// <summary>
  133. /// 图像预处理
  134. /// </summary>
  135. /// <param name="imgGray"></param>
  136. /// <returns></returns>
  137. private Image<Gray, byte> ImagePreprocessing(Image<Gray, byte> imgGray, int minVesselH)
  138. {
  139. var imageBri = new Image<Gray, byte>(imgGray.Width, imgGray.Height);
  140. try
  141. {
  142. //图像增强
  143. CvInvoke.EqualizeHist(imgGray, imgGray);
  144. //二值化
  145. var threshold = ImageTools.GetThreshold(imgGray);
  146. CvInvoke.Threshold(imgGray, imageBri, threshold, 255, ThresholdType.Binary);
  147. //中值滤波去噪点
  148. CvInvoke.MedianBlur(imageBri, imageBri, 3);
  149. //反转二值化图像
  150. CvInvoke.Threshold(imageBri, imageBri, 100, 255, ThresholdType.BinaryInv);
  151. //去除杂质
  152. RemoveNonVessel(imageBri, false, minVesselH);
  153. //反转二值化图像
  154. CvInvoke.Threshold(imageBri, imageBri, 100, 255, ThresholdType.BinaryInv);
  155. //去掉边缘周围的杂质
  156. RemoveNonVessel(imageBri, true, minVesselH);
  157. return imageBri;
  158. }
  159. catch (Exception e)
  160. {
  161. Logger.WriteLineError("DetectVessel ImagePreprocessing error," + e.Message + "," + e.StackTrace);
  162. return imageBri;
  163. }
  164. }
  165. /// <summary>
  166. /// 计算血管线的属性
  167. /// </summary>
  168. /// <param name="allVesselLines"></param>
  169. private void CalculationVesselLinesAttributes(List<VesselEdgeLines> allVesselLines)
  170. {
  171. var count1 = allVesselLines.Count;
  172. for (var j = 0; j < count1; ++j)
  173. {
  174. var startLine = allVesselLines[j].StartLine;
  175. var endLine = allVesselLines[j].EndLine;
  176. var count = startLine.Count;
  177. var vesselHeight = new int[count];
  178. var sumHeight = 0;
  179. var minY = int.MaxValue;
  180. var maxY = 0;
  181. for (var i = 0; i < count; ++i)
  182. {
  183. var startY = startLine[i].Y;
  184. var endY = endLine[i].Y;
  185. if (startY < minY)
  186. {
  187. minY = startY;
  188. }
  189. if (endY > maxY)
  190. {
  191. maxY = endY;
  192. }
  193. vesselHeight[i] = endY - startY;
  194. sumHeight += vesselHeight[i];
  195. }
  196. var avgHeight = sumHeight / (float)count;
  197. float sumStd = 0;
  198. for (var i = 0; i < count; ++i)
  199. {
  200. var diff = vesselHeight[i] - avgHeight;
  201. sumStd += diff * diff;
  202. }
  203. sumStd = (float)Math.Sqrt(sumStd / count);
  204. allVesselLines[j] = new VesselEdgeLines
  205. {
  206. StartLine = startLine,
  207. EndLine = endLine,
  208. AvgHeight = avgHeight,
  209. HeightStd = sumStd,
  210. Area = sumHeight,
  211. VesselWidth = count,
  212. VesselRectangle = new Rectangle(new Point(startLine[0].X, minY), new Size(startLine.Last().X - startLine[0].X, maxY - minY))
  213. };
  214. }
  215. }
  216. /// <summary>
  217. /// 获得所有的边缘点
  218. /// </summary>
  219. /// <param name="imageGray"></param>
  220. /// <returns></returns>
  221. private List<EdgePoints>[] GetAllEdgePoints(Image<Gray, byte> imageGray)
  222. {
  223. try
  224. {
  225. var parallelOption = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount };
  226. var width = imageGray.Width;
  227. var height = imageGray.Height;
  228. var allEdgePoints = new List<EdgePoints>[width];
  229. var dataArray = new byte[width][];
  230. Parallel.For(0, width, parallelOption, w =>
  231. {
  232. dataArray[w] = new byte[height];
  233. for (var h = 1; h != height; ++h)
  234. {
  235. dataArray[w][h] = imageGray.Data[h, w, 0];
  236. }
  237. allEdgePoints[w] = GetOneRowEdgePoints(dataArray[w], w);
  238. });
  239. return allEdgePoints;
  240. }
  241. catch (Exception e)
  242. {
  243. Logger.WriteLineError("DetectVessel GetAllEdgePoints error," + e.Message + "," + e.StackTrace);
  244. return new List<EdgePoints>[0];
  245. }
  246. }
  247. /// <summary>
  248. /// 获得一列图像的开始点和结束点
  249. /// </summary>
  250. /// <param name="dataArray"></param>
  251. /// <param name="w"></param>
  252. /// <returns></returns>
  253. private List<EdgePoints> GetOneRowEdgePoints(byte[] dataArray, int w)
  254. {
  255. var startPoint = new Point();
  256. var start = false;
  257. var height = dataArray.Length;
  258. var edgeList = new List<EdgePoints>();
  259. var threshold = 100;
  260. for (var h = 1; h < height - 1; ++h)
  261. {
  262. var data = dataArray[h];
  263. if (data > threshold)
  264. {
  265. if (!start)
  266. {
  267. start = true;
  268. }
  269. }
  270. else
  271. {
  272. if (start)
  273. {
  274. var dataLast = dataArray[h - 1];
  275. var dataNext = dataArray[h + 1];
  276. if (dataLast > threshold)
  277. {
  278. startPoint = new Point(w, h);
  279. }
  280. if (dataNext > threshold)
  281. {
  282. edgeList.Add(new EdgePoints
  283. {
  284. StartPoint = startPoint,
  285. EndPoint = new Point(w, h)
  286. });
  287. start = false;
  288. }
  289. }
  290. }
  291. }
  292. return edgeList;
  293. }
  294. /// <summary>
  295. /// 获得所有血管边缘
  296. /// </summary>
  297. /// <param name="allEdgePoints"></param>
  298. /// <param name="width"></param>
  299. /// <returns></returns>
  300. private List<VesselEdgeLines> GetAllVesselEdgeLines(List<EdgePoints>[] allEdgePoints, int width, int minVesselH)
  301. {
  302. try
  303. {
  304. var minH = minVesselH / 3;
  305. //生成边缘线-----
  306. var lineList = new List<VesselEdgeLines>();
  307. for (var w = 0; w < width; ++w)
  308. {
  309. foreach (var edgePoints in allEdgePoints[w])
  310. {
  311. var h = edgePoints.EndPoint.Y - edgePoints.StartPoint.Y;
  312. if (h < minH)
  313. {
  314. continue;
  315. }
  316. var index = GetTheClosestContours(lineList, edgePoints);
  317. if (index == -1)
  318. {
  319. lineList.Add(new VesselEdgeLines
  320. {
  321. StartLine = new List<Point> { edgePoints.StartPoint },
  322. EndLine = new List<Point> { edgePoints.EndPoint }
  323. });
  324. }
  325. else
  326. {
  327. lineList[index].StartLine.Add(edgePoints.StartPoint);
  328. lineList[index].EndLine.Add(edgePoints.EndPoint);
  329. }
  330. }
  331. }
  332. return lineList;
  333. }
  334. catch (Exception e)
  335. {
  336. Logger.WriteLineError("DetectVessel GetAllVesselEdgeLines error," + e.Message + "," + e.StackTrace);
  337. return new List<VesselEdgeLines>();
  338. }
  339. }
  340. //寻找合适的points轮廓
  341. private int GetTheClosestContours(List<VesselEdgeLines> linePoints, EdgePoints edgePoints)
  342. {
  343. try
  344. {
  345. var count = linePoints.Count;
  346. if (count == 0)
  347. {
  348. return -1;
  349. }
  350. var maxClosePointsNum = -1;
  351. var maxIndex = -1;
  352. for (var i = 0; i < count; ++i)
  353. {
  354. var startPoint = linePoints[i].StartLine.Last();
  355. var endPoint = linePoints[i].EndLine.Last();
  356. var maxStart = Math.Max(startPoint.Y, edgePoints.StartPoint.Y);
  357. var minEnd = Math.Min(endPoint.Y, edgePoints.EndPoint.Y);
  358. if (maxStart < minEnd && edgePoints.StartPoint.X == startPoint.X + 1)
  359. {
  360. var height1 = endPoint.Y - startPoint.Y;
  361. var height2 = edgePoints.EndPoint.Y - edgePoints.StartPoint.Y;
  362. //如果两段的高都小于5,则不考虑2倍的情况
  363. if (height2 < DiffHeight && height1 < DiffHeight)
  364. {
  365. var closePointNum = minEnd - maxStart;
  366. if (closePointNum > maxClosePointsNum)
  367. {
  368. maxClosePointsNum = closePointNum;
  369. maxIndex = i;
  370. }
  371. }
  372. else
  373. {
  374. //血管高度差不超过2倍
  375. if (height1 / height2 < 2 && height2 / height1 < 2)
  376. {
  377. var closePointNum = minEnd - maxStart;
  378. if (closePointNum > maxClosePointsNum)
  379. {
  380. maxClosePointsNum = closePointNum;
  381. maxIndex = i;
  382. }
  383. }
  384. }
  385. }
  386. }
  387. return maxIndex;
  388. }
  389. catch (Exception e)
  390. {
  391. Logger.WriteLineError("DetectVessel GetTheClosestContours error," + e.Message + "," + e.StackTrace);
  392. return -1;
  393. }
  394. }
  395. /// <summary>
  396. /// 再次去除杂质
  397. /// </summary>
  398. /// <param name="image"></param>
  399. private void RemoveNonVessel(Image<Gray, byte> image, bool isRemoveImpurity, int minVesselH)
  400. {
  401. try
  402. {
  403. //去掉边缘周围的杂质---
  404. //求连通域
  405. var contours = ImageTools.FindContours(image);
  406. var minWidth = image.Width / 5;
  407. if (isRemoveImpurity)
  408. {
  409. //去掉宽度较小的连通域
  410. FillSmallArea(contours, image, rect => rect.Width < minWidth);
  411. }
  412. else
  413. {
  414. //去掉宽度或高度较小的连通域
  415. FillSmallArea(contours, image, rect => rect.Width < minWidth || rect.Height < minVesselH);
  416. }
  417. }
  418. catch (Exception e)
  419. {
  420. Logger.WriteLineError("DetectVessel RemoveNonVessel error," + e.Message + "," + e.StackTrace);
  421. }
  422. }
  423. private void FillSmallArea(VectorOfVectorOfPoint contours, Image<Gray, byte> image, Func<Rectangle, bool> condition)
  424. {
  425. for (var i = 0; i < contours.Size; ++i)
  426. {
  427. var contour = contours[i];
  428. var rect = CvInvoke.BoundingRectangle(contour);
  429. if (condition(rect))
  430. {
  431. var points = contour.ToArray();
  432. if (rect.Height > rect.Width)
  433. {
  434. ImageTools.ChangeImageH(image, points, rect);
  435. }
  436. else
  437. {
  438. ImageTools.ChangeImageW(image, points, rect);
  439. }
  440. }
  441. }
  442. }
  443. }
  444. }