Files
OrpaonVision/OrpaonVision.SiteApp/Runtime/Services/ImageProcessingService.cs
2026-04-06 22:04:05 +08:00

409 lines
14 KiB
C#
Raw 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.
using Microsoft.Extensions.Logging;
using OrpaonVision.Core.Results;
using OrpaonVision.SiteApp.Runtime.Contracts;
using OrpaonVision.SiteApp.Controls;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Media.Imaging;
using System.Windows.Media;
using System.Windows;
using WpfColor = System.Windows.Media.Color;
using WpfPixelFormat = System.Windows.Media.PixelFormat;
using WpfFontStyle = System.Windows.FontStyle;
using DrawingPen = System.Drawing.Pen;
namespace OrpaonVision.SiteApp.Runtime.Services;
/// <summary>
/// 图像处理服务接口。
/// </summary>
public interface IImageProcessingService
{
/// <summary>
/// 将相机帧转换为BitmapSource。
/// </summary>
Result<BitmapSource> ConvertFrameToBitmapSource(CameraFrameDto frame);
/// <summary>
/// 将推理结果转换为检测框列表。
/// </summary>
Result<List<DetectionBox>> ConvertInferenceToDetectionBoxes(InferenceResultDto inference);
/// <summary>
/// 创建占位图像。
/// </summary>
BitmapSource CreatePlaceholderImage(int width, int height, string text);
/// <summary>
/// 调整图像大小。
/// </summary>
Result<BitmapSource> ResizeImage(BitmapSource source, int width, int height);
/// <summary>
/// 旋转图像。
/// </summary>
Result<BitmapSource> RotateImage(BitmapSource source, double angle);
/// <summary>
/// 裁剪图像。
/// </summary>
Result<BitmapSource> CropImage(BitmapSource source, int x, int y, int width, int height);
/// <summary>
/// 应用图像滤镜。
/// </summary>
Result<BitmapSource> ApplyFilter(BitmapSource source, ImageFilter filter);
}
/// <summary>
/// 图像滤镜类型。
/// </summary>
public enum ImageFilter
{
/// <summary>
/// 无滤镜。
/// </summary>
None,
/// <summary>
/// 灰度。
/// </summary>
Grayscale,
/// <summary>
/// 二值化。
/// </summary>
Binary,
/// <summary>
/// 边缘检测。
/// </summary>
EdgeDetection,
/// <summary>
/// 模糊。
/// </summary>
Blur,
/// <summary>
/// 锐化。
/// </summary>
Sharpen
}
/// <summary>
/// 图像处理服务实现。
/// </summary>
public sealed class ImageProcessingService : IImageProcessingService
{
private readonly ILogger<ImageProcessingService> _logger;
/// <summary>
/// 构造函数。
/// </summary>
public ImageProcessingService(ILogger<ImageProcessingService> logger)
{
_logger = logger;
}
/// <inheritdoc />
public Result<BitmapSource> ConvertFrameToBitmapSource(CameraFrameDto frame)
{
try
{
if (frame == null)
{
return Result<BitmapSource>.Fail("FRAME_NULL", "相机帧不能为空。");
}
if (frame.ImageData == null || frame.ImageData.Length == 0)
{
return Result<BitmapSource>.Fail("IMAGE_DATA_EMPTY", "图像数据为空。");
}
_logger.LogDebug("正在转换相机帧为BitmapSource尺寸: {Width}x{Height}", frame.Width, frame.Height);
using var ms = new System.IO.MemoryStream(frame.ImageData);
var bitmap = new Bitmap(ms);
var bitmapSource = bitmap.ToBitmapSource();
_logger.LogDebug("相机帧转换完成");
return Result<BitmapSource>.Success(bitmapSource, message: "相机帧转换成功。");
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "转换相机帧失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "CONVERT_FRAME_FAILED", "转换相机帧失败。", traceId);
return Result<BitmapSource>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public Result<List<DetectionBox>> ConvertInferenceToDetectionBoxes(InferenceResultDto inference)
{
try
{
if (inference == null)
{
return Result<List<DetectionBox>>.Fail("INFERENCE_NULL", "推理结果不能为空。");
}
var detectionBoxes = new List<DetectionBox>();
if (inference.Detections != null)
{
foreach (var detection in inference.Detections)
{
var box = new DetectionBox
{
ClassName = detection.ClassName ?? "Unknown",
Confidence = detection.Confidence,
X = detection.X,
Y = detection.Y,
Width = detection.Width,
Height = detection.Height,
Label = $"{detection.ClassName} ({detection.Confidence:P0})"
};
detectionBoxes.Add(box);
}
}
_logger.LogDebug("推理结果转换完成,检测框数量: {Count}", detectionBoxes.Count);
return Result<List<DetectionBox>>.Success(detectionBoxes, message: "推理结果转换成功。");
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "转换推理结果失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "CONVERT_INFERENCE_FAILED", "转换推理结果失败。", traceId);
return Result<List<DetectionBox>>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public BitmapSource CreatePlaceholderImage(int width, int height, string text)
{
try
{
using var bitmap = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
using var graphics = Graphics.FromImage(bitmap);
// 清除背景
graphics.Clear(System.Drawing.Color.LightGray);
// 绘制文本
var font = new Font("Arial", 16, System.Drawing.FontStyle.Bold);
var brush = new SolidBrush(System.Drawing.Color.DarkGray);
var textSize = graphics.MeasureString(text, font);
var textX = (width - textSize.Width) / 2;
var textY = (height - textSize.Height) / 2;
graphics.DrawString(text, font, brush, textX, textY);
// 绘制边框
using var pen = new DrawingPen(System.Drawing.Color.Gray, 2);
graphics.DrawRectangle(pen, 0, 0, width - 1, height - 1);
return bitmap.ToBitmapSource();
}
catch (Exception ex)
{
_logger.LogError(ex, "创建占位图像失败");
return CreateErrorPlaceholder(width, height);
}
}
/// <inheritdoc />
public Result<BitmapSource> ResizeImage(BitmapSource source, int width, int height)
{
try
{
if (source == null)
{
return Result<BitmapSource>.Fail("SOURCE_NULL", "源图像不能为空。");
}
_logger.LogDebug("正在调整图像大小: {Width}x{Height} -> {NewWidth}x{NewHeight}",
source.PixelWidth, source.PixelHeight, width, height);
var scaled = new TransformedBitmap(source, new ScaleTransform(
(double)width / source.PixelWidth,
(double)height / source.PixelHeight));
_logger.LogDebug("图像大小调整完成");
return Result<BitmapSource>.Success(scaled, message: "图像大小调整成功。");
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "调整图像大小失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "RESIZE_IMAGE_FAILED", "调整图像大小失败。", traceId);
return Result<BitmapSource>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public Result<BitmapSource> RotateImage(BitmapSource source, double angle)
{
try
{
if (source == null)
{
return Result<BitmapSource>.Fail("SOURCE_NULL", "源图像不能为空。");
}
_logger.LogDebug("正在旋转图像,角度: {Angle}", angle);
var rotated = new TransformedBitmap(source, new RotateTransform(angle));
_logger.LogDebug("图像旋转完成");
return Result<BitmapSource>.Success(rotated, message: "图像旋转成功。");
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "旋转图像失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "ROTATE_IMAGE_FAILED", "旋转图像失败。", traceId);
return Result<BitmapSource>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public Result<BitmapSource> CropImage(BitmapSource source, int x, int y, int width, int height)
{
try
{
if (source == null)
{
return Result<BitmapSource>.Fail("SOURCE_NULL", "源图像不能为空。");
}
_logger.LogDebug("正在裁剪图像: ({X},{Y}) {Width}x{Height}", x, y, width, height);
var cropped = new CroppedBitmap(source, new Int32Rect(x, y, width, height));
_logger.LogDebug("图像裁剪完成");
return Result<BitmapSource>.Success(cropped, message: "图像裁剪成功。");
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "裁剪图像失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "CROP_IMAGE_FAILED", "裁剪图像失败。", traceId);
return Result<BitmapSource>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public Result<BitmapSource> ApplyFilter(BitmapSource source, ImageFilter filter)
{
try
{
if (source == null)
{
return Result<BitmapSource>.Fail("SOURCE_NULL", "源图像不能为空。");
}
_logger.LogDebug("正在应用图像滤镜: {Filter}", filter);
// 这里可以实现各种滤镜效果
// 由于WPF的图像处理相对复杂这里只是占位实现
BitmapSource result = filter switch
{
ImageFilter.Grayscale => ApplyGrayscaleFilter(source),
ImageFilter.Binary => ApplyBinaryFilter(source),
ImageFilter.EdgeDetection => ApplyEdgeDetectionFilter(source),
ImageFilter.Blur => ApplyBlurFilter(source),
ImageFilter.Sharpen => ApplySharpenFilter(source),
_ => source
};
_logger.LogDebug("图像滤镜应用完成");
return Result<BitmapSource>.Success(result, message: "图像滤镜应用成功。");
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "应用图像滤镜失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "APPLY_FILTER_FAILED", "应用图像滤镜失败。", traceId);
return Result<BitmapSource>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
private BitmapSource ApplyGrayscaleFilter(BitmapSource source)
{
// 简化的灰度滤镜实现
// 这里应该实现真正的灰度转换
// 为了简化,直接返回原图
return source;
}
private BitmapSource ApplyBinaryFilter(BitmapSource source)
{
// 简化的二值化滤镜实现
return source;
}
private BitmapSource ApplyEdgeDetectionFilter(BitmapSource source)
{
// 简化的边缘检测滤镜实现
return source;
}
private BitmapSource ApplyBlurFilter(BitmapSource source)
{
// 简化的模糊滤镜实现
return source;
}
private BitmapSource ApplySharpenFilter(BitmapSource source)
{
// 简化的锐化滤镜实现
return source;
}
private BitmapSource CreateErrorPlaceholder(int width, int height)
{
using var bitmap = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
using var graphics = Graphics.FromImage(bitmap);
graphics.Clear(System.Drawing.Color.Red);
using var font = new Font("Arial", 12, System.Drawing.FontStyle.Bold);
using var brush = new SolidBrush(System.Drawing.Color.White);
graphics.DrawString("Error", font, brush, 10, 10);
return bitmap.ToBitmapSource();
}
}
/// <summary>
/// Bitmap扩展方法。
/// </summary>
public static class BitmapExtensions
{
/// <summary>
/// 将Bitmap转换为BitmapSource。
/// </summary>
public static BitmapSource ToBitmapSource(this Bitmap bitmap)
{
var hBitmap = bitmap.GetHbitmap();
try
{
return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
hBitmap,
IntPtr.Zero,
new Int32Rect(0, 0, bitmap.Width, bitmap.Height),
BitmapSizeOptions.FromEmptyOptions());
}
finally
{
DeleteObject(hBitmap);
}
}
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);
}