视觉ROI调试工具
视觉ROI调试工具
项目整体介绍
左区为功能区和信息显示,右区为ROI交互调试
项目总体为ImageViewBox+ROIRectangle两个自定义工具和主界面MainWindow
图像显示工具
///图像数据结构
public struct ImgStruct_t
{
public int Channels;
public int Width;
public int Height;
public IntPtr ImgDataAddr;
public ImgStruct_t()
{
Channels = 0;
Width = 0;
Height = 0;
ImgDataAddr = IntPtr.Zero;
}
}
MainWindow中点击选着图像触发事件
<Button Content="选择图像" Command="{Binding SelectImageCommand}" />
/// <summary>
/// 源图
/// </summary>
public WriteableBitmap SourceBitmap { get; set; }
/// <summary>
/// 源图数据
/// </summary>
internal ImgStruct_t SourceImgData = new ImgStruct_t();
public string BMPFileName
{
get => bmpFileName; set
{
if (string.IsNullOrEmpty(value) || !File.Exists(value)) return;
//读取图像信息
ImageHelper.ReadImage(value, ref SourceImgData, true);
//初始化WriteableBitmap
SourceBitmap = ImageHelper.UpdateBitmapSize(SourceBitmap, ref SourceImgData);
//读取图像并获取指针
HalconTool.ReadImage(0, value, "", ref SourceImgData);
///锁定内存位置,写入数据
SourceBitmap.Lock();
SourceBitmap.AddDirtyRect(new Int32Rect(0, 0, SourceImgData.Width, SourceImgData.Height));
///解锁用于显示
SourceBitmap.Unlock();
bmpFileName = value;
//DrawingType = DrawingType.OriginalDrawing;
}
}
string bmpFileName;
public ICommand SelectImageCommand
{
get => new DelegateCommand(() =>
{
string initDir = System.IO.Path.GetDirectoryName(BMPFileName);
BMPFileName = ControlHelper.SelectFile("请选择图像文件", "图像文件(*.bmp, *.jpg,*.tiff,*.png,*.jpeg)|*.bmp;*.jpg;*.tiff;*.png;*.jpeg", initDir);
//UpdateFileList();
});
}
Halcon读取本地图像
//
// 摘要:
// 读取本地图片,若Img的Ptr为0,则自动申请。若img的Ptr不为0,则按照img参数读取
//
// 参数:
// path:
// 文件路径
//
// img:
// 图像数据
//
// readInfoOnly:
// 是否只读信息
//
// 返回结果:
// 是否读取成功
public static bool ReadImage(string path, ref ImgStruct_t img, bool readInfoOnly = false)
{
if (string.IsNullOrEmpty(path) || !File.Exists(path))
{
return false;
}
if (img.ImgDataAddr == IntPtr.Zero || readInfoOnly)
{
FileStream fileStream = File.OpenRead(path);
Image image = Image.FromStream(fileStream, useEmbeddedColorManagement: false, validateImageData: false);
fileStream.Close();
string text = image.PixelFormat.ToString();
if (!text.StartsWith("Format"))
{
MessageBox.Show($"{image.PixelFormat}不支持!");
return false;
}
int num = text.IndexOf('b');
int channels = int.Parse(text.Substring(6, num - 6)) / 8;
img.Width = image.Width;
img.Height = image.Height;
img.Channels = channels;
if (readInfoOnly)
{
return true;
}
img.ImgDataAddr = Marshal.AllocHGlobal(img.Width * img.Height * img.Channels);
}
//ContainerLocator.Container.Resolve<IVisualOperator>().ReadImage(0, path, "", ref img);
HalconTool.ReadImage(0, path, "", ref img);
return true;
}
根据图像信息初始化WriteableBitmap
public static WriteableBitmap UpdateBitmapSize(WriteableBitmap source, ref ImgStruct_t imgStruct_T)
{
if (source == null || source.Format.BitsPerPixel / 8 != imgStruct_T.Channels || source.PixelWidth != imgStruct_T.Width || source.PixelHeight != imgStruct_T.Height)
{
PixelFormat pixelFormat = PixelFormats.Indexed8;
if (imgStruct_T.Channels == 1)
{
pixelFormat = PixelFormats.Gray8;
}
else if (imgStruct_T.Channels == 2)
{
pixelFormat = PixelFormats.Gray16;
}
else if (imgStruct_T.Channels == 3)
{
pixelFormat = PixelFormats.Rgb24;
}
else if (imgStruct_T.Channels == 4)
{
pixelFormat = PixelFormats.Bgr32;
}
source = new WriteableBitmap(imgStruct_T.Width, imgStruct_T.Height, 96.0, 96.0, pixelFormat, null);
}
imgStruct_T.ImgDataAddr = source.BackBuffer;
return source;
}
ImageViewBox代码
<Grid x:Class="ROITool.ImageViewBox.ImageViewBox"
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"
xmlns:local="clr-namespace:ROITool.ImageViewBox"
xmlns:local1="clr-namespace:ROITool.ROIRectangle"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" >
<Border Grid.Row="1" ClipToBounds="True" ><!--ClipToBounds="True"限制Viewbox显示范围在Border内-->
<Viewbox x:Name="MyViewbox" Stretch="Uniform" >
<Viewbox.RenderTransform>
<TransformGroup>
<ScaleTransform x:Name="ScaleTransform" />
<TranslateTransform x:Name="TranslateTransform" />
</TransformGroup>
</Viewbox.RenderTransform>
<Grid local1:ROIRectangle.Selected="ROI_SelectedHandle">
<Image x:Name="DisplayImage" Stretch="UniformToFill" />
</Grid>
</Viewbox>
</Border>
</Grid>
ImageViewBox添加ImageSource依赖属性和鼠标事件
public partial class ImageViewBox : Grid
{
public ImageViewBox()
{
InitializeComponent();
// 监听鼠标滚轮事件
this.MouseWheel += MainWindow_PreviewMouseWheel;
// 监听鼠标按下事件
this.MouseLeftButtonDown += MainWindow_PreviewMouseDown;
// 监听鼠标移动事件
this.MouseMove += MainWindow_PreviewMouseMove;
// 监听鼠标释放事件
this.MouseLeftButtonUp += MainWindow_PreviewMouseUp;
}
public static DependencyProperty ImageSourceProperty = DependencyProperty.Register("ImageSource", typeof(WriteableBitmap), typeof(ImageViewBox), new PropertyMetadata(null, delegate (DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is ImageViewBox imageViewBox6)
{
imageViewBox6.UpdateDisplayBitmap((d as ImageViewBox).Name);
}
}));
public WriteableBitmap ImageSource
{
get
{
return (WriteableBitmap)GetValue(ImageSourceProperty);
}
set
{
SetValue(ImageSourceProperty, value);
}
}
private void MainWindow_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
// 检查是否按下了 Ctrl 键
//if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
//{
// 获取鼠标相对于 Viewbox 的位置
Point mousePosition = e.GetPosition(MyViewbox);
// 计算缩放因子
double scaleFactor = e.Delta > 0 ? 1.1 : 0.9;
// 以鼠标为中心进行缩放
ZoomAtPosition(mousePosition, scaleFactor);
// 标记事件已处理,防止继续传递
e.Handled = true;
//}
}
private void ZoomAtPosition(Point position, double scaleFactor)
{
// 获取当前的缩放和平移变换
double currentScale = ScaleTransform.ScaleX;
double newScale = currentScale * scaleFactor;
// 计算缩放后的偏移量
double offsetX = position.X * (1 - scaleFactor);
double offsetY = position.Y * (1 - scaleFactor);
// 应用新的缩放比例
ScaleTransform.ScaleX = newScale;
ScaleTransform.ScaleY = newScale;
// 更新平移变换
TranslateTransform.X += offsetX;
TranslateTransform.Y += offsetY;
}
private void MainWindow_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
// 检查是否按下了鼠标左键
if (e.LeftButton == MouseButtonState.Pressed)
{
// 记录鼠标按下的初始位置
_startPoint = e.GetPosition(this);
_isDragging = true;
// 捕获鼠标,确保即使鼠标移出窗口也能继续拖动
this.CaptureMouse();
}
}
private void MainWindow_PreviewMouseMove(object sender, MouseEventArgs e)
{
Point Point = e.GetPosition(DisplayImage);
// 如果正在拖动
if (_isDragging)
{
// 获取当前鼠标位置
Point currentPoint = e.GetPosition(this);
// 计算鼠标移动的偏移量
double offsetX = currentPoint.X - _startPoint.X;
double offsetY = currentPoint.Y - _startPoint.Y;
// 更新平移变换
TranslateTransform.X += offsetX;
TranslateTransform.Y += offsetY;
// 更新初始位置
_startPoint = currentPoint;
}
}
private void MainWindow_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
// 如果释放了鼠标左键
if (e.LeftButton == MouseButtonState.Released && _isDragging)
{
// 结束拖动
_isDragging = false;
// 释放鼠标捕获
this.ReleaseMouseCapture();
}
}
}
MainWindow使用控件
<controls:ImageViewBox Grid.Column="1" ImageSource="{Binding SourceBitmap}" SelectedROIChanged="ROI_SelectedROIChanged">
</controls:ImageViewBox>
ROI调试工具
ROIRectangle继承Canvas,添加ROI位置信息相关的依赖属性,添加鼠标事件
/// <summary>
/// ROIRectangle.xaml 的交互逻辑
/// </summary>
public partial class ROIRectangle : Canvas
{
/// <summary>
/// ROI宽度依赖属性
/// </summary>
public static DependencyProperty ROIWidthProperty =
DependencyProperty.Register(
"ROIWidth",
typeof(double),
typeof(ROIRectangle),
new FrameworkPropertyMetadata(
0.0,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
delegate (DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is ROIRectangle rOIRectangle)
{
rOIRectangle.UpdateROI();
}
},
null,
false,
UpdateSourceTrigger.PropertyChanged)
);
public double ROIWidth
{
get
{
return (double)GetValue(ROIWidthProperty);
}
set
{
SetValue(ROIWidthProperty, value);
}
}
/// <summary>
/// ROI高度依赖属性
/// </summary>
public static DependencyProperty ROIHeightProperty =
DependencyProperty.Register(
"ROIHeight",
typeof(double),
typeof(ROIRectangle),
new FrameworkPropertyMetadata(
0.0,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
delegate (DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is ROIRectangle rOIRectangle)
{
rOIRectangle.UpdateROI();
}
},
null,
false,
UpdateSourceTrigger.PropertyChanged)
);
public double ROIHeight
{
get
{
return (double)GetValue(ROIHeightProperty);
}
set
{
SetValue(ROIHeightProperty, value);
}
}
/// <summary>
/// ROI左上角X坐标
/// </summary>
public static DependencyProperty ROILeftTopXProperty = DependencyProperty.Register(
"ROILeftTopX",
typeof(double),
typeof(ROIRectangle),
new FrameworkPropertyMetadata(
0.0,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
delegate (DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is ROIRectangle rOIRectangle)
{
rOIRectangle.UpdateROI();
}
},
null,
false,
UpdateSourceTrigger.PropertyChanged)
);
public double ROILeftTopX
{
get
{
return (double)GetValue(ROILeftTopXProperty);
}
set
{
SetValue(ROILeftTopXProperty, value);
}
}
/// <summary>
/// ROI右上角Y坐标
/// </summary>
public static DependencyProperty ROILeftTopYProperty =
DependencyProperty.Register(
"ROILeftTopY",
typeof(double),
typeof(ROIRectangle),
new FrameworkPropertyMetadata(
0.0,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
delegate (DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is ROIRectangle rOIRectangle)
{
rOIRectangle.UpdateROI();
}
},
null,
false,
UpdateSourceTrigger.PropertyChanged)
);
public double ROILeftTopY
{
get
{
return (double)GetValue(ROILeftTopYProperty);
}
set
{
SetValue(ROILeftTopYProperty, value);
}
}
/// <summary>
/// 更新ROI位置
/// </summary>
private void UpdateROI()
{
this.grid.Width = ROIWidth;
this.grid.Height = ROIHeight;
this.grid.SetValue(Canvas.LeftProperty, ROILeftTopX);
this.grid.SetValue(Canvas.TopProperty, ROILeftTopY);
}
/// <summary>
/// 自定义路由事件
/// </summary>
public static readonly RoutedEvent SelectedEvent =
EventManager.RegisterRoutedEvent(
"Selected",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(ROIRectangle));
public event RoutedEventHandler Selected
{
add
{
AddHandler(SelectedEvent, value);
}
remove
{
RemoveHandler(SelectedEvent, value);
}
}
/// <summary>
/// 触发路由事件
/// </summary>
protected virtual void OnCustomClick()
{
RoutedEventArgs args = new RoutedEventArgs(SelectedEvent, this);
RaiseEvent(args);
}
public ROIRectangle()
{
InitializeComponent();
}
Rectangle rectangle;
private Point _startPoint; // 记录鼠标按下的初始位置
private bool _isDragging; // 标记是否正在拖动
/// <summary>
/// 鼠标按下事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Rectangle_MouseDown(object sender, MouseButtonEventArgs e)
{
rectangle = (Rectangle)e.OriginalSource;
_startPoint = e.GetPosition(canvas);
_isDragging = true;
//锁定鼠标焦点在控件上
rectangle.CaptureMouse();
//触发路由事件
OnCustomClick();
e.Handled = true;
}
private void Rectangle_MouseUp(object sender, MouseButtonEventArgs e)
{
// 如果释放了鼠标左键
if (e.LeftButton == MouseButtonState.Released && _isDragging)
{
// 结束拖动
_isDragging = false;
// 释放鼠标捕获
rectangle.ReleaseMouseCapture();
}
e.Handled = true;
}
private void Rectangle_MouseMove(object sender, MouseEventArgs e)
{
// 如果正在拖动
if (_isDragging)
{
// 获取当前鼠标位置
Point currentPoint = e.GetPosition(canvas);
// 计算鼠标移动的偏移量
var delta = currentPoint - _startPoint;
_startPoint = currentPoint;
// 更新
switch (rectangle.Name)
{
case "CenterCenter":
ROILeftTopX += delta.X;
ROILeftTopY += delta.Y;
break;
case "LeftBottom":
ROILeftTopX += delta.X;
ROIWidth = Math.Max(60, ROIWidth - delta.X);
ROIHeight = Math.Max(60, ROIHeight + delta.Y);
break;
case "LeftCenter":
ROILeftTopX += delta.X;
ROIWidth = Math.Max(60, ROIWidth - delta.X);
break;
case "LeftTop":
ROILeftTopX += delta.X;
ROIWidth = Math.Max(60, ROIWidth - delta.X);
ROILeftTopY += delta.Y;
ROIHeight = Math.Max(60, ROIHeight - delta.Y);
break;
case "RightBottom":
ROIWidth = Math.Max(60, ROIWidth + delta.X);
ROIHeight = Math.Max(60, ROIHeight + delta.Y);
break;
case "RightCenter":
ROIWidth = Math.Max(60, ROIWidth + delta.X);
break;
case "RightTop":
ROILeftTopY += delta.Y;
ROIWidth = Math.Max(60, ROIWidth + delta.X);
ROIHeight = Math.Max(60, ROIHeight - delta.Y);
break;
case "CenterBottom":
ROIHeight = Math.Max(60, ROIHeight + delta.Y);
break;
case "CenterTop":
ROILeftTopX += delta.X;
ROIHeight = Math.Max(60, ROIHeight + delta.Y);
break;
default:
break;
}
}
//e.Handled = true;
}
}
Xaml代码
<Canvas x:Class="ROITool.ROIRectangle.ROIRectangle"
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"
xmlns:local="clr-namespace:ROITool.ROIRectangle" x:Name="canvas"
mc:Ignorable="d" MouseMove="Rectangle_MouseMove" MouseLeftButtonUp="Rectangle_MouseUp">
<Grid x:Name="grid">
<Rectangle Fill="Transparent" Stroke="Red" StrokeThickness="1" PreviewMouseLeftButtonDown="Rectangle_MouseDown" x:Name="CenterCenter" Cursor="SizeAll"/>
<Rectangle Width="20" Height="20" Fill="Transparent" Stroke="Red" StrokeThickness="1" PreviewMouseLeftButtonDown="Rectangle_MouseDown" x:Name="LeftBottom" Cursor="SizeNESW" HorizontalAlignment="Left" VerticalAlignment="Bottom"/>
<Rectangle Width="20" Height="20" Fill="Transparent" Stroke="Red" StrokeThickness="1" PreviewMouseLeftButtonDown="Rectangle_MouseDown" x:Name="LeftCenter" Cursor="SizeWE" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<Rectangle Width="20" Height="20" Fill="Transparent" Stroke="Red" StrokeThickness="1" PreviewMouseLeftButtonDown="Rectangle_MouseDown" x:Name="LeftTop" Cursor="SizeNWSE" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Rectangle Width="20" Height="20" Fill="Transparent" Stroke="Red" StrokeThickness="1" PreviewMouseLeftButtonDown="Rectangle_MouseDown" x:Name="RightBottom" Cursor="SizeNWSE" HorizontalAlignment="Right" VerticalAlignment="Bottom"/>
<Rectangle Width="20" Height="20" Fill="Transparent" Stroke="Red" StrokeThickness="1" PreviewMouseLeftButtonDown="Rectangle_MouseDown" x:Name="RightCenter" Cursor="SizeWE" HorizontalAlignment="Right" VerticalAlignment="Center"/>
<Rectangle Width="20" Height="20" Fill="Transparent" Stroke="Red" StrokeThickness="1" PreviewMouseLeftButtonDown="Rectangle_MouseDown" x:Name="RightTop" Cursor="SizeNESW" HorizontalAlignment="Right" VerticalAlignment="Top"/>
<Rectangle Width="20" Height="20" Fill="Transparent" Stroke="Red" StrokeThickness="1" PreviewMouseLeftButtonDown="Rectangle_MouseDown" x:Name="CenterBottom" Cursor="SizeNS" HorizontalAlignment="Center" VerticalAlignment="Bottom"/>
<Rectangle Width="20" Height="20" Fill="Transparent" Stroke="Red" StrokeThickness="1" PreviewMouseLeftButtonDown="Rectangle_MouseDown" x:Name="CenterTop" Cursor="SizeNS" HorizontalAlignment="Center" VerticalAlignment="Top"/>
</Grid>
</Canvas>
Cursor属性,设置鼠标悬停在控件上方时的样式(WIN系统定义)
ImageViewBox容器控件
控件中将ROI部分使用依赖属性开放到外部设置
/// <summary>
/// ImageViewBox.xaml 的交互逻辑
/// </summary>
public partial class ImageViewBox : Grid
{
/// <summary>
/// 内容依赖属性
/// </summary>
public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register(
"Content",
typeof(object),
typeof(ImageViewBox),
new PropertyMetadata(null, OnContentChanged));
public object Content
{
get { return GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
/// <summary>
/// 将内容添加到内部控件中
/// </summary>
/// <param name="d"></param>
/// <param name="e"></param>
private static void OnContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as ImageViewBox;
if (control != null)
{
control.PART_ContentHost.Content = e.NewValue;
}
}
/// <summary>
/// 图像源依赖属性
/// </summary>
public static DependencyProperty ImageSourceProperty = DependencyProperty.Register("ImageSource", typeof(WriteableBitmap), typeof(ImageViewBox), new PropertyMetadata(null, delegate (DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is ImageViewBox imageViewBox6)
{
imageViewBox6.UpdateDisplayBitmap((d as ImageViewBox).Name);
}
}));
public WriteableBitmap ImageSource
{
get
{
return (WriteableBitmap)GetValue(ImageSourceProperty);
}
set
{
SetValue(ImageSourceProperty, value);
}
}
private Point _startPoint; // 记录鼠标按下的初始位置
private bool _isDragging; // 标记是否正在拖动
public ImageViewBox()
{
InitializeComponent();
// 监听鼠标滚轮事件
this.MouseWheel += MainWindow_PreviewMouseWheel;
// 监听鼠标按下事件
this.MouseLeftButtonDown += MainWindow_PreviewMouseDown;
// 监听鼠标移动事件
this.MouseMove += MainWindow_PreviewMouseMove;
// 监听鼠标释放事件
this.MouseLeftButtonUp += MainWindow_PreviewMouseUp;
}
/// <summary>
/// 更新信息栏图像尺寸信息
/// </summary>
/// <param name="ImageName"></param>
private void UpdateDisplayBitmap(string ImageName)
{
this.DisplayImage.Source = ImageSource;
this.measureLabel.Content = "尺寸:" + ImageSource.PixelWidth + "*" + ImageSource.PixelHeight;
}
/// <summary>
/// 鼠标滚轮事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MainWindow_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
// 检查是否按下了 Ctrl 键
//if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
//{
// 获取鼠标相对于 Viewbox 的位置
Point mousePosition = e.GetPosition(MyViewbox);
// 计算缩放因子
double scaleFactor = e.Delta > 0 ? 1.1 : 0.9;
// 以鼠标为中心进行缩放
ZoomAtPosition(mousePosition, scaleFactor);
// 标记事件已处理,防止继续传递
e.Handled = true;
//}
}
private void ZoomAtPosition(Point position, double scaleFactor)
{
// 获取当前的缩放和平移变换
double currentScale = ScaleTransform.ScaleX;
double newScale = currentScale * scaleFactor;
// 计算缩放后的偏移量
double offsetX = position.X * (1 - scaleFactor);
double offsetY = position.Y * (1 - scaleFactor);
// 应用新的缩放比例
ScaleTransform.ScaleX = newScale;
ScaleTransform.ScaleY = newScale;
// 更新平移变换
TranslateTransform.X += offsetX;
TranslateTransform.Y += offsetY;
}
/// <summary>
/// 鼠标按下事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MainWindow_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
// 检查是否按下了鼠标左键
if (e.LeftButton == MouseButtonState.Pressed)
{
// 记录鼠标按下的初始位置
_startPoint = e.GetPosition(this);
_isDragging = true;
// 捕获鼠标,确保即使鼠标移出窗口也能继续拖动
this.CaptureMouse();
}
}
/// <summary>
/// 鼠标拖动事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MainWindow_PreviewMouseMove(object sender, MouseEventArgs e)
{
Point Point = e.GetPosition(DisplayImage);
GetGrayValueAt(ImageSource, (int)Point.X, (int)Point.Y);
// 如果正在拖动
if (_isDragging)
{
// 获取当前鼠标位置
Point currentPoint = e.GetPosition(this);
// 计算鼠标移动的偏移量
double offsetX = currentPoint.X - _startPoint.X;
double offsetY = currentPoint.Y - _startPoint.Y;
// 更新平移变换
TranslateTransform.X += offsetX;
TranslateTransform.Y += offsetY;
// 更新初始位置
_startPoint = currentPoint;
}
}
/// <summary>
/// 鼠标抬起事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MainWindow_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
// 如果释放了鼠标左键
if (e.LeftButton == MouseButtonState.Released && _isDragging)
{
// 结束拖动
_isDragging = false;
// 释放鼠标捕获
this.ReleaseMouseCapture();
}
}
private void Rectangle_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var canvas = (Canvas)sender;
var parent = (Panel)canvas.Parent;
// 获取当前最大ZIndex
int maxZ = parent.Children.OfType<UIElement>().Select(x => Panel.GetZIndex(x)).Max();
// 设置点击的矩形在最前
Panel.SetZIndex(canvas, maxZ + 1);
// 可选:添加动画效果
//var anim = new DoubleAnimation(1.1, TimeSpan.FromSeconds(0.1));
//anim.AutoReverse = true;
//rectangle.RenderTransform = new ScaleTransform();
//rectangle.RenderTransform.BeginAnimation(ScaleTransform.ScaleXProperty, anim);
//rectangle.RenderTransform.BeginAnimation(ScaleTransform.ScaleYProperty, anim);
}
/// <summary>
/// 更新信息栏灰度值信息
/// </summary>
/// <param name="bitmap"></param>
/// <param name="x"></param>
/// <param name="y"></param>
private void GetGrayValueAt(WriteableBitmap bitmap, int x, int y)
{
if (bitmap == null) return;
if (x < 0 || x >= bitmap.PixelWidth || y < 0 || y >= bitmap.PixelHeight) return;
bitmap.Lock();
try
{
// 获取像素缓冲区的指针
IntPtr backBuffer = bitmap.BackBuffer;
// 计算每像素的字节数
int bytesPerPixel = (bitmap.Format.BitsPerPixel + 7) / 8;
// 计算指定位置的字节偏移量
int offset = y * bitmap.BackBufferStride + x * bytesPerPixel;
// 读取像素值
byte B = System.Runtime.InteropServices.Marshal.ReadByte(backBuffer, offset);
byte G = System.Runtime.InteropServices.Marshal.ReadByte(backBuffer, offset + 1);
byte R = System.Runtime.InteropServices.Marshal.ReadByte(backBuffer, offset + 2);
this.locationLabel.Content = "位置:" + x + "*" + y;
this.BLabel.Content = "B通道:" + B;
this.GLabel.Content = "G通道:" + G;
this.RLabel.Content = "R通道:" + R;
// 计算灰度值(使用加权平均法)
this.GrayLabel.Content = "Gray:" + (byte)(0.299 * R + 0.587 * G + 0.114 * B);
return;
}
finally
{
// 确保无论如何都解锁位图
bitmap.Unlock();
}
}
/// <summary>
/// ROI选择变化事件
/// </summary>
/// <param name="sender"></param>
/// <param name="selectedROI"></param>
public delegate void SelectedROIChangedEvent(ImageViewBox? sender, ROIRectangle.ROIRectangle selectedROI);
public event SelectedROIChangedEvent SelectedROIChanged;
private void ROI_SelectedHandle(object sender, RoutedEventArgs arg)
{
var SelectedROI = arg.OriginalSource as ROIRectangle.ROIRectangle;
this.SelectedROIChanged?.Invoke(this, SelectedROI);
arg.Handled = true;
}
}
Xaml代码
<Grid x:Class="ROITool.ImageViewBox.ImageViewBox"
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"
xmlns:local="clr-namespace:ROITool.ImageViewBox"
xmlns:local1="clr-namespace:ROITool.ROIRectangle"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" >
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<DockPanel DockPanel.Dock="Top" Height="25" Grid.Row="0" Background="White">
<Label x:Name="measureLabel" Content="尺寸:0*0"/>
<Label x:Name="locationLabel" Content="位置:0*0"/>
<Label x:Name="GrayLabel" Content="Gray:0"/>
<Label x:Name="RLabel" Content="R通道:0"/>
<Label x:Name="GLabel" Content="G通道:0"/>
<Label x:Name="BLabel" Content="B通道:0"/>
</DockPanel>
<Border Grid.Row="1" ClipToBounds="True" ><!--ClipToBounds="True"限制Viewbox显示范围在Border内-->
<Viewbox x:Name="MyViewbox" Stretch="Uniform" >
<Viewbox.RenderTransform>
<TransformGroup>
<ScaleTransform x:Name="ScaleTransform" />
<TranslateTransform x:Name="TranslateTransform" />
</TransformGroup>
</Viewbox.RenderTransform>
<Grid local1:ROIRectangle.Selected="ROI_SelectedHandle">
<Image x:Name="DisplayImage" Stretch="UniformToFill" />
<ContentPresenter x:Name="PART_ContentHost"/>
</Grid>
</Viewbox>
</Border>
</Grid>
MainWindow控件使用
Xaml代码
<Window x:Class="ROITool.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ROITool"
xmlns:controls="clr-namespace:ROITool.ImageViewBox"
xmlns:controls1="clr-namespace:ROITool.ROIRectangle"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
mc:Ignorable="d"
Title="MainWindow" Height="900" Width="1600">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<i:InvokeCommandAction Command="{Binding ClosingCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="800"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<DockPanel Grid.Column="0">
<DockPanel DockPanel.Dock="Top">
<TextBox Text="本地图像:" IsHitTestVisible="False"/>
<TextBox Text="{Binding BMPFileName}" IsReadOnly="True"/>
</DockPanel >
<UniformGrid Columns="4" Height="20" DockPanel.Dock="Top" >
<Button Content="选择图像" Command="{Binding SelectImageCommand}" />
<Button Content="执行检测" Command="{Binding ExecuteDetectCommand}" />
<Button Content="全览ROI" Command="{Binding AllROICommand}" />
<Button Content="保存设置" Command="{Binding SaveConfigCommand}" />
</UniformGrid>
<DockPanel DockPanel.Dock="Top" Name="dplSizeDetectPram">
<UniformGrid Columns="2">
<Label Content="ROI左上(X)"/>
<TextBox Text="{Binding ROILeftTopX,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
<Label Content="ROI左上(Y)"/>
<TextBox Text="{Binding ROILeftTopY,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
<Label Content="宽度"/>
<TextBox Text="{Binding ROIWidth,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
<Label Content="高度"/>
<TextBox Text="{Binding ROIHeight,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
</UniformGrid>
</DockPanel>
<UniformGrid>
</UniformGrid>
</DockPanel>
<controls:ImageViewBox Grid.Column="1" ImageSource="{Binding SourceBitmap}" SelectedROIChanged="ROI_SelectedROIChanged">
<controls:ImageViewBox.Resources>
<Style TargetType="controls1:ROIRectangle" x:Key="sizeDetectGroupStyle">
<Setter Property="ROIHeight" Value="{Binding ROIHeight,Mode=TwoWay}"/>
<Setter Property="ROIWidth" Value="{Binding ROIWidth,Mode=TwoWay}"/>
<Setter Property="ROILeftTopX" Value="{Binding ROILeftTopX,Mode=TwoWay}"/>
<Setter Property="ROILeftTopY" Value="{Binding ROILeftTopY,Mode=TwoWay}"/>
</Style>
</controls:ImageViewBox.Resources>
<controls:ImageViewBox.Content>
<Grid>
<controls1:ROIRectangle DataContext="{Binding ROIData1}">
<controls1:ROIRectangle.Style>
<Style TargetType="controls1:ROIRectangle" BasedOn="{StaticResource sizeDetectGroupStyle}"/>
</controls1:ROIRectangle.Style>
</controls1:ROIRectangle>
<controls1:ROIRectangle DataContext="{Binding ROIData2}">
<controls1:ROIRectangle.Style>
<Style TargetType="controls1:ROIRectangle" BasedOn="{StaticResource sizeDetectGroupStyle}"/>
</controls1:ROIRectangle.Style>
</controls1:ROIRectangle>
</Grid>
</controls:ImageViewBox.Content>
</controls:ImageViewBox>
</Grid>
</Window>
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowModel();
}
/// <summary>
/// 更新绑定数据源
/// </summary>
/// <param name="sender"></param>
/// <param name="selectedROI"></param>
private void ROI_SelectedROIChanged(ImageViewBox.ImageViewBox sender, ROIRectangle.ROIRectangle selectedROI)
{
if (selectedROI.DataContext is ROIRectangleData)
{
dplSizeDetectPram.DataContext = selectedROI.DataContext;
}
}
ROIRectangle控件单击ROI触发SelectedEvent路由事件冒泡传递,ImageViewBox控件使用local1:ROIRectangle.Selected="ROI_SelectedHandle"进行监听并触发ImageViewBox控件的SelectedROIChanged继续向外传递选中的ROIRectangle控件,最后在MainWindow中SelectedROIChanged="ROI_SelectedROIChanged"订阅事件对dplSizeDetectPram的数据源进行修改。
MainWindowModel代码
[PropertyChanged.AddINotifyPropertyChangedInterface]
public class MainWindowModel
{
/// <summary>
/// 源图
/// </summary>
public WriteableBitmap SourceBitmap { get; set; }
/// <summary>
/// 效果图
/// </summary>
public WriteableBitmap EffectBitmap { get; set; }
/// <summary>
/// 源图数据
/// </summary>
internal ImgStruct_t SourceImgData = new ImgStruct_t();
public ROIRectangleData ROIData1 { get; set; } = new ROIRectangleData();
public ROIRectangleData ROIData2 { get; set; } = new ROIRectangleData();
public string BMPFileName
{
get => bmpFileName; set
{
if (string.IsNullOrEmpty(value) || !File.Exists(value)) return;
//读取图像信息
ImageHelper.ReadImage(value, ref SourceImgData, true);
//初始化WriteableBitmap
SourceBitmap = ImageHelper.UpdateBitmapSize(SourceBitmap, ref SourceImgData);
//读取图像并获取指针
HalconTool.ReadImage(0, value, "", ref SourceImgData);
SourceBitmap.Lock();
SourceBitmap.AddDirtyRect(new Int32Rect(0, 0, SourceImgData.Width, SourceImgData.Height));
SourceBitmap.Unlock();
bmpFileName = value;
//DrawingType = DrawingType.OriginalDrawing;
}
}
string bmpFileName;
public ICommand SelectImageCommand
{
get => new DelegateCommand(() =>
{
string initDir = System.IO.Path.GetDirectoryName(BMPFileName);
BMPFileName = ControlHelper.SelectFile("请选择图像文件", "图像文件(*.bmp, *.jpg,*.tiff,*.png,*.jpeg)|*.bmp;*.jpg;*.tiff;*.png;*.jpeg", initDir);
//UpdateFileList();
});
}
public ICommand ExecuteDetectCommand
{
get => new DelegateCommand(() =>
{
var a = ROIData1;
var b = ROIData2;
});
}
public ICommand LoadedCommand
{
get => new DelegateCommand(() =>
{
InitModel();
});
}
internal void InitModel()
{
ROIData1 = new ROIRectangleData()
{
ROIHeight = 300,
ROIWidth = 300,
ROILeftTopX = 200,
ROILeftTopY = 200,
};
ROIData2 = new ROIRectangleData()
{
ROIHeight = 300,
ROIWidth = 300,
ROILeftTopX = 300,
ROILeftTopY = 300,
};
}
}
public enum DrawingType
{
//
// 摘要:
// 原图
[Description("原图")]
OriginalDrawing,
//
// 摘要:
// 效果图
[Description("效果图")]
EffectDrawing
}