标定面板图像显示控件替换为PolygonRoiCanvas,删除旧ImageCanvasControl

This commit is contained in:
李伟
2026-05-27 09:22:56 +08:00
parent bc626a0ca8
commit b3d39c3492
7 changed files with 73 additions and 363 deletions
@@ -5,6 +5,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:cal="clr-namespace:XP.Camera.Calibration"
xmlns:controls="clr-namespace:XP.Camera.Calibration.Controls"
xmlns:roi="clr-namespace:XP.ImageProcessing.RoiControl.Controls;assembly=XP.ImageProcessing.RoiControl"
mc:Ignorable="d"
d:DesignHeight="850"
d:DesignWidth="1400">
@@ -88,6 +89,20 @@
<Image Source="/XP.Camera;component/Calibration/Resources/外部导入.png" Width="24" Height="24" />
</Button.Tag>
</Button>
<Button Content="采集当前点"
Command="{Binding CapturePointCommand}" FontFamily="Segoe UI"
Style="{StaticResource ToolbarButtonStyle}">
<Button.Tag>
<Image Source="/XP.Camera;component/Calibration/Resources/执行.png" Width="24" Height="24" />
</Button.Tag>
</Button>
<Button Content="删除选中"
Command="{Binding DeleteSelectedPointCommand}" FontFamily="Segoe UI"
Style="{StaticResource ToolbarButtonStyle}">
<Button.Tag>
<TextBlock Text="✕" FontSize="20" Foreground="Red" HorizontalAlignment="Center" />
</Button.Tag>
</Button>
<Button Content="{Binding Source={StaticResource LocalizedStrings}, Path=CalibrationExecute}"
Command="{Binding CalibrateCommand}" FontFamily="Segoe UI"
Style="{StaticResource ToolbarButtonStyle}">
@@ -113,6 +128,32 @@
VerticalAlignment="Center" FontFamily="Segoe UI"
IsChecked="{Binding ShowWorldCoordinates}"
Margin="10,0,0,0" FontSize="13" Foreground="{StaticResource TextColor}" />
<ToggleButton IsChecked="{Binding IsLiveView}" VerticalAlignment="Center" Margin="10,0,0,0"
Width="80" Height="66" Cursor="Hand" FontFamily="Segoe UI">
<ToggleButton.Template>
<ControlTemplate TargetType="ToggleButton">
<Border x:Name="Bd" Background="White" BorderBrush="#E1E1E1"
BorderThickness="1" CornerRadius="6">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock x:Name="Icon" Text="▶" FontSize="20" HorizontalAlignment="Center" Margin="0,2,0,3" />
<TextBlock x:Name="Label" Text="实时" FontSize="10.5" HorizontalAlignment="Center" />
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="Icon" Property="Text" Value="⏸" />
<Setter TargetName="Label" Property="Text" Value="冻结" />
<Setter TargetName="Bd" Property="Background" Value="#E8F5E9" />
<Setter TargetName="Bd" Property="BorderBrush" Value="#81C784" />
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Bd" Property="Background" Value="#EAF2FB" />
<Setter TargetName="Bd" Property="BorderBrush" Value="#B0D4F1" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ToggleButton.Template>
</ToggleButton>
</StackPanel>
</Border>
@@ -136,6 +177,7 @@
<DataGrid Grid.Row="1" AutoGenerateColumns="False" CanUserAddRows="True"
ItemsSource="{Binding CalibrationPoints}"
SelectedItem="{Binding SelectedPoint}"
HeadersVisibility="Column" GridLinesVisibility="All"
FontFamily="Segoe UI"
BorderBrush="{StaticResource BorderColor}" BorderThickness="1">
@@ -159,7 +201,8 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<controls:ImageCanvasControl Grid.Row="0" x:Name="imageCanvas" Margin="12,12,12,8" />
<roi:PolygonRoiCanvas Grid.Row="0" x:Name="roiCanvas" Margin="12,12,12,8"
ImageSource="{Binding ImageSource}" />
<Border Grid.Row="1" Background="White" BorderBrush="{StaticResource BorderColor}" BorderThickness="1"
Margin="12,0,12,12" Padding="12" MinHeight="80">
@@ -1,10 +1,9 @@
using System.Drawing;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using XP.Camera.Calibration.ViewModels;
using WpfBrushes = System.Windows.Media.Brushes;
using WpfColor = System.Windows.Media.Color;
using XP.ImageProcessing.RoiControl.Controls;
namespace XP.Camera.Calibration.Controls;
@@ -23,82 +22,42 @@ public partial class CalibrationControl : UserControl
if (DataContext is CalibrationViewModel viewModel)
{
_viewModel = viewModel;
_viewModel.PropertyChanged += OnViewModelPropertyChanged;
}
}
_viewModel.ImageLoadedRequested += (s, e) =>
private void OnViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
imageCanvas.ReferenceImage = _viewModel.ImageSource;
imageCanvas.RoiCanvas.Children.Clear();
if (e.PropertyName == nameof(CalibrationViewModel.OverlayImage))
{
UpdateDetectionOverlay();
}
}
private void UpdateDetectionOverlay()
{
if (_viewModel?.OverlayImage == null)
{
roiCanvas.ClearDetectionOverlay();
return;
}
var overlayCanvas = new Canvas
{
Width = _viewModel.OverlayImage.PixelWidth,
Height = _viewModel.OverlayImage.PixelHeight,
IsHitTestVisible = false
};
imageCanvas.CanvasRightMouseUp += ImageCanvas_RightMouseUp;
imageCanvas.CanvasMouseWheel += ImageCanvas_MouseWheel;
}
}
private void ImageCanvas_MouseWheel(object? sender, MouseWheelEventArgs e)
var image = new System.Windows.Controls.Image
{
if (_viewModel?.CurrentImage == null) return;
double zoom = e.Delta > 0 ? 1.1 : 0.9;
imageCanvas.ZoomScale *= zoom;
imageCanvas.ZoomScale = Math.Max(0.1, Math.Min(imageCanvas.ZoomScale, 10));
}
private void ImageCanvas_RightMouseUp(object? sender, MouseButtonEventArgs e)
{
if (_viewModel?.CurrentImage == null) return;
var pos = e.GetPosition(imageCanvas.RoiCanvas);
float imageX = (float)pos.X;
float imageY = (float)pos.Y;
if (imageX >= 0 && imageX < _viewModel.CurrentImage.Width &&
imageY >= 0 && imageY < _viewModel.CurrentImage.Height)
{
var pixelPoint = new PointF(imageX, imageY);
var worldPoint = _viewModel.ConvertPixelToWorld(pixelPoint);
_viewModel.StatusText = $"像素坐标: ({imageX:F2}, {imageY:F2})\n世界坐标: ({worldPoint.X:F2}, {worldPoint.Y:F2})";
DrawMarkerOnCanvas(imageX, imageY, worldPoint);
}
}
private void DrawMarkerOnCanvas(float imageX, float imageY, PointF worldPoint)
{
imageCanvas.RoiCanvas.Children.Clear();
var ellipse = new System.Windows.Shapes.Ellipse
{
Width = 10, Height = 10,
Stroke = WpfBrushes.Red, StrokeThickness = 2,
Fill = WpfBrushes.Transparent
Source = _viewModel.OverlayImage,
Width = _viewModel.OverlayImage.PixelWidth,
Height = _viewModel.OverlayImage.PixelHeight,
IsHitTestVisible = false
};
Canvas.SetLeft(ellipse, imageX - 5);
Canvas.SetTop(ellipse, imageY - 5);
imageCanvas.RoiCanvas.Children.Add(ellipse);
var pixelText = new TextBlock
{
Text = $"P:({imageX:F0},{imageY:F0})",
Foreground = WpfBrushes.Red, FontSize = 12,
Background = new System.Windows.Media.SolidColorBrush(WpfColor.FromArgb(180, 255, 255, 255))
};
Canvas.SetLeft(pixelText, imageX + 10);
Canvas.SetTop(pixelText, imageY - 20);
imageCanvas.RoiCanvas.Children.Add(pixelText);
if (_viewModel?.ShowWorldCoordinates == true)
{
var worldText = new TextBlock
{
Text = $"W:({worldPoint.X:F2},{worldPoint.Y:F2})",
Foreground = WpfBrushes.Blue, FontSize = 12,
Background = new System.Windows.Media.SolidColorBrush(WpfColor.FromArgb(180, 255, 255, 255))
};
Canvas.SetLeft(worldText, imageX + 10);
Canvas.SetTop(worldText, imageY + 5);
imageCanvas.RoiCanvas.Children.Add(worldText);
}
overlayCanvas.Children.Add(image);
roiCanvas.SetDetectionOverlayCanvas(overlayCanvas);
}
}
@@ -5,6 +5,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:cal="clr-namespace:XP.Camera.Calibration"
xmlns:controls="clr-namespace:XP.Camera.Calibration.Controls"
xmlns:roi="clr-namespace:XP.ImageProcessing.RoiControl.Controls;assembly=XP.ImageProcessing.RoiControl"
mc:Ignorable="d"
d:DesignHeight="900"
d:DesignWidth="1600">
@@ -168,7 +169,8 @@
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<controls:ImageCanvasControl Grid.Row="0" x:Name="imageCanvas" Margin="12,12,8,8" />
<roi:PolygonRoiCanvas Grid.Row="0" x:Name="roiCanvas" Margin="12,12,8,8"
ImageSource="{Binding ImageSource}" />
<Border Grid.Row="1" Background="White" BorderBrush="{StaticResource BorderColor}" BorderThickness="1"
Margin="12,0,8,12" Padding="12" Height="70">
<Grid>
@@ -1,46 +1,13 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using XP.Camera.Calibration.ViewModels;
namespace XP.Camera.Calibration.Controls;
public partial class ChessboardCalibrationControl : UserControl
{
private ChessboardCalibrationViewModel? _viewModel;
public ChessboardCalibrationControl()
{
InitializeComponent();
Loaded += ChessboardCalibrationControl_Loaded;
}
private void ChessboardCalibrationControl_Loaded(object sender, RoutedEventArgs e)
{
if (DataContext is ChessboardCalibrationViewModel viewModel)
{
_viewModel = viewModel;
_viewModel.ImageLoadedRequested += (s, e) =>
{
imageCanvas.ReferenceImage = _viewModel.ImageSource;
};
_viewModel.ImageClearedRequested += (s, e) =>
{
imageCanvas.ReferenceImage = null;
};
imageCanvas.CanvasMouseWheel += ImageCanvas_MouseWheel;
}
}
private void ImageCanvas_MouseWheel(object? sender, MouseWheelEventArgs e)
{
if (_viewModel?.ImageSource == null) return;
double zoom = e.Delta > 0 ? 1.1 : 0.9;
imageCanvas.ZoomScale *= zoom;
imageCanvas.ZoomScale = Math.Max(0.1, Math.Min(imageCanvas.ZoomScale, 10));
}
}
@@ -1,33 +0,0 @@
<UserControl x:Class="XP.Camera.Calibration.Controls.ImageCanvasControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="600" d:DesignWidth="800" x:Name="imageCanvasControl">
<Border ClipToBounds="True" RenderOptions.BitmapScalingMode="NearestNeighbor">
<Viewbox>
<AdornerDecorator x:Name="adorner" MouseWheel="Adorner_MouseWheel">
<AdornerDecorator.RenderTransform>
<TransformGroup>
<TranslateTransform X="{Binding PanningOffsetX, ElementName=imageCanvasControl}"
Y="{Binding PanningOffsetY, ElementName=imageCanvasControl}" />
<ScaleTransform ScaleX="{Binding ZoomScale, ElementName=imageCanvasControl}"
ScaleY="{Binding ZoomScale, ElementName=imageCanvasControl}"
CenterX="{Binding ZoomCenter.X, ElementName=imageCanvasControl}"
CenterY="{Binding ZoomCenter.Y, ElementName=imageCanvasControl}" />
</TransformGroup>
</AdornerDecorator.RenderTransform>
<Grid PreviewMouseMove="Canvas_MouseMove"
PreviewMouseLeftButtonUp="Canvas_MouseLeftButtonUp"
PreviewMouseRightButtonUp="Canvas_MouseRightButtonUp"
MouseEnter="Canvas_MouseEnter"
PreviewMouseLeftButtonDown="Canvas_MouseLeftButtonDown"
PreviewMouseRightButtonDown="Canvas_MouseRightButtonDown">
<ContentPresenter Content="{Binding RoiCanvas, ElementName=imageCanvasControl}"
SizeChanged="ContentPresenter_SizeChanged" />
</Grid>
</AdornerDecorator>
</Viewbox>
</Border>
</UserControl>
@@ -1,229 +0,0 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace XP.Camera.Calibration.Controls;
/// <summary>
/// 图像画布控件 - 提供图像显示、缩放、平移功能
/// </summary>
public partial class ImageCanvasControl : UserControl
{
private Point mouseDownPoint = new Point();
#region Dependency Properties
public static readonly DependencyProperty ZoomScaleProperty =
DependencyProperty.Register("ZoomScale", typeof(double), typeof(ImageCanvasControl), new PropertyMetadata(1.0));
public static readonly DependencyProperty ZoomCenterProperty =
DependencyProperty.Register("ZoomCenter", typeof(Point), typeof(ImageCanvasControl), new PropertyMetadata(new Point()));
public static readonly DependencyProperty PanningOffsetXProperty =
DependencyProperty.Register("PanningOffsetX", typeof(double), typeof(ImageCanvasControl), new PropertyMetadata(0.0));
public static readonly DependencyProperty PanningOffsetYProperty =
DependencyProperty.Register("PanningOffsetY", typeof(double), typeof(ImageCanvasControl), new PropertyMetadata(0.0));
public static readonly DependencyProperty ReferenceImageProperty =
DependencyProperty.Register("ReferenceImage", typeof(BitmapSource), typeof(ImageCanvasControl),
new UIPropertyMetadata(null, ReferenceImageChanged));
public static readonly DependencyProperty ImageScaleFactorProperty =
DependencyProperty.Register("ImageScaleFactor", typeof(double), typeof(ImageCanvasControl), new PropertyMetadata(1.0));
public static readonly DependencyProperty MaxImageWidthProperty =
DependencyProperty.Register("MaxImageWidth", typeof(int), typeof(ImageCanvasControl), new PropertyMetadata(0));
public static readonly DependencyProperty MaxImageHeightProperty =
DependencyProperty.Register("MaxImageHeight", typeof(int), typeof(ImageCanvasControl), new PropertyMetadata(0));
public static readonly DependencyProperty EnablePanningProperty =
DependencyProperty.Register("EnablePanning", typeof(bool), typeof(ImageCanvasControl), new PropertyMetadata(true));
#endregion
#region Properties
public double ZoomScale
{
get => (double)GetValue(ZoomScaleProperty);
set => SetValue(ZoomScaleProperty, value);
}
public Point ZoomCenter
{
get => (Point)GetValue(ZoomCenterProperty);
set => SetValue(ZoomCenterProperty, value);
}
public double PanningOffsetX
{
get => (double)GetValue(PanningOffsetXProperty);
set => SetValue(PanningOffsetXProperty, value);
}
public double PanningOffsetY
{
get => (double)GetValue(PanningOffsetYProperty);
set => SetValue(PanningOffsetYProperty, value);
}
public BitmapSource? ReferenceImage
{
get => (BitmapSource?)GetValue(ReferenceImageProperty);
set => SetValue(ReferenceImageProperty, value);
}
public double ImageScaleFactor
{
get => (double)GetValue(ImageScaleFactorProperty);
set => SetValue(ImageScaleFactorProperty, value);
}
public int MaxImageWidth
{
get => (int)GetValue(MaxImageWidthProperty);
set => SetValue(MaxImageWidthProperty, value);
}
public int MaxImageHeight
{
get => (int)GetValue(MaxImageHeightProperty);
set => SetValue(MaxImageHeightProperty, value);
}
public bool EnablePanning
{
get => (bool)GetValue(EnablePanningProperty);
set => SetValue(EnablePanningProperty, value);
}
private Canvas roiCanvas = new Canvas();
public Canvas RoiCanvas
{
get => roiCanvas;
set => roiCanvas = value;
}
#endregion
#region Events
public event EventHandler<MouseButtonEventArgs>? CanvasRightMouseUp;
public event EventHandler<MouseButtonEventArgs>? CanvasRightMouseDown;
public event EventHandler<MouseButtonEventArgs>? CanvasLeftMouseDown;
public event EventHandler<MouseEventArgs>? CanvasMouseMove;
public event EventHandler<MouseWheelEventArgs>? CanvasMouseWheel;
#endregion
public ImageCanvasControl()
{
InitializeComponent();
}
#region Private Methods
private static void ReferenceImageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as ImageCanvasControl)?.OnReferenceImageChanged(e.NewValue as BitmapSource);
}
private void OnReferenceImageChanged(BitmapSource? bitmapSource)
{
if (bitmapSource != null)
{
ImageBrush brush = new ImageBrush { ImageSource = bitmapSource, Stretch = Stretch.Uniform };
RoiCanvas.Background = brush;
RoiCanvas.Height = bitmapSource.Height;
RoiCanvas.Width = bitmapSource.Width;
}
else
{
RoiCanvas.Height = MaxImageHeight > 0 ? MaxImageHeight : 600;
RoiCanvas.Width = MaxImageWidth > 0 ? MaxImageWidth : 800;
RoiCanvas.Background = Brushes.LightGray;
}
}
private double CalculateScaleFactor()
{
if (ActualWidth <= 0) return 1;
double scaleFactor = Math.Max(RoiCanvas.Width / ActualWidth, RoiCanvas.Height / ActualHeight);
if (scaleFactor < 0)
scaleFactor = Math.Min(RoiCanvas.Width / ActualWidth, RoiCanvas.Height / ActualHeight);
return scaleFactor;
}
#endregion
#region Event Handlers
private void Canvas_MouseEnter(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
mouseDownPoint = e.GetPosition(RoiCanvas);
}
private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
CanvasMouseMove?.Invoke(sender, e);
if (EnablePanning && e.LeftButton == MouseButtonState.Pressed)
{
Point mousePoint = e.GetPosition(RoiCanvas);
double mouseMoveLength = Point.Subtract(mousePoint, mouseDownPoint).Length;
if (mouseMoveLength > (10 * CalculateScaleFactor()) / ZoomScale)
{
PanningOffsetX += mousePoint.X - mouseDownPoint.X;
PanningOffsetY += mousePoint.Y - mouseDownPoint.Y;
}
}
}
private void Canvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) { }
private void Canvas_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
{
CanvasRightMouseUp?.Invoke(sender, e);
}
private void Canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
mouseDownPoint = e.GetPosition(RoiCanvas);
CanvasLeftMouseDown?.Invoke(sender, e);
if (EnablePanning && e.ClickCount == 2)
{
ResetView();
e.Handled = true;
}
}
private void Canvas_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
CanvasRightMouseDown?.Invoke(sender, e);
e.Handled = true;
}
private void Adorner_MouseWheel(object sender, MouseWheelEventArgs e)
{
CanvasMouseWheel?.Invoke(sender, e);
}
private void ContentPresenter_SizeChanged(object sender, SizeChangedEventArgs e)
{
ImageScaleFactor = CalculateScaleFactor();
}
private void ResetView()
{
ZoomScale = 1.0;
PanningOffsetX = 0.0;
PanningOffsetY = 0.0;
ZoomCenter = new Point(0, 0);
}
#endregion
}
+1
View File
@@ -26,6 +26,7 @@
<PackageReference Include="Prism.Wpf" Version="9.0.537" />
<PackageReference Include="Serilog" Version="4.3.1" />
<ProjectReference Include="..\XP.Common\XP.Common.csproj" />
<ProjectReference Include="..\XP.ImageProcessing.RoiControl\XP.ImageProcessing.RoiControl.csproj" />
</ItemGroup>
<ItemGroup>