基于C#WPF和OpencvSharp开发的图像处理系统——4.输出图像控件

24 阅读3分钟

类似于输入图像控件

image.png

定义依赖属性

/// <summary>
/// 目的图像
/// </summary>
public Mat DstImageSource
{
    get { return (Mat)GetValue(DstImageSourceProperty); }
    set { SetValue(DstImageSourceProperty, value); }
}

// Using a DependencyProperty as the backing store for DatImageSource.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty DstImageSourceProperty =
    DependencyProperty.Register("DstImageSource", typeof(Mat), typeof(DstImgShowUserControl));

/// <summary>
/// 图像路径保存
/// </summary>
public string ImageSource_Save
{
    get { return (string)GetValue(ImageSource_SaveProperty); }
    set { SetValue(ImageSource_SaveProperty, value); }
}

// Using a DependencyProperty as the backing store for ImageSource.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty ImageSource_SaveProperty =
    DependencyProperty.Register("ImageSource_Save", typeof(string), typeof(DstImgShowUserControl));

public string LabelText
{
    get { return (string)GetValue(LabelTextProperty); }
    set { SetValue(LabelTextProperty, value); }
}

// Using a DependencyProperty as the backing store for LabelText.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty LabelTextProperty =
    DependencyProperty.Register("LabelText", typeof(string), typeof(DstImgShowUserControl), new PropertyMetadata("输出图像"));

xaml定义 采用Grid布局,定义三行

第一行 标题和清空按钮

<TextBlock
            Grid.Row="0"
            Margin="0,2,0,0"
            HorizontalAlignment="Center"
            VerticalAlignment="Top"
            Text="{Binding LabelText, RelativeSource={RelativeSource AncestorType=UserControl}}"
            FontSize="20"
            Foreground="White" />
 <Button
            Grid.Row="0"
            Width="35"
            Height="25"
            Margin="0,0,2,2"
            HorizontalAlignment="Right"
            VerticalAlignment="Center"
            Content="清空"
            Click="Button_Click" />

清空按钮点击事件

private void Button_Click(object sender, RoutedEventArgs e)
        {
            DstImageSource = null;
        }

第二行 图像显示

<Image
            Grid.Row="1"
            Width="335"
            Height="335"
            Source="{Binding DstImageSource, RelativeSource={RelativeSource AncestorType=UserControl}, Converter={StaticResource matToImageSourceConverter}}"
            Stretch="Uniform" 
            MouseLeftButtonDown="Image_MouseLeftButtonDown"
            />

由于每个Mat经过Opecvsharp处理后得到的还是一个Mat,而Image控件的Source无法直接绑定Mat,所以需要定义一个转换器,将Mat类型转换成BitmapSource供Image绑定。

public class MatToImageSourceConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is Mat mat && !mat.Empty())
            {
                if (mat == null || mat.Empty())
                    return null;
                try
                {
                    // 根据Mat类型进行相应的转换
                    Mat imageToShow = mat;

                    // 如果是单通道灰度图,转换为BGR
                    if (mat.Channels() == 1)
                    {
                        imageToShow = new Mat();
                        Cv2.CvtColor(mat, imageToShow, ColorConversionCodes.GRAY2BGR);
                    }
                    // 如果是4通道(带Alpha),转换为BGR
                    else if (mat.Channels() == 4)
                    {
                        imageToShow = new Mat();
                        Cv2.CvtColor(mat, imageToShow, ColorConversionCodes.BGRA2BGR);
                    }

                    var bitmapSource = OpenCvSharp.WpfExtensions.BitmapSourceConverter.ToBitmapSource(imageToShow);

                    return bitmapSource;
                }
                catch (Exception ex)
                {
                    return null;
                }
            }
            return null;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

在xaml中定义转换器

<UserControl.Resources>
       <converter:MatToImageSourceConverter x:Key="matToImageSourceConverter" />
   </UserControl.Resources>

还实现了双击Image可以放大显示图像,Image控件没有鼠标双击事件,就用MouseLeftButtonDown事件连续两次触发来代替实现。

        private DateTime _lastClickTime;
        private const int DoubleClickThreshold = 300; // 双击时间间隔阈值,单位为毫秒
        private void Image_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            if ((DateTime.Now - _lastClickTime).TotalMilliseconds < DoubleClickThreshold)
            {
                PicShowWindow picShowWindow = new PicShowWindow(DstImageSource);
                picShowWindow.Show();
                e.Handled = true;
            }
            _lastClickTime = DateTime.Now;
        }

其中,PicShowWindow代码如下:

PicShowWindow.xaml

<Window x:Class="ImageHandle.Views.PicShowWindow"
        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:ImageHandle.Views"
        mc:Ignorable="d"
        WindowStartupLocation="CenterScreen"
        ResizeMode="CanResizeWithGrip"
        Title="PicShowWindow" Height="400" Width="400" Deactivated="Window_Deactivated"
        MinHeight="100" MinWidth="100" MaxHeight="1000" MaxWidth="1000">
    <!--#region 隐藏标题栏-->
    <WindowChrome.WindowChrome>
        <WindowChrome GlassFrameThickness="0" />
    </WindowChrome.WindowChrome>
    <!--#endregion-->
    <Grid>
        <Image x:Name="mainImage" 
               Stretch="Fill" 
               MouseLeftButtonDown="mainImage_MouseLeftButtonDown" 
               MouseWheel="mainImage_MouseWheel"/>
    </Grid>
