318 lines
11 KiB
C#
318 lines
11 KiB
C#
|
|
using MainShell.EventArgsFolder;
|
|||
|
|
using MainShell.Log;
|
|||
|
|
using MainShell.Vision;
|
|||
|
|
using MwFramework.Device.Model;
|
|||
|
|
using MwFramework.Controls.ControlCanvas.DrawingControl;
|
|||
|
|
using Stylet;
|
|||
|
|
using System;
|
|||
|
|
using System.Collections.Generic;
|
|||
|
|
using System.Collections.ObjectModel;
|
|||
|
|
using System.Linq;
|
|||
|
|
using System.Text;
|
|||
|
|
using System.Threading.Tasks;
|
|||
|
|
using System.Windows;
|
|||
|
|
using System.Windows.Media.Imaging;
|
|||
|
|
using AppCameraType = MainShell.Common.CameraType;
|
|||
|
|
using System.Windows.Media;
|
|||
|
|
|
|||
|
|
namespace MainShell.Common.Display.ViewModel
|
|||
|
|
{
|
|||
|
|
public class CameraViewModel : Screen, IHandle<DisplayImageEventArgs>, IHandle<DisplayFindTemplateResultEventArgs>
|
|||
|
|
{
|
|||
|
|
private readonly IEventAggregator _eventAggregator;
|
|||
|
|
public AppCameraType CameraType { get; }
|
|||
|
|
|
|||
|
|
private WriteableBitmap currentImage;
|
|||
|
|
private WriteableBitmap _displayImage;
|
|||
|
|
public WriteableBitmap DisplayImage
|
|||
|
|
{
|
|||
|
|
get { return _displayImage; }
|
|||
|
|
set
|
|||
|
|
{
|
|||
|
|
SetAndNotify(ref _displayImage, value);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private ObservableCollection<object> _shapes = new ObservableCollection<object>();
|
|||
|
|
public ObservableCollection<object> Shapes
|
|||
|
|
{
|
|||
|
|
get { return _shapes; }
|
|||
|
|
set
|
|||
|
|
{
|
|||
|
|
if (_shapes != value) { _shapes = value; OnPropertyChanged(nameof(Shapes)); }
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
/// <summary>
|
|||
|
|
/// 根据 CameraData 更新并返回 WriteableBitmap。
|
|||
|
|
/// 优化要点:
|
|||
|
|
/// - 重用已有 WriteableBitmap(当尺寸与像素格式相同时)以减少分配。
|
|||
|
|
/// - 校验参数与缓冲区长度。
|
|||
|
|
/// - 捕获并记录异常,失败时返回 null(不抛出)。
|
|||
|
|
/// </summary>
|
|||
|
|
public WriteableBitmap UpdateBitmap(CameraData cameraData)
|
|||
|
|
{
|
|||
|
|
if (cameraData == null)
|
|||
|
|
{
|
|||
|
|
"图片数据为空".LogSysError();
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
int width = (int)cameraData.Width;
|
|||
|
|
int height = (int)cameraData.Height;
|
|||
|
|
int bitCnt = cameraData.Bit;
|
|||
|
|
byte[] buffer = cameraData.BufferData;
|
|||
|
|
|
|||
|
|
if (width <= 0 || height <= 0)
|
|||
|
|
{
|
|||
|
|
$"CameraViewModel.UpdateBitmap: invalid size {width}x{height}".LogSysError();
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 选择像素格式
|
|||
|
|
System.Windows.Media.PixelFormat pixelFormat = bitCnt == 1
|
|||
|
|
? System.Windows.Media.PixelFormats.Gray8
|
|||
|
|
: (bitCnt == 3 ? System.Windows.Media.PixelFormats.Bgr24 : System.Windows.Media.PixelFormats.Bgra32);
|
|||
|
|
|
|||
|
|
// 计算每行字节数(stride)
|
|||
|
|
int bytesPerPixel = (pixelFormat.BitsPerPixel + 7) / 8;
|
|||
|
|
int stride = width * bytesPerPixel;
|
|||
|
|
long requiredLength = (long)stride * height;
|
|||
|
|
|
|||
|
|
if (buffer == null)
|
|||
|
|
{
|
|||
|
|
"CameraViewModel.UpdateBitmap: buffer is null".LogSysError();
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (buffer.Length < requiredLength)
|
|||
|
|
{
|
|||
|
|
($"CameraViewModel.UpdateBitmap: buffer length {buffer.Length} is less than required {requiredLength}").LogSysError();
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果现有 bitmap 可复用(尺寸和格式一致),就重用以减少 GC/分配
|
|||
|
|
if (currentImage == null ||
|
|||
|
|
currentImage.PixelWidth != width ||
|
|||
|
|
currentImage.PixelHeight != height ||
|
|||
|
|
!currentImage.Format.Equals(pixelFormat))
|
|||
|
|
{
|
|||
|
|
// 新建 WriteableBitmap
|
|||
|
|
currentImage = new WriteableBitmap(width, height, 96, 96, pixelFormat, null);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 将字节拷贝到 BackBuffer(UI 线程中调用)
|
|||
|
|
currentImage.Lock();
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
// BackBuffer 为 IntPtr,使用 Marshal.Copy 从 byte[] 拷贝
|
|||
|
|
System.Runtime.InteropServices.Marshal.Copy(buffer, 0, currentImage.BackBuffer, (int)requiredLength);
|
|||
|
|
currentImage.AddDirtyRect(new Int32Rect(0, 0, width, height));
|
|||
|
|
}
|
|||
|
|
finally
|
|||
|
|
{
|
|||
|
|
currentImage.Unlock();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return currentImage;
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
($"CameraViewModel.UpdateBitmap exception: {ex}").LogSysError();
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public CameraViewModel(IEventAggregator eventAggregator, AppCameraType cameraType)
|
|||
|
|
{
|
|||
|
|
_eventAggregator = eventAggregator;
|
|||
|
|
CameraType = cameraType;
|
|||
|
|
_eventAggregator?.Subscribe(this);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void Handle(DisplayImageEventArgs message)
|
|||
|
|
{
|
|||
|
|
if (message == null || message.CameraType != CameraType)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
CommonUti.RunOnUi(() =>
|
|||
|
|
{
|
|||
|
|
WriteableBitmap bmp = UpdateBitmap(message.CameraData);
|
|||
|
|
// 只有在成功生成 bitmap 时才更新 DisplayImage,避免把 null 赋值导致 UI 清空
|
|||
|
|
if (bmp != null)
|
|||
|
|
{
|
|||
|
|
DisplayImage = bmp;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void Handle(DisplayFindTemplateResultEventArgs message)
|
|||
|
|
{
|
|||
|
|
if (message == null || message.CameraType != CameraType)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
CommonUti.RunOnUi(() =>
|
|||
|
|
{
|
|||
|
|
ApplyTemplateResultShapes(message);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void ApplyTemplateResultShapes(DisplayFindTemplateResultEventArgs message)
|
|||
|
|
{
|
|||
|
|
if (message.ClearShapesBeforeDisplay)
|
|||
|
|
{
|
|||
|
|
Shapes.Clear();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (message.Result == null)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
AppendCenterShapes(message.Result);
|
|||
|
|
AppendContourShape(message.Result.Contour);
|
|||
|
|
AppendCircleShapes(message.Result.Circles);
|
|||
|
|
AppendLineShapes(message.Result.Lines);
|
|||
|
|
AppendRectangleShapes(message.Result.Rectangles);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void AppendCenterShapes(FindTemplateResult result)
|
|||
|
|
{
|
|||
|
|
if (result == null || !AreFinite(result.CenterX, result.CenterY, result.Score))
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
ShapeDrawing pointShape = new ShapeDrawing();
|
|||
|
|
pointShape.X = result.CenterX;
|
|||
|
|
pointShape.Y = result.CenterY;
|
|||
|
|
Shapes.Add(pointShape);
|
|||
|
|
|
|||
|
|
TextShapeDrawing textShape = new TextShapeDrawing();
|
|||
|
|
textShape.X = result.CenterX;
|
|||
|
|
textShape.Y = result.CenterY;
|
|||
|
|
textShape.Text = $"Score:{result.Score:F3}";
|
|||
|
|
Shapes.Add(textShape);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void AppendContourShape(FindTemplateContourResult contour)
|
|||
|
|
{
|
|||
|
|
if (contour == null || !AreFinite(contour.StartX, contour.StartY, contour.EndX, contour.EndY))
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
RectShapeDrawing contourShape = new RectShapeDrawing();
|
|||
|
|
contourShape.X = contour.StartX;
|
|||
|
|
contourShape.Y = contour.StartY;
|
|||
|
|
contourShape.X1 = contour.EndX;
|
|||
|
|
contourShape.Y1 = contour.EndY;
|
|||
|
|
Shapes.Add(contourShape);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void AppendCircleShapes(IList<FindTemplateCircleResult> circles)
|
|||
|
|
{
|
|||
|
|
if (circles == null)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach (FindTemplateCircleResult circle in circles)
|
|||
|
|
{
|
|||
|
|
if (circle == null || !AreFinite(circle.CenterX, circle.CenterY, circle.Radius))
|
|||
|
|
{
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
CircleShapeDrawing circleShape = new CircleShapeDrawing();
|
|||
|
|
circleShape.X = circle.CenterX;
|
|||
|
|
circleShape.Y = circle.CenterY;
|
|||
|
|
circleShape.CenterPoint = new Point(circle.CenterX, circle.CenterY);
|
|||
|
|
circleShape.RadiusX = circle.Radius;
|
|||
|
|
circleShape.RadiusY = circle.Radius;
|
|||
|
|
circleShape.LineWidth = 1;
|
|||
|
|
circleShape.FillColor = Brushes.Blue;
|
|||
|
|
Shapes.Add(circleShape);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void AppendLineShapes(IList<FindTemplateLineResult> lines)
|
|||
|
|
{
|
|||
|
|
if (lines == null)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach (FindTemplateLineResult line in lines)
|
|||
|
|
{
|
|||
|
|
if (line == null || !AreFinite(line.StartX, line.StartY, line.EndX, line.EndY))
|
|||
|
|
{
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
LineShapeDrawing lineShape = new LineShapeDrawing();
|
|||
|
|
lineShape.X = line.StartX;
|
|||
|
|
lineShape.Y = line.StartY;
|
|||
|
|
lineShape.X1 = line.EndX;
|
|||
|
|
lineShape.Y1 = line.EndY;
|
|||
|
|
Shapes.Add(lineShape);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void AppendRectangleShapes(IList<FindTemplateRectangleResult> rectangles)
|
|||
|
|
{
|
|||
|
|
if (rectangles == null)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach (FindTemplateRectangleResult rectangle in rectangles)
|
|||
|
|
{
|
|||
|
|
if (rectangle == null || !AreFinite(rectangle.StartX, rectangle.StartY, rectangle.Width, rectangle.Height, rectangle.Angle))
|
|||
|
|
{
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
double halfWidth = rectangle.Width / 2d;
|
|||
|
|
double halfHeight = rectangle.Height / 2d;
|
|||
|
|
double cosAngle = Math.Cos(rectangle.Angle);
|
|||
|
|
double sinAngle = Math.Sin(rectangle.Angle);
|
|||
|
|
double centerX = rectangle.StartX + (halfWidth * cosAngle) - (halfHeight * sinAngle);
|
|||
|
|
double centerY = rectangle.StartY + (halfWidth * sinAngle) + (halfHeight * cosAngle);
|
|||
|
|
|
|||
|
|
RectShape2Drawing rectangleShape = new RectShape2Drawing();
|
|||
|
|
rectangleShape.X = centerX;
|
|||
|
|
rectangleShape.Y = centerY;
|
|||
|
|
rectangleShape.CenterX = centerX;
|
|||
|
|
rectangleShape.CenterY = centerY;
|
|||
|
|
rectangleShape.HalfWidth = halfWidth;
|
|||
|
|
rectangleShape.HalfHeigth = halfHeight;
|
|||
|
|
rectangleShape.Angle = rectangle.Angle;
|
|||
|
|
Shapes.Add(rectangleShape);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static bool AreFinite(params double[] values)
|
|||
|
|
{
|
|||
|
|
if (values == null)
|
|||
|
|
{
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach (double value in values)
|
|||
|
|
{
|
|||
|
|
if (double.IsNaN(value) || double.IsInfinity(value))
|
|||
|
|
{
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|