| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678 |
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
- using OpenCvSharp;
- namespace TeamAAS_VP.Core
- {
- public class CameraFocusAnalyzer
- {
- // 清晰度评估方法枚举
- public enum FocusMethod
- {
- Tenengrad, // Tenengrad梯度法(推荐)
- Laplacian, // 拉普拉斯方差法
- Brenner, // Brenner梯度法
- GrayVariance, // 灰度方差法
- SMD // 灰度差分法
- }
- /// <summary>
- /// 计算图像清晰度(多种方法)
- /// </summary>
- public static double CalculateImageSharpness(Mat image, FocusMethod method = FocusMethod.Tenengrad, Rect? roi = null)
- {
- if (image == null || image.Empty())
- throw new ArgumentException("输入图像无效");
- // 转换为灰度图
- Mat gray = new Mat();
- if (image.Channels() == 3)
- Cv2.CvtColor(image, gray, ColorConversionCodes.BGR2GRAY);
- else
- gray = image.Clone();
- // 应用ROI
- if (roi.HasValue)
- {
- gray = new Mat(gray, roi.Value);
- }
- double sharpness = 0;
- switch (method)
- {
- case FocusMethod.Tenengrad:
- sharpness = CalculateTenengrad(gray);
- break;
- case FocusMethod.Laplacian:
- sharpness = CalculateLaplacianVariance(gray);
- break;
- case FocusMethod.Brenner:
- sharpness = CalculateBrenner(gray);
- break;
- case FocusMethod.GrayVariance:
- sharpness = CalculateGrayVariance(gray);
- break;
- case FocusMethod.SMD:
- sharpness = CalculateSMD(gray);
- break;
- }
- gray.Dispose();
- return sharpness;
- }
- /// <summary>
- /// Tenengrad梯度法(对噪声敏感度较低)
- /// </summary>
- private static double CalculateTenengrad(Mat gray)
- {
- Mat gradX = new Mat();
- Mat gradY = new Mat();
- Mat magnitude = new Mat();
- Cv2.Sobel(gray, gradX, MatType.CV_32F, 1, 0, 3);
- Cv2.Sobel(gray, gradY, MatType.CV_32F, 0, 1, 3);
- Cv2.Magnitude(gradX, gradY, magnitude);
- Scalar mean = Cv2.Mean(magnitude);
- gradX.Dispose();
- gradY.Dispose();
- magnitude.Dispose();
- return mean.Val0;
- }
- /// <summary>
- /// 拉普拉斯方差法(对边缘敏感)
- /// </summary>
- private static double CalculateLaplacianVariance(Mat gray)
- {
- Mat laplacian = new Mat();
- Cv2.Laplacian(gray, laplacian, MatType.CV_32F);
- Mat mean = new Mat();
- Mat stdDev = new Mat();
- Cv2.MeanStdDev(laplacian, mean, stdDev);
- double variance = stdDev.Get<double>(0);
- variance = variance * variance;
- laplacian.Dispose();
- mean.Dispose();
- stdDev.Dispose();
- return variance;
- }
- /// <summary>
- /// Brenner梯度法(简单快速)
- /// </summary>
- private static double CalculateBrenner(Mat gray)
- {
- double sum = 0;
- int width = gray.Cols;
- int height = gray.Rows;
- for (int y = 0; y < height; y++)
- {
- for (int x = 0; x < width - 2; x++)
- {
- int diff = gray.Get<byte>(y, x + 2) - gray.Get<byte>(y, x);
- sum += diff * diff;
- }
- }
- return sum / (width * height);
- }
- /// <summary>
- /// 灰度方差法
- /// </summary>
- private static double CalculateGrayVariance(Mat gray)
- {
- Mat mean = new Mat();
- Mat stdDev = new Mat();
- Cv2.MeanStdDev(gray, mean, stdDev);
- double variance = stdDev.Get<double>(0);
- variance = variance * variance;
- mean.Dispose();
- stdDev.Dispose();
- return variance;
- }
- /// <summary>
- /// 灰度差分法(SMD)
- /// </summary>
- private static double CalculateSMD(Mat gray)
- {
- double sum = 0;
- int width = gray.Cols;
- int height = gray.Rows;
- for (int y = 0; y < height; y++)
- {
- for (int x = 0; x < width - 1; x++)
- {
- sum += Math.Abs(gray.Get<byte>(y, x + 1) - gray.Get<byte>(y, x));
- }
- }
- for (int y = 0; y < height - 1; y++)
- {
- for (int x = 0; x < width; x++)
- {
- sum += Math.Abs(gray.Get<byte>(y + 1, x) - gray.Get<byte>(y, x));
- }
- }
- return sum / (width * height);
- }
- /// <summary>
- /// 智能棋盘格对比度分析(简化版)
- /// </summary>
- /// <param name="image">输入图像</param>
- /// <param name="roi">检测区域(可选)</param>
- /// <returns>对比度分析结果,包含黑白均值、对比度和亮度建议</returns>
- public static CheckerboardContrastResult AnalyzeCheckerboardContrast(Mat image, Rect? roi = null)
- {
- var result = new CheckerboardContrastResult
- {
- Success = false,
- BlackMean = 0,
- WhiteMean = 0,
- Contrast = 0,
- OptimalBrightness = 128
- };
- if (image == null || image.Empty())
- return result;
- try
- {
- // 转换为灰度图
- Mat gray = new Mat();
- if (image.Channels() == 3)
- Cv2.CvtColor(image, gray, ColorConversionCodes.BGR2GRAY);
- else
- gray = image.Clone();
- // 应用ROI
- if (roi.HasValue)
- {
- gray = new Mat(gray, roi.Value);
- }
- // 1. 自适应检测棋盘格角点
- var corners = DetectCheckerboardCorners(gray);
- if (corners == null || corners.Length < 20) // 至少需要一定数量的角点
- {
- gray.Dispose();
- return result;
- }
- // 2. 自动分析网格结构
- var gridInfo = AnalyzeGridStructure(corners);
- if (!gridInfo.IsValid)
- {
- gray.Dispose();
- return result;
- }
- // 3. 采样黑白格子像素
- SampleCheckerboardColors(gray, corners, gridInfo,
- out double blackMean, out double whiteMean, out int sampleCount);
- if (sampleCount > 0)
- {
- result.BlackMean = blackMean;
- result.WhiteMean = whiteMean;
- result.Contrast = Math.Abs(whiteMean - blackMean);
- result.OptimalBrightness = CalculateOptimalBrightness(blackMean, whiteMean);
- result.Success = true;
- // 计算图像质量评分
- result.QualityScore = CalculateQualityScore(result.Contrast, blackMean, whiteMean);
- }
- gray.Dispose();
- return result;
- }
- catch (Exception ex)
- {
- Console.WriteLine($"棋盘格分析出错: {ex.Message}");
- return result;
- }
- }
- /// <summary>
- /// 自适应检测棋盘格角点
- /// </summary>
- private static Point2f[] DetectCheckerboardCorners(Mat gray)
- {
- // 首先尝试标准的棋盘格检测
- for (int boardWidth = 5; boardWidth <= 15; boardWidth++)
- {
- for (int boardHeight = 5; boardHeight <= 15; boardHeight++)
- {
- Size patternSize = new Size(boardWidth, boardHeight);
- Point2f[] corners;
- bool found = Cv2.FindChessboardCorners(gray, patternSize, out corners,
- ChessboardFlags.AdaptiveThresh |
- ChessboardFlags.NormalizeImage |
- ChessboardFlags.FastCheck);
- if (found && corners.Length > 0)
- {
- // 提高角点精度
- Mat grayFloat = new Mat();
- gray.ConvertTo(grayFloat, MatType.CV_32F);
- Cv2.CornerSubPix(grayFloat, corners, new Size(5, 5), new Size(-1, -1),
- new TermCriteria(CriteriaTypes.Eps | CriteriaTypes.MaxIter, 30, 0.1));
- grayFloat.Dispose();
- return corners;
- }
- }
- }
- // 如果标准方法失败,尝试使用角点检测
- return DetectCornersByHarris(gray);
- }
- /// <summary>
- /// 使用Harris角点检测作为备选方案
- /// </summary>
- private static Point2f[] DetectCornersByHarris(Mat gray)
- {
- Mat cornersMat = new Mat();
- Mat grayFloat = new Mat();
- gray.ConvertTo(grayFloat, MatType.CV_32F);
- // Harris角点检测
- Cv2.CornerHarris(grayFloat, cornersMat, 2, 3, 0.04);
- // 找到局部最大值
- var corners = new List<Point2f>();
- double maxVal = 0;
- Cv2.MinMaxLoc(cornersMat, out _, out maxVal);
- float threshold = (float)(maxVal * 0.01);
- for (int y = 0; y < cornersMat.Rows; y++)
- {
- for (int x = 0; x < cornersMat.Cols; x++)
- {
- float value = cornersMat.Get<float>(y, x);
- if (value > threshold)
- {
- // 检查是否是局部最大值
- bool isLocalMax = true;
- int neighborhood = 5;
- for (int dy = -neighborhood; dy <= neighborhood && isLocalMax; dy++)
- {
- for (int dx = -neighborhood; dx <= neighborhood && isLocalMax; dx++)
- {
- int ny = y + dy;
- int nx = x + dx;
- if (ny >= 0 && ny < cornersMat.Rows &&
- nx >= 0 && nx < cornersMat.Cols &&
- !(dy == 0 && dx == 0))
- {
- if (cornersMat.Get<float>(ny, nx) > value)
- {
- isLocalMax = false;
- }
- }
- }
- }
- if (isLocalMax)
- {
- corners.Add(new Point2f(x, y));
- }
- }
- }
- }
- grayFloat.Dispose();
- cornersMat.Dispose();
- return corners.ToArray();
- }
- /// <summary>
- /// 分析网格结构
- /// </summary>
- private static GridStructureInfo AnalyzeGridStructure(Point2f[] corners)
- {
- var info = new GridStructureInfo();
- if (corners.Length < 9) // 至少3x3网格
- return info;
- // 估计网格间距
- List<double> distances = new List<double>();
- for (int i = 0; i < corners.Length; i++)
- {
- for (int j = i + 1; j < Math.Min(i + 10, corners.Length); j++) // 限制检查数量
- {
- double dist = Math.Sqrt(Math.Pow(corners[j].X - corners[i].X, 2) +
- Math.Pow(corners[j].Y - corners[i].Y, 2));
- if (dist > 5) // 忽略太近的点
- distances.Add(dist);
- }
- }
- if (distances.Count == 0)
- return info;
- // 找到最常见的距离作为网格间距
- distances.Sort();
- double estimatedSpacing = distances[distances.Count / 2]; // 中位数
- // 对角落点进行排序(按大致位置)
- var sortedCorners = corners.OrderBy(p => p.Y).ThenBy(p => p.X).ToArray();
- // 尝试分组到行和列
- double tolerance = estimatedSpacing * 0.3;
- info.IsValid = true;
- info.EstimatedSpacing = estimatedSpacing;
- info.Corners = sortedCorners;
- return info;
- }
- /// <summary>
- /// 采样棋盘格颜色
- /// </summary>
- private static void SampleCheckerboardColors(Mat gray, Point2f[] corners, GridStructureInfo gridInfo,
- out double blackMean, out double whiteMean, out int sampleCount)
- {
- blackMean = 0;
- whiteMean = 0;
- int blackCount = 0;
- int whiteCount = 0;
- sampleCount = 0;
- double spacing = gridInfo.EstimatedSpacing;
- int sampleRadius = (int)(spacing * 0.3);
- // 对每个角点区域采样
- for (int i = 0; i < corners.Length; i++)
- {
- int x = (int)corners[i].X;
- int y = (int)corners[i].Y;
- // 采样区域
- int x1 = Math.Max(0, x - sampleRadius);
- int y1 = Math.Max(0, y - sampleRadius);
- int x2 = Math.Min(gray.Cols - 1, x + sampleRadius);
- int y2 = Math.Min(gray.Rows - 1, y + sampleRadius);
- if (x2 > x1 && y2 > y1)
- {
- Rect roi = new Rect(x1, y1, x2 - x1, y2 - y1);
- Mat sample = new Mat(gray, roi);
- Scalar mean = Cv2.Mean(sample);
- sample.Dispose();
- double value = mean.Val0;
- // 根据位置判断黑白(奇偶性)
- int gridX = (int)(x / spacing);
- int gridY = (int)(y / spacing);
- bool isBlack = (gridX + gridY) % 2 == 0;
- if (isBlack)
- {
- blackMean += value;
- blackCount++;
- }
- else
- {
- whiteMean += value;
- whiteCount++;
- }
- sampleCount++;
- }
- }
- if (blackCount > 0) blackMean /= blackCount;
- if (whiteCount > 0) whiteMean /= whiteCount;
- // 确保黑色比白色暗
- if (blackMean > whiteMean)
- {
- // 交换
- double temp = blackMean;
- blackMean = whiteMean;
- whiteMean = temp;
- }
- }
- /// <summary>
- /// 计算最佳亮度
- /// </summary>
- private static int CalculateOptimalBrightness(double blackMean, double whiteMean)
- {
- double currentMid = (blackMean + whiteMean) / 2;
- int optimal = (int)Clamp(128 + (128 - currentMid), 30, 225);
- return optimal;
- }
- public static double Clamp(double value, double min, double max)
- {
- return value < min ? min : (value > max ? max : value);
- }
- /// <summary>
- /// 计算图像质量评分
- /// </summary>
- private static double CalculateQualityScore(double contrast, double blackMean, double whiteMean)
- {
- // 对比度评分(0-100)
- double contrastScore = Math.Min(100, contrast / 2.55);
- // 亮度范围评分
- double blackScore = Math.Max(0, 100 - Math.Abs(blackMean - 30));
- double whiteScore = Math.Max(0, 100 - Math.Abs(whiteMean - 220));
- double rangeScore = (blackScore + whiteScore) / 2;
- // 综合评分
- double qualityScore = contrastScore * 0.6 + rangeScore * 0.4;
- return Math.Min(100, Math.Max(0, qualityScore));
- }
- /// <summary>
- /// 一键获取图像质量报告
- /// </summary>
- public static ImageQualityReport GetImageQualityReport(Mat image, Rect? roi = null)
- {
- var report = new ImageQualityReport();
- if (image == null || image.Empty())
- return report;
- // 计算清晰度
- report.Sharpness = CalculateImageSharpness(image, FocusMethod.Tenengrad, roi);
- // 分析棋盘格对比度
- var contrastResult = AnalyzeCheckerboardContrast(image, roi);
- if (contrastResult.Success)
- {
- report.HasCheckerboard = true;
- report.BlackMean = contrastResult.BlackMean;
- report.WhiteMean = contrastResult.WhiteMean;
- report.Contrast = contrastResult.Contrast;
- report.OptimalBrightness = contrastResult.OptimalBrightness;
- report.QualityScore = contrastResult.QualityScore;
- }
- else
- {
- // 如果没有检测到棋盘格,使用图像整体对比度
- report.HasCheckerboard = false;
- report.Contrast = CalculateGlobalContrast(image, roi);
- report.OptimalBrightness = EstimateOptimalBrightness(image, roi);
- report.QualityScore = Math.Min(100, report.Contrast / 2.0);
- }
- // 给出建议
- report.Suggestions = GenerateSuggestions(report);
- return report;
- }
- /// <summary>
- /// 计算全局对比度(无棋盘格时使用)
- /// </summary>
- private static double CalculateGlobalContrast(Mat image, Rect? roi = null)
- {
- Mat gray = new Mat();
- if (image.Channels() == 3)
- Cv2.CvtColor(image, gray, ColorConversionCodes.BGR2GRAY);
- else
- gray = image.Clone();
- if (roi.HasValue)
- {
- gray = new Mat(gray, roi.Value);
- }
- Mat mean = new Mat();
- Mat stdDev = new Mat();
- Cv2.MeanStdDev(gray, mean, stdDev);
- double contrast = stdDev.Get<double>(0) * 2; // 标准差的两倍作为对比度估计
- gray.Dispose();
- mean.Dispose();
- stdDev.Dispose();
- return contrast;
- }
- /// <summary>
- /// 估计最佳亮度(无棋盘格时使用)
- /// </summary>
- private static int EstimateOptimalBrightness(Mat image, Rect? roi = null)
- {
- Mat gray = new Mat();
- if (image.Channels() == 3)
- Cv2.CvtColor(image, gray, ColorConversionCodes.BGR2GRAY);
- else
- gray = image.Clone();
- if (roi.HasValue)
- {
- gray = new Mat(gray, roi.Value);
- }
- Scalar mean = Cv2.Mean(gray);
- double avgGray = mean.Val0;
- // 根据平均灰度调整
- int optimal;
- if (avgGray < 80)
- optimal = (int)Math.Min(255, avgGray + 80);
- else if (avgGray > 180)
- optimal = (int)Math.Max(0, avgGray - 80);
- else
- optimal = 128;
- gray.Dispose();
- return optimal;
- }
- /// <summary>
- /// 生成调整建议
- /// </summary>
- private static List<string> GenerateSuggestions(ImageQualityReport report)
- {
- var suggestions = new List<string>();
- // 清晰度建议
- if (report.Sharpness < 10)
- suggestions.Add("图像模糊,请调整焦距使图像变清晰");
- else if (report.Sharpness < 20)
- suggestions.Add("清晰度一般,可以继续微调焦距");
- else
- suggestions.Add("图像清晰度良好");
- // 对比度建议
- if (report.HasCheckerboard)
- {
- if (report.Contrast < 50)
- suggestions.Add("棋盘格对比度过低,建议调整光源或相机参数");
- else if (report.Contrast < 100)
- suggestions.Add("棋盘格对比度适中");
- else
- suggestions.Add("棋盘格对比度很好");
- suggestions.Add($"建议亮度值: {report.OptimalBrightness}");
- }
- else
- {
- if (report.Contrast < 30)
- suggestions.Add("图像整体对比度较低");
- else
- suggestions.Add("图像对比度正常");
- }
- // 质量评分建议
- if (report.QualityScore < 60)
- suggestions.Add($"图像质量需要改进,当前评分: {report.QualityScore:F1}/100");
- else if (report.QualityScore < 80)
- suggestions.Add($"图像质量良好,当前评分: {report.QualityScore:F1}/100");
- else
- suggestions.Add($"图像质量优秀,当前评分: {report.QualityScore:F1}/100");
- return suggestions;
- }
- }
- // 辅助数据结构
- public class CheckerboardContrastResult
- {
- public bool Success { get; set; }
- public double BlackMean { get; set; }
- public double WhiteMean { get; set; }
- public double Contrast { get; set; }
- public int OptimalBrightness { get; set; }
- public double QualityScore { get; set; }
- }
- public class GridStructureInfo
- {
- public bool IsValid { get; set; }
- public double EstimatedSpacing { get; set; }
- public Point2f[] Corners { get; set; }
- }
- public class ImageQualityReport
- {
- public double Sharpness { get; set; }
- public bool HasCheckerboard { get; set; }
- public double BlackMean { get; set; }
- public double WhiteMean { get; set; }
- public double Contrast { get; set; }
- public int OptimalBrightness { get; set; }
- public double QualityScore { get; set; }
- public List<string> Suggestions { get; set; } = new List<string>();
- }
- }
|