426 lines
17 KiB
C#
426 lines
17 KiB
C#
|
||
#if false
|
||
|
||
using Microsoft.Extensions.Logging;
|
||
using Microsoft.Extensions.Options;
|
||
using OrpaonVision.Core.Results;
|
||
using OrpaonVision.SiteApp.Runtime.Contracts;
|
||
using SixLabors.ImageSharp;
|
||
using SixLabors.ImageSharp.PixelFormats;
|
||
using YoloDotNet;
|
||
using YoloDotNet.Models;
|
||
using System.Drawing;
|
||
using System.IO;
|
||
|
||
namespace OrpaonVision.SiteApp.Runtime.Services;
|
||
|
||
/// <summary>
|
||
/// YOLO推理服务实现(支持真实和模拟模式)。
|
||
/// </summary>
|
||
public sealed class RealYoloInferenceService : IYoloInferenceService, IDisposable
|
||
{
|
||
private readonly ILogger<RealYoloInferenceService> _logger;
|
||
private readonly YoloInferenceOptions _options;
|
||
private Yolo? _yolo;
|
||
private bool _yoloAvailable;
|
||
private readonly object _lockObject = new();
|
||
private bool _isInitialized;
|
||
private readonly List<string> _classNames = new();
|
||
private readonly Random _random = new();
|
||
|
||
/// <summary>
|
||
/// 构造函数。
|
||
/// </summary>
|
||
public RealYoloInferenceService(ILogger<RealYoloInferenceService> logger, IOptions<YoloInferenceOptions> options)
|
||
{
|
||
_logger = logger;
|
||
_options = options.Value;
|
||
_logger.LogInformation("YOLO推理服务已初始化(模式: {RunMode})", _options.RunMode);
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public bool IsInitialized => _isInitialized;
|
||
|
||
/// <inheritdoc />
|
||
public Result Initialize()
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
try
|
||
{
|
||
if (_isInitialized)
|
||
{
|
||
return Result.Success(message: "YOLO模型已初始化。");
|
||
}
|
||
|
||
if (_options.RunMode != "Real")
|
||
{
|
||
_logger.LogInformation("模拟模式:跳过YOLO模型加载");
|
||
_isInitialized = true;
|
||
return Result.Success(message: "YOLO推理服务初始化成功(模拟模式)。");
|
||
}
|
||
|
||
_logger.LogInformation("正在初始化YOLO模型: {ModelPath}", _options.ModelPath);
|
||
|
||
var loadResult = TryLoadYoloModel();
|
||
if (!loadResult.Succeeded)
|
||
{
|
||
return loadResult;
|
||
}
|
||
|
||
_isInitialized = true;
|
||
_logger.LogInformation("YOLO模型初始化成功");
|
||
return Result.Success(message: "YOLO模型初始化成功。");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "初始化YOLO模型失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "YOLO_INITIALIZE_FAILED", "初始化YOLO模型失败。", traceId);
|
||
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public Result<IReadOnlyList<YoloDetectionResult>> Predict(byte[] imageData, int width, int height, string pixelFormat = "BGR8Packed")
|
||
{
|
||
try
|
||
{
|
||
if (!_isInitialized)
|
||
{
|
||
return Result<IReadOnlyList<YoloDetectionResult>>.Fail("YOLO_NOT_INITIALIZED", "YOLO模型未初始化。");
|
||
}
|
||
|
||
if (_options.RunMode != "Real" || !_yoloAvailable)
|
||
{
|
||
_logger.LogDebug("模拟模式:生成模拟检测结果");
|
||
return GenerateMockDetections(width, height);
|
||
}
|
||
|
||
// 转换图像数据为ImageSharp格式
|
||
using var image = ConvertImageData(imageData, width, height, pixelFormat);
|
||
if (image == null)
|
||
{
|
||
return Result<IReadOnlyList<YoloDetectionResult>>.Fail("IMAGE_CONVERSION_FAILED", "图像数据转换失败。");
|
||
}
|
||
|
||
// 执行YOLO推理
|
||
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
|
||
var detections = _yolo!.Run(image, _options.ConfidenceThreshold, _options.NmsThreshold);
|
||
stopwatch.Stop();
|
||
|
||
// 转换检测结果
|
||
var results = detections.Select(d => new YoloDetectionResult
|
||
{
|
||
ClassName = d.Class.Name,
|
||
Confidence = (float)d.Confidence,
|
||
BoundingBox = new RectangleF(
|
||
(float)d.Rectangle.X,
|
||
(float)d.Rectangle.Y,
|
||
(float)d.Rectangle.Width,
|
||
(float)d.Rectangle.Height),
|
||
ElapsedMs = stopwatch.ElapsedMilliseconds
|
||
}).ToList();
|
||
|
||
_logger.LogDebug("YOLO推理完成,检测到 {Count} 个目标,耗时 {ElapsedMs}ms", results.Count, stopwatch.ElapsedMilliseconds);
|
||
return Result<IReadOnlyList<YoloDetectionResult>>.Success(results);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "YOLO推理失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "YOLO_INFERENCE_FAILED", "YOLO推理失败。", traceId);
|
||
return Result<IReadOnlyList<YoloDetectionResult>>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public Result<IReadOnlyList<IReadOnlyList<YoloDetectionResult>>> PredictBatch(IReadOnlyList<byte[]> imageBatch, IReadOnlyList<(int width, int height)> dimensions, string pixelFormat = "BGR8Packed")
|
||
{
|
||
try
|
||
{
|
||
if (!_isInitialized)
|
||
{
|
||
return Result<IReadOnlyList<IReadOnlyList<YoloDetectionResult>>>.Fail("YOLO_NOT_INITIALIZED", "YOLO模型未初始化。");
|
||
}
|
||
|
||
if (imageBatch.Count != dimensions.Count)
|
||
{
|
||
return Result<IReadOnlyList<IReadOnlyList<YoloDetectionResult>>>.Fail("BATCH_SIZE_MISMATCH", "图像批次与尺寸数量不匹配。");
|
||
}
|
||
|
||
var batchResults = new List<IReadOnlyList<YoloDetectionResult>>(imageBatch.Count);
|
||
|
||
if (_options.RunMode != "Real" || !_yoloAvailable)
|
||
{
|
||
_logger.LogDebug("模拟模式:生成模拟批次检测结果");
|
||
for (var i = 0; i < imageBatch.Count; i++)
|
||
{
|
||
var (width, height) = dimensions[i];
|
||
var mockResult = GenerateMockDetections(width, height);
|
||
if (!mockResult.Succeeded)
|
||
{
|
||
return Result<IReadOnlyList<IReadOnlyList<YoloDetectionResult>>>.Fail(mockResult.Code, mockResult.Message, mockResult.Errors.ToArray());
|
||
}
|
||
batchResults.Add(mockResult.Data);
|
||
}
|
||
return Result<IReadOnlyList<IReadOnlyList<YoloDetectionResult>>>.Success(batchResults);
|
||
}
|
||
|
||
// 批量推理
|
||
for (var i = 0; i < imageBatch.Count; i++)
|
||
{
|
||
var result = Predict(imageBatch[i], dimensions[i].width, dimensions[i].height, pixelFormat);
|
||
if (!result.Succeeded)
|
||
{
|
||
return Result<IReadOnlyList<IReadOnlyList<YoloDetectionResult>>>.Fail(result.Code, result.Message, result.Errors.ToArray());
|
||
}
|
||
batchResults.Add(result.Data);
|
||
}
|
||
|
||
_logger.LogDebug("YOLO批量推理完成,处理 {Count} 张图像", batchResults.Count);
|
||
return Result<IReadOnlyList<IReadOnlyList<YoloDetectionResult>>>.Success(batchResults);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "YOLO批量推理失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "YOLO_BATCH_INFERENCE_FAILED", "YOLO批量推理失败。", traceId);
|
||
return Result<IReadOnlyList<IReadOnlyList<YoloDetectionResult>>>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public IReadOnlyList<string> GetSupportedClasses()
|
||
{
|
||
if (_options.RunMode != "Real" || !_yoloAvailable)
|
||
{
|
||
return GetMockClassNames();
|
||
}
|
||
|
||
return _classNames;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public YoloModelInfo GetModelInfo()
|
||
{
|
||
if (_options.RunMode != "Real" || !_yoloAvailable)
|
||
{
|
||
return new YoloModelInfo
|
||
{
|
||
ModelName = Path.GetFileNameWithoutExtension(_options.ModelPath),
|
||
ModelVersion = _options.ModelVersion,
|
||
InputSize = (_options.InputWidth, _options.InputHeight),
|
||
ClassCount = GetMockClassNames().Count,
|
||
ClassNames = GetMockClassNames(),
|
||
UseGpu = _options.UseGpu,
|
||
InferenceTimeMs = 0,
|
||
ModelFileSize = 0,
|
||
LoadTimeMs = 0
|
||
};
|
||
}
|
||
|
||
var modelFile = new FileInfo(_options.ModelPath);
|
||
return new YoloModelInfo
|
||
{
|
||
ModelName = Path.GetFileNameWithoutExtension(_options.ModelPath),
|
||
ModelVersion = _options.ModelVersion,
|
||
InputSize = (_options.InputWidth, _options.InputHeight),
|
||
ClassCount = _classNames.Count,
|
||
ClassNames = _classNames,
|
||
UseGpu = _options.UseGpu,
|
||
InferenceTimeMs = 0,
|
||
ModelFileSize = modelFile.Exists ? (long)modelFile.Length : 0,
|
||
LoadTimeMs = 0
|
||
};
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public Result Warmup(int warmupCount = 3)
|
||
{
|
||
try
|
||
{
|
||
if (!_isInitialized)
|
||
{
|
||
return Result.Fail("YOLO_NOT_INITIALIZED", "YOLO模型未初始化。");
|
||
}
|
||
|
||
_logger.LogInformation("执行YOLO模型预热,次数={WarmupCount}", warmupCount);
|
||
|
||
if (_options.RunMode != "Real" || !_yoloAvailable)
|
||
{
|
||
_logger.LogDebug("模拟模式:跳过YOLO模型预热");
|
||
return Result.Success(message: "YOLO模型预热完成(模拟模式)。");
|
||
}
|
||
|
||
// 生成模拟图像数据用于预热
|
||
var mockImage = new byte[_options.InputWidth * _options.InputHeight * 3];
|
||
_random.NextBytes(mockImage);
|
||
|
||
for (var i = 0; i < warmupCount; i++)
|
||
{
|
||
var result = Predict(mockImage, _options.InputWidth, _options.InputHeight);
|
||
if (!result.Succeeded)
|
||
{
|
||
_logger.LogWarning("预热第 {Index} 次失败: {Error}", i + 1, result.Message);
|
||
}
|
||
}
|
||
|
||
_logger.LogInformation("YOLO模型预热完成");
|
||
return Result.Success(message: "YOLO模型预热完成。");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "YOLO模型预热失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "YOLO_WARMUP_FAILED", "YOLO模型预热失败。", traceId);
|
||
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 尝试加载YOLO模型。
|
||
/// </summary>
|
||
private Result TryLoadYoloModel()
|
||
{
|
||
try
|
||
{
|
||
if (!File.Exists(_options.ModelPath))
|
||
{
|
||
_logger.LogError("YOLO模型文件不存在: {ModelPath}", _options.ModelPath);
|
||
return Result.Fail("YOLO_MODEL_FILE_NOT_FOUND", $"YOLO模型文件不存在: {_options.ModelPath}");
|
||
}
|
||
|
||
_logger.LogInformation("正在加载YOLO模型: {ModelPath}", _options.ModelPath);
|
||
|
||
// 加载YOLO模型
|
||
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
|
||
_yolo = new Yolo(_options.ModelPath);
|
||
stopwatch.Stop();
|
||
|
||
_yoloAvailable = true;
|
||
_classNames.AddRange(_yolo.ClassNames);
|
||
|
||
_logger.LogInformation("YOLO模型加载成功,耗时 {ElapsedMs}ms,支持 {ClassCount} 个类别",
|
||
stopwatch.ElapsedMilliseconds, _classNames.Count);
|
||
|
||
return Result.Success(message: "YOLO模型加载成功。");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "加载YOLO模型失败");
|
||
return Result.Fail("YOLO_MODEL_LOAD_FAILED", $"加载YOLO模型失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 转换图像数据。
|
||
/// </summary>
|
||
private Image<Rgb24>? ConvertImageData(byte[] imageData, int width, int height, string pixelFormat)
|
||
{
|
||
try
|
||
{
|
||
if (imageData.Length != width * height * 3)
|
||
{
|
||
_logger.LogWarning("图像数据大小不匹配,期望 {Expected},实际 {Actual}", width * height * 3, imageData.Length);
|
||
return null;
|
||
}
|
||
|
||
var image = Image.LoadPixelData<Rgb24>(imageData, width, height);
|
||
return image;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "图像数据转换失败");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成模拟检测结果。
|
||
/// </summary>
|
||
private Result<IReadOnlyList<YoloDetectionResult>> GenerateMockDetections(int width, int height)
|
||
{
|
||
try
|
||
{
|
||
var mockDetections = new List<YoloDetectionResult>();
|
||
var mockClasses = GetMockClassNames();
|
||
|
||
// 随机生成0-3个检测结果
|
||
var detectionCount = _random.Next(0, 4);
|
||
|
||
for (var i = 0; i < detectionCount; i++)
|
||
{
|
||
var className = mockClasses[_random.Next(mockClasses.Count)];
|
||
var confidence = (float)(_random.NextDouble() * 0.5 + 0.5); // 0.5-1.0
|
||
|
||
// 随机生成边界框(确保在图像范围内)
|
||
var boxWidth = (float)(_random.NextDouble() * width * 0.3 + width * 0.1); // 10%-40% of image width
|
||
var boxHeight = (float)(_random.NextDouble() * height * 0.3 + height * 0.1); // 10%-40% of image height
|
||
var x = (float)(_random.NextDouble() * (width - boxWidth));
|
||
var y = (float)(_random.NextDouble() * (height - boxHeight));
|
||
|
||
mockDetections.Add(new YoloDetectionResult
|
||
{
|
||
ClassName = className,
|
||
Confidence = confidence,
|
||
BoundingBox = new RectangleF(x, y, boxWidth, boxHeight),
|
||
ElapsedMs = _random.Next(10, 50) // 模拟推理时间10-50ms
|
||
});
|
||
}
|
||
|
||
return Result<IReadOnlyList<YoloDetectionResult>>.Success(mockDetections);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "生成模拟检测结果失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "MOCK_DETECTION_FAILED", "生成模拟检测结果失败。", traceId);
|
||
return Result<IReadOnlyList<YoloDetectionResult>>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取模拟类别名称。
|
||
/// </summary>
|
||
private IReadOnlyList<string> GetMockClassNames()
|
||
{
|
||
return new List<string>
|
||
{
|
||
"person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light",
|
||
"fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat", "dog", "horse", "sheep", "cow",
|
||
"elephant", "bear", "zebra", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee",
|
||
"skis", "snowboard", "sports ball", "kite", "baseball bat", "baseball glove", "skateboard", "surfboard",
|
||
"tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple",
|
||
"sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair", "couch",
|
||
"potted plant", "bed", "dining table", "toilet", "tv", "laptop", "mouse", "remote", "keyboard", "cell phone",
|
||
"microwave", "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase", "scissors", "teddy bear",
|
||
"hair drier", "toothbrush"
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 释放资源。
|
||
/// </summary>
|
||
public void Dispose()
|
||
{
|
||
try
|
||
{
|
||
_yolo?.Dispose();
|
||
_yolo = null;
|
||
_yoloAvailable = false;
|
||
_isInitialized = false;
|
||
_classNames.Clear();
|
||
|
||
_logger.LogInformation("YOLO推理服务已释放");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "释放YOLO推理服务资源时发生错误");
|
||
}
|
||
}
|
||
}
|
||
|
||
#endif
|