</Window>

PicShowWindow.xaml.cs

using OpenCvSharp;
using System.Windows.Media.Imaging;

namespace ImageHandle.Views
{
    /// <summary>
    /// PicShowWindow.xaml 的交互逻辑
    /// </summary>
    public partial class PicShowWindow : System.Windows.Window
    {
        private bool _isClosing = false;
        private readonly int _wheelRatio = 5;
        public PicShowWindow(Mat mat)
        {
            InitializeComponent();
            var bitmapSource = MatConvertToImageSource(mat);
            mainImage.Source = bitmapSource;
        }

        private void Window_Deactivated(object sender, EventArgs e)
        {
            if (this.Visibility == System.Windows.Visibility.Visible && this.IsLoaded && _isClosing == false)
            {
                _isClosing = true;
                this.Close();
            }
        }

        private BitmapSource MatConvertToImageSource(Mat mat)
        {
            if (mat == null || mat.Empty())
                return null;
            try
            {
                // 根据Mat类型进行相应的转换
                Mat imageToShow = mat;

                // 如果是单通道灰度图,转换为BGR
                if (mat.Channels() == 1)
                {
                    imageToShow = new Mat();
                    Cv2.CvtColor(mat, imageToShow, ColorConversionCodes.GRAY2BGR);
                }
                // 如果是4通道(带Alpha),转换为BGR
                else if (mat.Channels() == 4)
                {
                    imageToShow = new Mat();
                    Cv2.CvtColor(mat, imageToShow, ColorConversionCodes.BGRA2BGR);
                }

                var bitmapSource = OpenCvSharp.WpfExtensions.BitmapSourceConverter.ToBitmapSource(imageToShow);

                return bitmapSource;
            }
            catch (Exception ex)
            {
                return null;
            }
        }

        private DateTime _lastClickTime;
        private const int DoubleClickThreshold = 300; // 双击时间间隔阈值,单位为毫秒

        private void mainImage_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            if ((DateTime.Now - _lastClickTime).TotalMilliseconds < DoubleClickThreshold)
            {
                if (_isClosing == false)
                {
                    _isClosing = true;
                    this.Close();
                    e.Handled = true;
                }
            }
            else
            {
                this.DragMove();
            }
            _lastClickTime = DateTime.Now;
        }

        private void mainImage_MouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
        {
            int width = (int)this.Width;
            int height = (int)this.Height;
            width += e.Delta / _wheelRatio;
            height += e.Delta / _wheelRatio;
            if (width <= this.MinWidth)
            {
                width = (int)this.MinWidth;
            }
            if (height <= this.MinHeight)
            {
                height = (int)this.MinHeight;
            }
            if (width >= this.MaxWidth)
            {
                width = (int)this.MaxWidth;
            }
            if (height >= this.MaxHeight)
            {
                height = (int)this.MaxHeight;
            }
            this.Width = width;
            this.Height = height;
        }
    }
}

实现了拖拽窗体移动和改变大小,鼠标滚轮缩放窗体大小

第三行 保存图像和显示路径

该部分就类似于输入图像控件了。

<StackPanel
            Grid.Row="2"
            Margin="0,0,0,1"
            VerticalAlignment="Center"
            Orientation="Horizontal">
            <Label
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                Content="图像路径:"
                Foreground="White"
                FontSize="16"
                Background="Transparent" />
            <TextBox
                Width="280"
                Margin="0,2,0,0"
                HorizontalAlignment="Center"
                Text="{Binding ImageSource_Save, RelativeSource={RelativeSource AncestorType=UserControl}}"
                IsReadOnly="True"
                Background="Transparent"
                FontSize="18"
                Foreground="White"
                TextAlignment="Center"
                ToolTip="{Binding Text, RelativeSource={RelativeSource Self}}" />
            <Button
                Width="35"
                Height="25"
                Margin="2,0,0,0"
                HorizontalAlignment="Right"
                VerticalAlignment="Center"
                Content="保存"
                Click="Button_Click_Save" />
        </StackPanel>

保存按钮点击事件

private void Button_Click_Save(object sender, RoutedEventArgs e)
    {
        if (DstImageSource == null)
        {
            MessageBox.Show("没有需要保存的图像");
            return;
        }
        SaveFileDialog dialog = new SaveFileDialog();
        dialog.Title = "请选择图片";
        dialog.Filter = "图片文件 (*.jpg)|*.jpg |图片文件 (*.bmp)|*.bmp |图片文件 (*.jpeg)|*.jpeg |图片文件 (*.png)|*.png";

        if (dialog.ShowDialog() == true)
        {
            string savePath = dialog.FileName;
            if (savePath == "")
            {
                MessageBox.Show("未选择保存路径");
                return;
            }

            bool success = Cv2.ImWrite(savePath, DstImageSource);
            if (!success)
            {
                MessageBox.Show("图像保存失败");
                return;
            }
            MessageBox.Show("保存成功");
            ImageSource_Save = savePath;
        }
    }