ImageAnalysisService.cs 9.7 KB

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