ImageAnalysisService.cs 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. using TeamAAS_VP.Enums;
  2. using TeamAAS_VP.Models;
  3. using OpenCvSharp;
  4. using System;
  5. namespace TeamAAS_VP.Services
  6. {
  7. /// <summary>
  8. /// 图像分析服务
  9. /// </summary>
  10. public class ImageAnalysisService
  11. {
  12. /// <summary>
  13. /// 分析图像清晰度和对比度
  14. /// </summary>
  15. public AnalysisResult AnalyzeImage(Mat image, FocusMethod method, Rect? roi = null)
  16. {
  17. var result = new AnalysisResult { Timestamp = DateTime.Now };
  18. if (image == null || image.Empty())
  19. {
  20. result.Message = "图像为空";
  21. return result;
  22. }
  23. try
  24. {
  25. Mat analyzeMat = image;
  26. // 如果指定了ROI,则裁剪图像
  27. if (roi.HasValue && roi.Value.Width > 0 && roi.Value.Height > 0)
  28. {
  29. var roiRect = roi.Value;
  30. // 确保ROI在图像范围内
  31. roiRect.X = Math.Max(0, Math.Min(roiRect.X, image.Width - 1));
  32. roiRect.Y = Math.Max(0, Math.Min(roiRect.Y, image.Height - 1));
  33. roiRect.Width = Math.Min(roiRect.Width, image.Width - roiRect.X);
  34. roiRect.Height = Math.Min(roiRect.Height, image.Height - roiRect.Y);
  35. if (roiRect.Width > 0 && roiRect.Height > 0)
  36. {
  37. analyzeMat = new Mat(image, roiRect);
  38. }
  39. }
  40. // 转换为灰度图
  41. Mat gray = new Mat();
  42. if (analyzeMat.Channels() == 3)
  43. {
  44. Cv2.CvtColor(analyzeMat, gray, ColorConversionCodes.BGR2GRAY);
  45. }
  46. else
  47. {
  48. gray = analyzeMat.Clone();
  49. }
  50. // 计算清晰度
  51. result.Sharpness = CalculateSharpness(gray, method);
  52. // 计算对比度
  53. result.Contrast = CalculateContrast(gray);
  54. // 计算质量评分 (0-100)
  55. result.QualityScore = CalculateQualityScore(result.Sharpness, result.Contrast, method);
  56. result.Message = "分析完成";
  57. gray?.Dispose();
  58. if (roi.HasValue && analyzeMat != image)
  59. {
  60. analyzeMat?.Dispose();
  61. }
  62. }
  63. catch (Exception ex)
  64. {
  65. result.Message = $"分析错误: {ex.Message}";
  66. }
  67. return result;
  68. }
  69. /// <summary>
  70. /// 计算清晰度
  71. /// </summary>
  72. private double CalculateSharpness(Mat gray, FocusMethod method)
  73. {
  74. switch (method)
  75. {
  76. case FocusMethod.Laplacian:
  77. return CalculateLaplacianVariance(gray);
  78. case FocusMethod.Sobel:
  79. return CalculateSobelVariance(gray);
  80. case FocusMethod.Variance:
  81. return CalculateImageVariance(gray);
  82. case FocusMethod.Tenengrad:
  83. return CalculateTenengrad(gray);
  84. case FocusMethod.FrequencyDomain:
  85. return CalculateFrequencyDomain(gray);
  86. default:
  87. return CalculateLaplacianVariance(gray);
  88. }
  89. }
  90. /// <summary>
  91. /// Laplacian方差法
  92. /// </summary>
  93. private double CalculateLaplacianVariance(Mat gray)
  94. {
  95. using (var laplacian = new Mat())
  96. {
  97. Cv2.Laplacian(gray, laplacian, MatType.CV_64F);
  98. Cv2.MeanStdDev(laplacian, out _, out Scalar stddev);
  99. return stddev.Val0 * stddev.Val0;
  100. }
  101. }
  102. /// <summary>
  103. /// Sobel方差法
  104. /// </summary>
  105. private double CalculateSobelVariance(Mat gray)
  106. {
  107. using (var sobelX = new Mat())
  108. using (var sobelY = new Mat())
  109. using (var sobel = new Mat())
  110. {
  111. Cv2.Sobel(gray, sobelX, MatType.CV_64F, 1, 0, 3);
  112. Cv2.Sobel(gray, sobelY, MatType.CV_64F, 0, 1, 3);
  113. Cv2.Magnitude(sobelX, sobelY, sobel);
  114. return Cv2.Mean(sobel).Val0;
  115. }
  116. }
  117. /// <summary>
  118. /// 图像方差法
  119. /// </summary>
  120. private double CalculateImageVariance(Mat gray)
  121. {
  122. Cv2.MeanStdDev(gray, out _, out Scalar stddev);
  123. return stddev.Val0 * stddev.Val0;
  124. }
  125. /// <summary>
  126. /// Tenengrad算子
  127. /// </summary>
  128. private double CalculateTenengrad(Mat gray)
  129. {
  130. using (var sobelX = new Mat())
  131. using (var sobelY = new Mat())
  132. {
  133. Cv2.Sobel(gray, sobelX, MatType.CV_64F, 1, 0, 3);
  134. Cv2.Sobel(gray, sobelY, MatType.CV_64F, 0, 1, 3);
  135. double sum = 0;
  136. for (int y = 0; y < gray.Rows; y++)
  137. {
  138. for (int x = 0; x < gray.Cols; x++)
  139. {
  140. double gx = sobelX.At<double>(y, x);
  141. double gy = sobelY.At<double>(y, x);
  142. double magnitude = Math.Sqrt(gx * gx + gy * gy);
  143. // 只计算超过阈值的梯度
  144. if (magnitude > 50)
  145. {
  146. sum += magnitude * magnitude;
  147. }
  148. }
  149. }
  150. return sum / (gray.Rows * gray.Cols);
  151. }
  152. }
  153. /// <summary>
  154. /// 频域分析法
  155. /// </summary>
  156. private double CalculateFrequencyDomain(Mat gray)
  157. {
  158. try
  159. {
  160. // 转换为浮点型
  161. Mat floatMat = new Mat();
  162. gray.ConvertTo(floatMat, MatType.CV_32F);
  163. // 执行DFT
  164. Mat dft = new Mat();
  165. Cv2.Dft(floatMat, dft, DftFlags.ComplexOutput);
  166. // 计算幅度谱
  167. Mat[] planes = Cv2.Split(dft);
  168. Mat magnitude = new Mat();
  169. Cv2.Magnitude(planes[0], planes[1], magnitude);
  170. // 计算高频能量占比
  171. int centerX = magnitude.Cols / 2;
  172. int centerY = magnitude.Rows / 2;
  173. int radius = Math.Min(centerX, centerY) / 3;
  174. double totalEnergy = Cv2.Sum(magnitude).Val0;
  175. // 屏蔽低频区域
  176. Cv2.Circle(magnitude, new Point(centerX, centerY), radius, Scalar.All(0), -1);
  177. double highFreqEnergy = Cv2.Sum(magnitude).Val0;
  178. floatMat?.Dispose();
  179. dft?.Dispose();
  180. foreach (var plane in planes) plane?.Dispose();
  181. magnitude?.Dispose();
  182. return totalEnergy > 0 ? (highFreqEnergy / totalEnergy) * 100 : 0;
  183. }
  184. catch
  185. {
  186. return 0;
  187. }
  188. }
  189. /// <summary>
  190. /// 计算对比度
  191. /// </summary>
  192. private double CalculateContrast(Mat gray)
  193. {
  194. try
  195. {
  196. // 使用标准差作为对比度指标
  197. Cv2.MeanStdDev(gray, out Scalar mean, out Scalar stddev);
  198. // 归一化对比度 (0-100)
  199. double contrast = (stddev.Val0 / 128.0) * 100;
  200. return Math.Min(100, contrast);
  201. }
  202. catch
  203. {
  204. return 0;
  205. }
  206. }
  207. /// <summary>
  208. /// 计算质量评分
  209. /// </summary>
  210. private double CalculateQualityScore(double sharpness, double contrast, FocusMethod method)
  211. {
  212. // 根据不同的算法,设置不同的归一化参数
  213. double normalizedSharpness = 0;
  214. switch (method)
  215. {
  216. case FocusMethod.Laplacian:
  217. normalizedSharpness = Math.Min(100, (sharpness / 50.0) * 100);
  218. break;
  219. case FocusMethod.Sobel:
  220. normalizedSharpness = Math.Min(100, (sharpness / 30.0) * 100);
  221. break;
  222. case FocusMethod.Variance:
  223. normalizedSharpness = Math.Min(100, (sharpness / 5000.0) * 100);
  224. break;
  225. case FocusMethod.Tenengrad:
  226. normalizedSharpness = Math.Min(100, (sharpness / 1000.0) * 100);
  227. break;
  228. case FocusMethod.FrequencyDomain:
  229. normalizedSharpness = sharpness;
  230. break;
  231. }
  232. // 综合清晰度和对比度
  233. double score = (normalizedSharpness * 0.7 + contrast * 0.3);
  234. return Math.Min(100, Math.Max(0, score));
  235. }
  236. /// <summary>
  237. /// 获取ROI区域图像
  238. /// </summary>
  239. public Mat GetRoiImage(Mat image, Rect roi)
  240. {
  241. if (image == null || image.Empty())
  242. return null;
  243. try
  244. {
  245. // 确保ROI在图像范围内
  246. roi.X = Math.Max(0, Math.Min(roi.X, image.Width - 1));
  247. roi.Y = Math.Max(0, Math.Min(roi.Y, image.Height - 1));
  248. roi.Width = Math.Min(roi.Width, image.Width - roi.X);
  249. roi.Height = Math.Min(roi.Height, image.Height - roi.Y);
  250. if (roi.Width > 0 && roi.Height > 0)
  251. {
  252. return new Mat(image, roi).Clone();
  253. }
  254. }
  255. catch (Exception ex)
  256. {
  257. Console.WriteLine($"获取ROI图像错误: {ex.Message}");
  258. }
  259. return null;
  260. }
  261. }
  262. }