FocusAnalysisEngine.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734
  1. using MathNet.Numerics.Statistics;
  2. using OpenCvSharp;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. using System.Text;
  7. using System.Threading.Tasks;
  8. using TeamAAS_VP.Enums;
  9. using TeamAAS_VP.Models;
  10. namespace TeamAAS_VP.Core
  11. {
  12. /// <summary>
  13. /// 图像清晰度分析引擎
  14. /// </summary>
  15. public class FocusAnalysisEngine
  16. {
  17. // 自定义Clamp方法
  18. private static double Clamp(double value, double min, double max)
  19. {
  20. return value < min ? min : (value > max ? max : value);
  21. }
  22. private static int Clamp(int value, int min, int max)
  23. {
  24. return value < min ? min : (value > max ? max : value);
  25. }
  26. /// <summary>
  27. /// 计算图像清晰度
  28. /// </summary>
  29. public static double CalculateImageSharpness(Mat image, FocusMethod method, Rect? roi = null)
  30. {
  31. if (image == null || image.Empty())
  32. throw new ArgumentException("输入图像无效");
  33. // 转换为灰度图
  34. Mat gray = new Mat();
  35. if (image.Channels() == 3)
  36. Cv2.CvtColor(image, gray, ColorConversionCodes.BGR2GRAY);
  37. else
  38. gray = image.Clone();
  39. // 应用ROI
  40. if (roi.HasValue)
  41. {
  42. gray = new Mat(gray, roi.Value);
  43. }
  44. double sharpness = 0;
  45. switch (method)
  46. {
  47. case FocusMethod.Tenengrad:
  48. sharpness = CalculateTenengrad(gray);
  49. break;
  50. case FocusMethod.Laplacian:
  51. sharpness = CalculateLaplacianVariance(gray);
  52. break;
  53. case FocusMethod.Brenner:
  54. sharpness = CalculateBrenner(gray);
  55. break;
  56. case FocusMethod.GrayVariance:
  57. sharpness = CalculateGrayVariance(gray);
  58. break;
  59. case FocusMethod.SMD:
  60. sharpness = CalculateSMD(gray);
  61. break;
  62. }
  63. gray.Dispose();
  64. return sharpness;
  65. }
  66. private static double CalculateTenengrad(Mat gray)
  67. {
  68. Mat gradX = new Mat();
  69. Mat gradY = new Mat();
  70. Mat magnitude = new Mat();
  71. Cv2.Sobel(gray, gradX, MatType.CV_32F, 1, 0, 3);
  72. Cv2.Sobel(gray, gradY, MatType.CV_32F, 0, 1, 3);
  73. Cv2.Magnitude(gradX, gradY, magnitude);
  74. Scalar mean = Cv2.Mean(magnitude);
  75. gradX.Dispose();
  76. gradY.Dispose();
  77. magnitude.Dispose();
  78. return mean.Val0;
  79. }
  80. private static double CalculateLaplacianVariance(Mat gray)
  81. {
  82. Mat laplacian = new Mat();
  83. Cv2.Laplacian(gray, laplacian, MatType.CV_32F);
  84. Mat mean = new Mat();
  85. Mat stdDev = new Mat();
  86. Cv2.MeanStdDev(laplacian, mean, stdDev);
  87. double variance = stdDev.Get<double>(0);
  88. variance = variance * variance;
  89. laplacian.Dispose();
  90. mean.Dispose();
  91. stdDev.Dispose();
  92. return variance;
  93. }
  94. private static double CalculateBrenner(Mat gray)
  95. {
  96. double sum = 0;
  97. int width = gray.Cols;
  98. int height = gray.Rows;
  99. for (int y = 0; y < height; y++)
  100. {
  101. for (int x = 0; x < width - 2; x++)
  102. {
  103. int diff = gray.Get<byte>(y, x + 2) - gray.Get<byte>(y, x);
  104. sum += diff * diff;
  105. }
  106. }
  107. return sum / (width * height);
  108. }
  109. private static double CalculateGrayVariance(Mat gray)
  110. {
  111. Mat mean = new Mat();
  112. Mat stdDev = new Mat();
  113. Cv2.MeanStdDev(gray, mean, stdDev);
  114. double variance = stdDev.Get<double>(0);
  115. variance = variance * variance;
  116. mean.Dispose();
  117. stdDev.Dispose();
  118. return variance;
  119. }
  120. private static double CalculateSMD(Mat gray)
  121. {
  122. double sum = 0;
  123. int width = gray.Cols;
  124. int height = gray.Rows;
  125. for (int y = 0; y < height; y++)
  126. {
  127. for (int x = 0; x < width - 1; x++)
  128. {
  129. sum += Math.Abs(gray.Get<byte>(y, x + 1) - gray.Get<byte>(y, x));
  130. }
  131. }
  132. for (int y = 0; y < height - 1; y++)
  133. {
  134. for (int x = 0; x < width; x++)
  135. {
  136. sum += Math.Abs(gray.Get<byte>(y + 1, x) - gray.Get<byte>(y, x));
  137. }
  138. }
  139. return sum / (width * height);
  140. }
  141. /// <summary>
  142. /// 分析棋盘格对比度
  143. /// </summary>
  144. public static CheckerboardResult AnalyzeCheckerboardContrast(Mat image, Rect? roi = null)
  145. {
  146. var result = new CheckerboardResult();
  147. try
  148. {
  149. // 转换为灰度图
  150. Mat gray = new Mat();
  151. if (image.Channels() == 3)
  152. Cv2.CvtColor(image, gray, ColorConversionCodes.BGR2GRAY);
  153. else
  154. gray = image.Clone();
  155. // 应用ROI
  156. if (roi.HasValue)
  157. {
  158. gray = new Mat(gray, roi.Value);
  159. }
  160. // 简化版的棋盘格检测
  161. bool detected = TryDetectCheckerboard(gray, out double blackMean, out double whiteMean);
  162. if (detected)
  163. {
  164. result.Detected = true;
  165. result.BlackMean = blackMean;
  166. result.WhiteMean = whiteMean;
  167. result.Contrast = Math.Abs(whiteMean - blackMean);
  168. result.OptimalBrightness = CalculateOptimalBrightness(blackMean, whiteMean);
  169. }
  170. gray.Dispose();
  171. }
  172. catch (Exception ex)
  173. {
  174. Console.WriteLine($"棋盘格分析出错: {ex.Message}");
  175. }
  176. return result;
  177. }
  178. private static bool TryDetectCheckerboard(Mat gray, out double blackMean, out double whiteMean)
  179. {
  180. blackMean = 0;
  181. whiteMean = 0;
  182. try
  183. {
  184. // 使用自适应阈值进行二值化
  185. Mat binary = new Mat();
  186. Cv2.AdaptiveThreshold(gray, binary, 255,
  187. AdaptiveThresholdTypes.GaussianC,
  188. ThresholdTypes.Binary, 11, 2);
  189. // 查找轮廓
  190. var contours = Cv2.FindContoursAsArray(binary,
  191. RetrievalModes.External,
  192. ContourApproximationModes.ApproxSimple);
  193. // 寻找近似矩形的轮廓(可能是棋盘格)
  194. var rectangles = new List<Rect>();
  195. foreach (var contour in contours)
  196. {
  197. var poly = Cv2.ApproxPolyDP(contour, 0.02 * Cv2.ArcLength(contour, true), true);
  198. if (poly.Length == 4) // 四边形
  199. {
  200. var rect = Cv2.BoundingRect(contour);
  201. if (rect.Width > 50 && rect.Height > 50) // 忽略太小的区域
  202. {
  203. rectangles.Add(rect);
  204. }
  205. }
  206. }
  207. if (rectangles.Count >= 2)
  208. {
  209. // 采样黑色和白色区域
  210. blackMean = SampleRegionMean(gray, rectangles[0]);
  211. whiteMean = SampleRegionMean(gray, rectangles.Count > 1 ? rectangles[1] : rectangles[0]);
  212. // 确保黑色比白色暗
  213. if (blackMean > whiteMean)
  214. {
  215. double temp = blackMean;
  216. blackMean = whiteMean;
  217. whiteMean = temp;
  218. }
  219. return true;
  220. }
  221. binary.Dispose();
  222. }
  223. catch
  224. {
  225. // 如果检测失败,返回false
  226. }
  227. return false;
  228. }
  229. private static double SampleRegionMean(Mat gray, Rect region)
  230. {
  231. var sample = new Mat(gray, region);
  232. Scalar mean = Cv2.Mean(sample);
  233. sample.Dispose();
  234. return mean.Val0;
  235. }
  236. private static int CalculateOptimalBrightness(double blackMean, double whiteMean)
  237. {
  238. double currentMid = (blackMean + whiteMean) / 2;
  239. return (int)Clamp(128 + (128 - currentMid), 30, 225);
  240. }
  241. /// <summary>
  242. /// 计算图像质量评分
  243. /// </summary>
  244. public static double CalculateQualityScore(double sharpness, CheckerboardResult checkerboard)
  245. {
  246. // 清晰度评分(0-50分)
  247. double sharpnessScore = Clamp(sharpness / 0.5, 0, 50);
  248. // 对比度评分(0-50分)
  249. double contrastScore = 0;
  250. if (checkerboard.Detected)
  251. {
  252. contrastScore = Clamp(checkerboard.Contrast / 2, 0, 50);
  253. }
  254. return sharpnessScore + contrastScore;
  255. }
  256. /// <summary>
  257. /// 生成建议
  258. /// </summary>
  259. public static List<string> GenerateSuggestions(double sharpness, CheckerboardResult checkerboard,
  260. double sharpnessThreshold, double contrastThreshold)
  261. {
  262. var suggestions = new List<string>();
  263. // 清晰度建议
  264. if (sharpness < sharpnessThreshold)
  265. suggestions.Add("图像模糊,请调整焦距使图像变清晰");
  266. else if (sharpness < sharpnessThreshold * 2)
  267. suggestions.Add("清晰度一般,可以继续微调焦距");
  268. else
  269. suggestions.Add("图像清晰度良好");
  270. // 棋盘格建议
  271. if (checkerboard.Detected)
  272. {
  273. if (checkerboard.Contrast < contrastThreshold)
  274. suggestions.Add("棋盘格对比度过低,建议调整光源");
  275. else if (checkerboard.Contrast < contrastThreshold * 2)
  276. suggestions.Add("棋盘格对比度适中");
  277. else
  278. suggestions.Add("棋盘格对比度很好");
  279. suggestions.Add($"建议亮度值: {checkerboard.OptimalBrightness}");
  280. }
  281. else
  282. {
  283. suggestions.Add("未检测到棋盘格,请确保棋盘格在视野中");
  284. }
  285. return suggestions;
  286. }
  287. /// <summary>
  288. /// 使用MathNet计算统计信息
  289. /// </summary>
  290. public static StatisticalSummary CalculateStatistics(List<double> data)
  291. {
  292. if (data == null || data.Count == 0)
  293. return new StatisticalSummary();
  294. // 使用MathNet.Numerics.Statistics计算
  295. var stats = MathNet.Numerics.Statistics.Statistics.MeanVariance(data);
  296. return new StatisticalSummary
  297. {
  298. Mean = stats.Item1, // 均值
  299. Variance = stats.Item2, // 方差
  300. StdDev = Math.Sqrt(stats.Item2), // 标准差
  301. Min = data.Min(),
  302. Max = data.Max(),
  303. Count = data.Count
  304. };
  305. }
  306. /// <summary>
  307. /// 使用MathNet计算更详细的统计信息
  308. /// </summary>
  309. public static StatisticalSummary CalculateDetailedStatistics(List<double> data)
  310. {
  311. if (data == null || data.Count == 0)
  312. return new StatisticalSummary();
  313. // 使用DescriptiveStatistics获取更多统计信息
  314. var descriptiveStats = new MathNet.Numerics.Statistics.DescriptiveStatistics(data);
  315. return new StatisticalSummary
  316. {
  317. Mean = descriptiveStats.Mean,
  318. Variance = descriptiveStats.Variance,
  319. StdDev = descriptiveStats.StandardDeviation,
  320. Min = descriptiveStats.Minimum,
  321. Max = descriptiveStats.Maximum,
  322. Count = data.Count,
  323. // 还可以添加更多统计信息
  324. Skewness = descriptiveStats.Skewness,
  325. Kurtosis = descriptiveStats.Kurtosis
  326. };
  327. }
  328. /// <summary>
  329. /// 计算移动平均
  330. /// </summary>
  331. public static List<double> CalculateMovingAverage(List<double> data, int windowSize)
  332. {
  333. if (data == null || data.Count == 0 || windowSize <= 0)
  334. return new List<double>();
  335. var result = new List<double>();
  336. for (int i = 0; i < data.Count; i++)
  337. {
  338. int start = Math.Max(0, i - windowSize + 1);
  339. int count = Math.Min(windowSize, i + 1);
  340. double sum = 0;
  341. for (int j = start; j <= i; j++)
  342. {
  343. sum += data[j];
  344. }
  345. result.Add(sum / count);
  346. }
  347. return result;
  348. }
  349. /// <summary>
  350. /// 计算指数移动平均
  351. /// </summary>
  352. public static List<double> CalculateExponentialMovingAverage(List<double> data, double alpha)
  353. {
  354. if (data == null || data.Count == 0 || alpha <= 0 || alpha > 1)
  355. return new List<double>();
  356. var result = new List<double> { data[0] };
  357. for (int i = 1; i < data.Count; i++)
  358. {
  359. double ema = alpha * data[i] + (1 - alpha) * result[i - 1];
  360. result.Add(ema);
  361. }
  362. return result;
  363. }
  364. /// <summary>
  365. /// 检测峰值
  366. /// </summary>
  367. public static List<int> DetectPeaks(List<double> data, double threshold = 0.5)
  368. {
  369. var peaks = new List<int>();
  370. if (data == null || data.Count < 3)
  371. return peaks;
  372. var stats = CalculateStatistics(data);
  373. double mean = stats.Mean;
  374. double stdDev = stats.StdDev;
  375. for (int i = 1; i < data.Count - 1; i++)
  376. {
  377. if (data[i] > data[i - 1] && data[i] > data[i + 1])
  378. {
  379. // 峰值需要超过阈值
  380. if (data[i] > mean + threshold * stdDev)
  381. {
  382. peaks.Add(i);
  383. }
  384. }
  385. }
  386. return peaks;
  387. }
  388. /// <summary>
  389. /// 计算趋势线(线性回归)
  390. /// </summary>
  391. public static (double slope, double intercept) CalculateTrendLine(List<double> data)
  392. {
  393. if (data == null || data.Count == 0)
  394. return (0, 0);
  395. var xData = Enumerable.Range(0, data.Count).Select(x => (double)x).ToArray();
  396. var yData = data.ToArray();
  397. // 使用MathNet进行线性回归
  398. var fit = MathNet.Numerics.Fit.Line(xData, yData);
  399. return (fit.Item2, fit.Item1); // slope, intercept
  400. }
  401. /// <summary>
  402. /// 计算自相关性
  403. /// </summary>
  404. public static List<double> CalculateAutocorrelation(List<double> data, int maxLag = 20)
  405. {
  406. if (data == null || data.Count == 0)
  407. return new List<double>();
  408. var autocorr = new List<double>();
  409. var stats = CalculateStatistics(data);
  410. double mean = stats.Mean;
  411. double variance = stats.Variance;
  412. if (variance == 0) return autocorr;
  413. int n = data.Count;
  414. maxLag = Math.Min(maxLag, n - 1);
  415. for (int lag = 0; lag <= maxLag; lag++)
  416. {
  417. double sum = 0;
  418. for (int i = 0; i < n - lag; i++)
  419. {
  420. sum += (data[i] - mean) * (data[i + lag] - mean);
  421. }
  422. autocorr.Add(sum / ((n - lag) * variance));
  423. }
  424. return autocorr;
  425. }
  426. }
  427. /// <summary>
  428. /// 扩展的统计摘要类
  429. /// </summary>
  430. public class StatisticalSummary
  431. {
  432. public double Mean { get; set; }
  433. public double Variance { get; set; }
  434. public double StdDev { get; set; }
  435. public double Min { get; set; }
  436. public double Max { get; set; }
  437. public int Count { get; set; }
  438. // 扩展的统计信息
  439. public double Skewness { get; set; } // 偏度
  440. public double Kurtosis { get; set; } // 峰度
  441. // 分位数(可选)
  442. public double Median { get; set; }
  443. public double Q1 { get; set; } // 第一四分位数
  444. public double Q3 { get; set; } // 第三四分位数
  445. public double IQR => Q3 - Q1; // 四分位距
  446. /// <summary>
  447. /// 计算分位数
  448. /// </summary>
  449. public void CalculateQuantiles(List<double> data)
  450. {
  451. if (data == null || data.Count == 0)
  452. return;
  453. var sortedData = data.OrderBy(x => x).ToList();
  454. // 中位数
  455. Median = MathNet.Numerics.Statistics.Statistics.Median(sortedData);
  456. // 四分位数
  457. Q1 = MathNet.Numerics.Statistics.Statistics.Quantile(sortedData, 0.25);
  458. Q3 = MathNet.Numerics.Statistics.Statistics.Quantile(sortedData, 0.75);
  459. }
  460. /// <summary>
  461. /// 生成统计摘要字符串
  462. /// </summary>
  463. public string ToSummaryString()
  464. {
  465. return $@"统计摘要:
  466. 样本数量: {Count}
  467. 均值: {Mean:F3}
  468. 标准差: {StdDev:F3}
  469. 最小值: {Min:F3}
  470. 最大值: {Max:F3}
  471. 范围: {Max - Min:F3}
  472. 方差: {Variance:F3}
  473. 偏度: {Skewness:F3}
  474. 峰度: {Kurtosis:F3}";
  475. }
  476. /// <summary>
  477. /// 获取详细统计信息(包含分位数)
  478. /// </summary>
  479. public string ToDetailedString()
  480. {
  481. return $@"详细统计信息:
  482. 样本数量: {Count}
  483. 均值: {Mean:F3} ± {StdDev:F3}
  484. 中位数: {Median:F3}
  485. 第一四分位数(Q1): {Q1:F3}
  486. 第三四分位数(Q3): {Q3:F3}
  487. 四分位距(IQR): {IQR:F3}
  488. 最小值: {Min:F3}
  489. 最大值: {Max:F3}
  490. 范围: {Max - Min:F3}
  491. 方差: {Variance:F3}
  492. 标准差: {StdDev:F3}
  493. 变异系数: {(StdDev / Mean * 100):F1}%
  494. 偏度: {Skewness:F3}
  495. 峰度: {Kurtosis:F3}";
  496. }
  497. }
  498. /// <summary>
  499. /// 分析结果增强类
  500. /// </summary>
  501. public class EnhancedAnalysisResult : AnalysisResult
  502. {
  503. public StatisticalSummary SharpnessStats { get; set; }
  504. public StatisticalSummary ContrastStats { get; set; }
  505. public List<int> SharpnessPeaks { get; set; }
  506. public double TrendSlope { get; set; }
  507. public double TrendIntercept { get; set; }
  508. public double AutocorrelationAtLag1 { get; set; }
  509. public EnhancedAnalysisResult()
  510. {
  511. SharpnessStats = new StatisticalSummary();
  512. ContrastStats = new StatisticalSummary();
  513. SharpnessPeaks = new List<int>();
  514. }
  515. }
  516. /// <summary>
  517. /// 增强的分析引擎
  518. /// </summary>
  519. public static class EnhancedFocusAnalysisEngine
  520. {
  521. /// <summary>
  522. /// 执行增强分析
  523. /// </summary>
  524. public static EnhancedAnalysisResult PerformEnhancedAnalysis(
  525. List<DataPoint> historyData,
  526. AnalysisResult baseResult)
  527. {
  528. var enhancedResult = new EnhancedAnalysisResult
  529. {
  530. // 复制基础结果
  531. Sharpness = baseResult.Sharpness,
  532. Checkerboard = baseResult.Checkerboard,
  533. QualityScore = baseResult.QualityScore,
  534. FrameCount = baseResult.FrameCount,
  535. BestSharpness = baseResult.BestSharpness,
  536. AnalysisTime = baseResult.AnalysisTime,
  537. Suggestions = baseResult.Suggestions
  538. };
  539. if (historyData == null || historyData.Count == 0)
  540. return enhancedResult;
  541. // 提取清晰度和对比度数据
  542. var sharpnessData = historyData.Select(d => d.Sharpness).ToList();
  543. var contrastData = historyData
  544. .Where(d => d.Contrast > 0)
  545. .Select(d => d.Contrast)
  546. .ToList();
  547. // 计算统计信息
  548. enhancedResult.SharpnessStats = FocusAnalysisEngine.CalculateDetailedStatistics(sharpnessData);
  549. if (contrastData.Count > 0)
  550. {
  551. enhancedResult.ContrastStats = FocusAnalysisEngine.CalculateDetailedStatistics(contrastData);
  552. }
  553. // 计算分位数
  554. enhancedResult.SharpnessStats.CalculateQuantiles(sharpnessData);
  555. // 检测峰值
  556. enhancedResult.SharpnessPeaks = FocusAnalysisEngine.DetectPeaks(sharpnessData, 1.0);
  557. // 计算趋势线
  558. var trend = FocusAnalysisEngine.CalculateTrendLine(sharpnessData);
  559. enhancedResult.TrendSlope = trend.slope;
  560. enhancedResult.TrendIntercept = trend.intercept;
  561. // 计算自相关性
  562. var autocorr = FocusAnalysisEngine.CalculateAutocorrelation(sharpnessData, 1);
  563. if (autocorr.Count > 1)
  564. {
  565. enhancedResult.AutocorrelationAtLag1 = autocorr[1];
  566. }
  567. return enhancedResult;
  568. }
  569. /// <summary>
  570. /// 生成增强分析报告
  571. /// </summary>
  572. public static string GenerateEnhancedReport(EnhancedAnalysisResult result)
  573. {
  574. var report = new System.Text.StringBuilder();
  575. report.AppendLine("=== 增强分析报告 ===");
  576. report.AppendLine($"分析时间: {result.AnalysisTime:yyyy-MM-dd HH:mm:ss}");
  577. report.AppendLine($"总帧数: {result.FrameCount}");
  578. report.AppendLine();
  579. report.AppendLine("一、清晰度统计分析");
  580. report.AppendLine(result.SharpnessStats.ToDetailedString());
  581. report.AppendLine();
  582. report.AppendLine("二、趋势分析");
  583. report.AppendLine($"趋势线斜率: {result.TrendSlope:F6}");
  584. report.AppendLine($"趋势线截距: {result.TrendIntercept:F3}");
  585. report.AppendLine($"自相关性(lag=1): {result.AutocorrelationAtLag1:F3}");
  586. report.AppendLine($"检测到峰值数量: {result.SharpnessPeaks.Count}");
  587. if (result.SharpnessPeaks.Any())
  588. {
  589. report.Append("峰值位置(帧): ");
  590. report.AppendLine(string.Join(", ", result.SharpnessPeaks));
  591. }
  592. report.AppendLine();
  593. if (result.ContrastStats.Count > 0)
  594. {
  595. report.AppendLine("三、对比度统计分析");
  596. report.AppendLine(result.ContrastStats.ToSummaryString());
  597. report.AppendLine();
  598. }
  599. report.AppendLine("四、分析建议");
  600. if (result.SharpnessPeaks.Count > 0)
  601. {
  602. int lastPeak = result.SharpnessPeaks.Last();
  603. report.AppendLine($"发现清晰度峰值,最后峰值在第 {lastPeak + 1} 帧");
  604. if (result.TrendSlope < -0.01)
  605. report.AppendLine("清晰度呈下降趋势,可能已过最佳对焦点");
  606. else if (result.TrendSlope > 0.01)
  607. report.AppendLine("清晰度呈上升趋势,可继续当前方向调整");
  608. else
  609. report.AppendLine("清晰度趋势平稳");
  610. }
  611. if (Math.Abs(result.AutocorrelationAtLag1) > 0.5)
  612. {
  613. report.AppendLine("数据具有较强自相关性,表明调整过程平稳");
  614. }
  615. return report.ToString();
  616. }
  617. }
  618. }