Files
OrpaonVision/OrpaonVision.SiteApp/Runtime/Services/RealYoloInferenceService.cs
2026-04-12 22:34:46 +08:00

426 lines
17 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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