WPF基于OpencvSharp4实现图像马赛克

31 阅读2分钟

图像马赛克是一种通过模糊或像素化处理来隐藏敏感信息的图像处理技术,通常用于保护隐私、隐藏人脸、车牌等身份信息。

实现原理:将区域内的像素进行平均颜色填充。也就是将每个区域内的所有像素点都设置成同样的像素。其本质是直接对图像的像素进行更改,因此,马赛克通常是不可逆的,当然,简单的马赛克可能被AI修复,其修复原理也只是利用图像的整体信息和局部模式来推测被模糊的内容,而且修复后的图像也不一定跟初始图像一模一样。

效果如下:

screenshots.gif

核心逻辑就是在Image控件的MouseMove事件中,将以鼠标点坐标为中心的附近领域内所有像素点的像素值都设置成鼠标点的像素即可。鼠标左键按下时启动,抬起时停止。只需要MouseMove,MouseDown,MouseUp三个事件。

Image控件的xaml代码如下:

      <Image
          Name="TargetImage"
          Grid.Row="1"
          Width="760"
          Height="720"
          Source="{Binding MainMat, Converter={StaticResource matToImageSource}, Mode=TwoWay}"
          Stretch="Fill">
          <i:Interaction.Triggers>
              <i:EventTrigger EventName="MouseDown">
                  <i:InvokeCommandAction Command="{Binding MouseDownCommand}" />
              </i:EventTrigger>
              <i:EventTrigger EventName="MouseUp">
                  <i:InvokeCommandAction Command="{Binding MouseUpCommand}" />
              </i:EventTrigger>
              <i:EventTrigger EventName="MouseMove">
                  <i:InvokeCommandAction Command="{Binding MouseMoveCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Image}}" />
              </i:EventTrigger>
          </i:Interaction.Triggers>
      </Image>

其中,Image控件的Source是 ImageSource类型的,需要定义一个转换器将OpencvSharp的Mat对象转换成BitmapSource。

 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();
     }
 }

需要引用官方的行为包 Microsoft.Xaml.Behaviors.Wpf ,来使用行为。 在 MouseMove事件中,知道鼠标的实时位置,同时Image控件的大小跟图片大小不一定相同,并且其Stretch="Fill",所以需要将鼠标位置和Image控件大小传给vm,所以就只需要将CommandParameter Bingding Image控件自身即可。在vm中就可以通过 Mouse.GetPosition来获取鼠标位置,ActualWidth和ActualHeight来获取控件大小。

MouseMove命令处理代码

  private void ProcessMouseMove(System.Windows.Controls.Image image)
  {
      if (!_isStartMosaic || MainMat == null || mosaicFlagArray == null) return;
      if (image == null)
          return;
      System.Windows.Point pos = Mouse.GetPosition(image);
      System.Windows.Size size = new System.Windows.Size(image.ActualWidth, image.ActualHeight);

      var imagePosition = ConvertToImageCoordinates(pos, size);
      CurrentCoordinate.X = Math.Round(imagePosition.X, 1);
      CurrentCoordinate.Y = Math.Round(imagePosition.Y, 1);

      Mat srcImg = MainMat.Clone();
      int posX = (int)CurrentCoordinate.X;
      int posY = (int)CurrentCoordinate.Y;
      if (mosaicFlagArray[posY, posX] == false)
      {
          mosaicFlagArray[posY, posX] = true;
          OpenCvSharp.Vec3b color = new OpenCvSharp.Vec3b();
          color = srcImg.Get<OpenCvSharp.Vec3b>(posY, posX);

          for (int i = posX - _mosaicSize; i <= posX + _mosaicSize; i++)
          {
              for (int j = posY - _mosaicSize; j <= posY + _mosaicSize; j++)
              {
                  //边界考虑
                  if (i < 0) i = 0;
                  if (i >= srcImg.Width) i = srcImg.Width - 1;
                  if (j < 0) j = 0;
                  if (j >= srcImg.Height) j = srcImg.Height - 1;
                  mosaicFlagArray[j, i] = true;
                  srcImg.Set(j, i, color);
              }
          }
          MainMat = srcImg.Clone();
          srcImg.Dispose();
          //Thread.Sleep(50);
      }
  }
  
         /// <summary>
       /// 将控件坐标转换为图像坐标(Stretch="Fill")
       /// </summary>
       private System.Windows.Point ConvertToImageCoordinates(System.Windows.Point controlPosition, System.Windows.Size controlSize)
       {
           if (MainMat == null || controlSize.Width == 0 || controlSize.Height == 0)
               return new System.Windows.Point(0, 0);

           // 获取图像原始尺寸
           double imageWidth = MainMat.Width;
           double imageHeight = MainMat.Height;

           // Stretch="Fill" 的简单计算
           // 直接按比例映射
           double imageX = (controlPosition.X / controlSize.Width) * imageWidth;
           double imageY = (controlPosition.Y / controlSize.Height) * imageHeight;

           // 确保坐标在图像范围内
           imageX = Math.Max(0, Math.Min(imageX, imageWidth));
           imageY = Math.Max(0, Math.Min(imageY, imageHeight));

           return new System.Windows.Point(imageX, imageY);
       }