313 lines
12 KiB
C#
313 lines
12 KiB
C#
using Microsoft.Extensions.Logging;
|
||
using Microsoft.Extensions.Options;
|
||
using OrpaonVision.Core.Results;
|
||
using OrpaonVision.SiteApp.Runtime.Contracts;
|
||
using OrpaonVision.SiteApp.Runtime.Services;
|
||
using System.Diagnostics;
|
||
using System.Drawing;
|
||
using System.Drawing.Imaging;
|
||
using System.IO;
|
||
|
||
namespace OrpaonVision.SiteApp.Runtime.Services;
|
||
|
||
/// <summary>
|
||
/// YOLO推理服务实现。
|
||
/// 注意:这是一个基于模拟的实现,实际使用时需要替换为YoloDotNet或其他YOLO库的真实调用。
|
||
/// </summary>
|
||
public sealed class YoloInferenceService : IYoloInferenceService, IDisposable
|
||
{
|
||
private readonly ILogger<YoloInferenceService> _logger;
|
||
private readonly YoloInferenceOptions _options;
|
||
private readonly object _lockObject = new();
|
||
private bool _isInitialized;
|
||
private readonly List<string> _classNames = new();
|
||
private readonly Random _random = new();
|
||
private YoloModelInfo _modelInfo = new();
|
||
|
||
/// <summary>
|
||
/// 构造函数。
|
||
/// </summary>
|
||
public YoloInferenceService(ILogger<YoloInferenceService> logger, IOptions<YoloInferenceOptions> options)
|
||
{
|
||
_logger = logger;
|
||
_options = options.Value;
|
||
_logger.LogInformation("YOLO推理服务已初始化(模拟模式)");
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public Result Initialize()
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
try
|
||
{
|
||
if (_isInitialized)
|
||
{
|
||
return Result.Success(message: "YOLO模型已初始化。");
|
||
}
|
||
|
||
_logger.LogInformation("正在初始化YOLO模型: {ModelPath}", _options.ModelPath);
|
||
|
||
var stopwatch = Stopwatch.StartNew();
|
||
|
||
// 模拟模型加载 - 实际实现需要调用YoloDotNet或其他YOLO库
|
||
Thread.Sleep(1000); // 模拟加载时间
|
||
|
||
// 模拟加载类别标签
|
||
_classNames.Clear();
|
||
if (File.Exists(_options.LabelsPath))
|
||
{
|
||
var lines = File.ReadAllLines(_options.LabelsPath);
|
||
foreach (var line in lines)
|
||
{
|
||
var className = line.Trim();
|
||
if (!string.IsNullOrEmpty(className))
|
||
{
|
||
_classNames.Add(className);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 使用默认类别
|
||
_classNames.AddRange(new[]
|
||
{
|
||
"大电容", "螺丝", "铜排", "小电容", "电阻", "连接器", "散热片", "PCB板"
|
||
});
|
||
}
|
||
|
||
// 设置模型信息
|
||
_modelInfo = new YoloModelInfo
|
||
{
|
||
ModelName = Path.GetFileNameWithoutExtension(_options.ModelPath),
|
||
ModelVersion = "1.0.0",
|
||
InputSize = (_options.InputWidth, _options.InputHeight),
|
||
ClassCount = _classNames.Count,
|
||
ClassNames = _classNames.ToList(),
|
||
UseGpu = _options.UseGpu,
|
||
LoadTimeMs = stopwatch.Elapsed.TotalMilliseconds,
|
||
ModelFileSize = File.Exists(_options.ModelPath) ? new FileInfo(_options.ModelPath).Length : 0
|
||
};
|
||
|
||
_isInitialized = true;
|
||
stopwatch.Stop();
|
||
|
||
_logger.LogInformation("YOLO模型初始化成功,耗时: {ElapsedMs:F2}ms,类别数: {ClassCount}",
|
||
stopwatch.Elapsed.TotalMilliseconds, _classNames.Count);
|
||
|
||
// 执行预热
|
||
if (_options.EnableWarmup)
|
||
{
|
||
var warmupResult = Warmup(_options.WarmupCount);
|
||
if (!warmupResult.Succeeded)
|
||
{
|
||
_logger.LogWarning("模型预热失败: {Message}", warmupResult.Message);
|
||
}
|
||
}
|
||
|
||
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_INIT_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")
|
||
{
|
||
if (!_isInitialized)
|
||
{
|
||
var initResult = Initialize();
|
||
if (!initResult.Succeeded)
|
||
{
|
||
return Result<IReadOnlyList<YoloDetectionResult>>.Fail(initResult.Code, initResult.Message, initResult.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
try
|
||
{
|
||
_logger.LogDebug("开始YOLO推理,图像尺寸: {Width}x{Height}", width, height);
|
||
|
||
var stopwatch = Stopwatch.StartNew();
|
||
|
||
// 模拟推理过程 - 实际实现需要调用YoloDotNet的Predict方法
|
||
Thread.Sleep(50); // 模拟推理时间
|
||
|
||
var results = new List<YoloDetectionResult>();
|
||
|
||
// 生成模拟检测结果
|
||
var detectionCount = _random.Next(0, 5); // 随机生成0-4个检测结果
|
||
for (int i = 0; i < detectionCount; i++)
|
||
{
|
||
var classId = _random.Next(0, _classNames.Count);
|
||
var confidence = (float)(_random.NextDouble() * 0.4 + 0.6); // 0.6-1.0的置信度
|
||
|
||
var x = _random.Next(0, width / 2);
|
||
var y = _random.Next(0, height / 2);
|
||
var w = _random.Next(width / 8, width / 4);
|
||
var h = _random.Next(height / 8, height / 4);
|
||
|
||
results.Add(new YoloDetectionResult
|
||
{
|
||
ClassId = classId,
|
||
ClassName = _classNames[classId],
|
||
Confidence = confidence,
|
||
X = x,
|
||
Y = y,
|
||
Width = w,
|
||
Height = h
|
||
});
|
||
}
|
||
|
||
// 按置信度排序
|
||
results.Sort((a, b) => b.Confidence.CompareTo(a.Confidence));
|
||
|
||
stopwatch.Stop();
|
||
_modelInfo.InferenceTimeMs = stopwatch.Elapsed.TotalMilliseconds;
|
||
|
||
_logger.LogDebug("YOLO推理完成,检测到 {Count} 个目标,耗时: {ElapsedMs:F2}ms",
|
||
results.Count, stopwatch.Elapsed.TotalMilliseconds);
|
||
|
||
return Result<IReadOnlyList<YoloDetectionResult>>.Success(results, message: "YOLO推理成功。");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "YOLO推理失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "YOLO_PREDICT_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")
|
||
{
|
||
if (!_isInitialized)
|
||
{
|
||
var initResult = Initialize();
|
||
if (!initResult.Succeeded)
|
||
{
|
||
return Result<IReadOnlyList<IReadOnlyList<YoloDetectionResult>>>.Fail(initResult.Code, initResult.Message, initResult.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
if (imageBatch.Count != dimensions.Count)
|
||
{
|
||
return Result<IReadOnlyList<IReadOnlyList<YoloDetectionResult>>>.Fail(
|
||
"YOLO_BATCH_SIZE_MISMATCH", "图像批次与维度信息数量不匹配。");
|
||
}
|
||
|
||
try
|
||
{
|
||
_logger.LogDebug("开始YOLO批量推理,批次大小: {BatchSize}", imageBatch.Count);
|
||
|
||
var results = new List<IReadOnlyList<YoloDetectionResult>>();
|
||
|
||
for (int i = 0; i < imageBatch.Count; i++)
|
||
{
|
||
var predictResult = Predict(imageBatch[i], dimensions[i].width, dimensions[i].height, pixelFormat);
|
||
if (!predictResult.Succeeded)
|
||
{
|
||
return Result<IReadOnlyList<IReadOnlyList<YoloDetectionResult>>>.Fail(
|
||
predictResult.Code, $"第{i+1}张图像推理失败: {predictResult.Message}", predictResult.Errors.ToArray());
|
||
}
|
||
|
||
results.Add(predictResult.Data);
|
||
}
|
||
|
||
_logger.LogDebug("YOLO批量推理完成,处理了 {Count} 张图像", results.Count);
|
||
|
||
return Result<IReadOnlyList<IReadOnlyList<YoloDetectionResult>>>.Success(results, message: "YOLO批量推理成功。");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "YOLO批量推理失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "YOLO_BATCH_PREDICT_FAILED", "YOLO批量推理失败。", traceId);
|
||
return Result<IReadOnlyList<IReadOnlyList<YoloDetectionResult>>>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public IReadOnlyList<string> GetSupportedClasses()
|
||
{
|
||
return _classNames.ToList();
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public YoloModelInfo GetModelInfo()
|
||
{
|
||
return _modelInfo;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public bool IsInitialized => _isInitialized;
|
||
|
||
/// <inheritdoc />
|
||
public Result Warmup(int warmupCount = 3)
|
||
{
|
||
if (!_isInitialized)
|
||
{
|
||
return Result.Fail("YOLO_NOT_INITIALIZED", "YOLO模型未初始化。");
|
||
}
|
||
|
||
try
|
||
{
|
||
_logger.LogInformation("开始YOLO模型预热,预热次数: {WarmupCount}", warmupCount);
|
||
|
||
// 生成模拟图像数据
|
||
var dummyImage = new byte[_options.InputWidth * _options.InputHeight * 3]; // BGR格式
|
||
|
||
for (int i = 0; i < warmupCount; i++)
|
||
{
|
||
var warmupResult = Predict(dummyImage, _options.InputWidth, _options.InputHeight);
|
||
if (!warmupResult.Succeeded)
|
||
{
|
||
return Result.Fail(warmupResult.Code, $"预热第{i+1}次失败: {warmupResult.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>
|
||
/// 释放资源。
|
||
/// </summary>
|
||
public void Dispose()
|
||
{
|
||
try
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
if (_isInitialized)
|
||
{
|
||
// 模拟资源释放 - 实际实现需要释放YOLO模型资源
|
||
_isInitialized = false;
|
||
_logger.LogInformation("YOLO推理服务资源已释放");
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "释放YOLO推理服务资源时发生异常");
|
||
}
|
||
}
|
||
}
|