WPFでOpenCVのフィルター用のGUIを作成しましたのでフィルターを追加して輪郭強調をしてみたいと思います。
実行環境
Windows10 2004
dotnet –version 5.0.104
Visual Studio Code
PowerShell 5.1
dotnet –version 5.0.104
Visual Studio Code
PowerShell 5.1
プロジェクトの作成
mkdir プロジェクト名
cd プロジェクト名
dotnet new wpf
dotnet add package Microsoft.Xaml.Behaviors.Wpf
dotnet add package ReactiveProperty.WPF
dotnet add package OpenCvSharp4.Windows
code .
ソースコード
ファイル名:MainWindow.xaml
<Window x:Class="WpfSample15OpenCVFilter.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:i="clr-namespace:Microsoft.Xaml.Behaviors;assembly=Microsoft.Xaml.Behaviors"
xmlns:interactivity="clr-namespace:Reactive.Bindings.Interactivity;assembly=ReactiveProperty.WPF"
xmlns:local="clr-namespace:WpfSample15OpenCVFilter"
mc:Ignorable="d"
Title="MainWindow" Height="600" Width="800">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="AUTO"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<x:Code>
<![CDATA[
private void PreviewDragOrver(object sender, DragEventArgs e)
{
e.Effects = (e.Data.GetDataPresent(DataFormats.FileDrop)) ? DragDropEffects.Copy : DragDropEffects.None;
e.Handled = true;
}
private void OriginalImage_SizeChanged(object sender, SizeChangedEventArgs e)
{
OriginalCanvas.Height = e.NewSize.Height;
OriginalCanvas.Width = e.NewSize.Width;
}
private void ModifiedImage_SizeChanged(object sender, SizeChangedEventArgs e)
{
ModifiedCanvas.Height = e.NewSize.Height;
ModifiedCanvas.Width = e.NewSize.Width;
}
]]>
</x:Code>
<ScrollViewer
HorizontalScrollBarVisibility="Visible"
VerticalScrollBarVisibility="Visible"
Margin="10,10,10,0"
Background="Azure"
Grid.Column="0">
<Canvas
Name="OriginalCanvas"
Margin="0,0,0,0"
RenderTransformOrigin="0.5,0.5"
Background="AliceBlue">
<Canvas.RenderTransform>
<ScaleTransform ScaleX="{Binding ZoomScale.Value}" ScaleY="{Binding ZoomScale.Value}"/>
</Canvas.RenderTransform>
<Image
Name="OriginalImage"
Stretch="None"
SizeChanged="OriginalImage_SizeChanged"
Source="{Binding OriginalImage.Value}">
</Image>
</Canvas>
</ScrollViewer>
<StackPanel
Grid.Column="1">
<Expander
Header="ぼかし処理"
IsExpanded="True"
IsEnabled="{Binding FilterEnabled.Value}"
BorderBrush="Gray">
<StackPanel>
<StackPanel Orientation=" Horizontal">
<Label Content="回数:" />
<TextBox Text="{Binding BlurNumberOfTimes.Value}" />
</StackPanel>
<Slider
Minimum="0"
Maximum="32"
Value="{Binding BlurNumberOfTimes.Value}"
TickPlacement="Both" />
</StackPanel>
</Expander>
<Expander
Header="ノンローカルミーンフィルタ"
IsExpanded="True"
IsEnabled="{Binding FilterEnabled.Value}"
BorderBrush="Gray">
<StackPanel>
<StackPanel Orientation=" Horizontal">
<Label Content="h:" />
<TextBox Text="{Binding NonLocalMeanH.Value}" />
</StackPanel>
<Slider
Minimum="0"
Maximum="32"
Value="{Binding NonLocalMeanH.Value}"
TickPlacement="Both" />
</StackPanel>
</Expander>
<Expander
Header="ラプラシアンフィルタ"
IsExpanded="True"
IsEnabled="{Binding FilterEnabled.Value}"
BorderBrush="Gray">
<StackPanel>
<StackPanel Orientation=" Horizontal">
<Label Content="ksize:" />
<TextBox Text="{Binding LaplacianKsize.Value}" />
</StackPanel>
<Slider
SmallChange="2"
LargeChange="2"
Minimum="0"
Maximum="15"
Value="{Binding LaplacianKsize.Value}"
TickPlacement="Both" />
</StackPanel>
</Expander>
<Expander
Header="ズーム"
IsExpanded="True"
IsEnabled="{Binding FilterEnabled.Value}"
BorderBrush="Gray">
<StackPanel>
<StackPanel Orientation=" Horizontal">
<Label Content="倍率:" />
<TextBox Text="{Binding ZoomScale.Value}" />
</StackPanel>
<Slider
Minimum="1"
Maximum="16"
Value="{Binding ZoomScale.Value}"
TickPlacement="Both" />
</StackPanel>
</Expander>
<Button
Content="フィルター処理"
IsEnabled="{Binding FilterEnabled.Value}"
Command="{Binding FilterCommand}" />
<Button
Content="クリア"
IsEnabled="{Binding FilterEnabled.Value}"
Command="{Binding ClearCommand}" />
<Label
AllowDrop="True"
PreviewDragOver="PreviewDragOrver"
Content="こちらにファイルをドラックアンドドロップ" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewDrop">
<interactivity:EventToReactiveCommand Command="{Binding PreviewDropCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Label>
</StackPanel>
<ScrollViewer
HorizontalScrollBarVisibility="Visible"
VerticalScrollBarVisibility="Visible"
Margin="10,10,10,0"
Background="Azure"
Grid.Column="2">
<Canvas
Name="ModifiedCanvas"
Margin="0,0,0,0"
RenderTransformOrigin="0.5,0.5"
Background="AliceBlue">
<Canvas.RenderTransform>
<ScaleTransform ScaleX="{Binding ZoomScale.Value}" ScaleY="{Binding ZoomScale.Value}"/>
</Canvas.RenderTransform>
<Image
Name="ModifiedImage"
SizeChanged="ModifiedImage_SizeChanged"
Stretch="None"
Source="{Binding ModifiedImage.Value}"
RenderTransformOrigin="0.5,0.5">
</Image>
</Canvas>
</ScrollViewer>
</Grid>
</Window>
ファイル名:MainWindowViewModel.cs
using System.Diagnostics;
using System;
using System.Windows;
using Reactive.Bindings;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Windows.Media.Imaging;
using OpenCvSharp;
using OpenCvSharp.WpfExtensions;
namespace WpfSample15OpenCVFilter
{
public class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ReactiveCommand<DragEventArgs> PreviewDropCommand { get; } = new ReactiveCommand<DragEventArgs>();
// オリジナル画像のOpenCVオブジェクト
private Mat _orginalMat;
// オリジナル画像の表示用
public ReactiveProperty<WriteableBitmap> OriginalImage { get; private set;} = new ReactiveProperty<WriteableBitmap>();
// フィルター処理可能フラグ
public ReactiveProperty<bool> FilterEnabled { get; private set;} = new ReactiveProperty<bool>(false);
// ぼかし処理の繰り返し回数
public ReactiveProperty<int> BlurNumberOfTimes { get; private set;} = new ReactiveProperty<int>(6);
// ノンローカルミーンフィルタ
public ReactiveProperty<int> NonLocalMeanH { get; private set;} = new ReactiveProperty<int>(12);
// ズーム倍率
public ReactiveProperty<int> ZoomScale { get; private set;} = new ReactiveProperty<int>(1);
// 加工画像のOpenCVオブジェクト
private Mat _modifiedMat;
// 加工画像の表示用
public ReactiveProperty<WriteableBitmap> ModifiedImage { get; private set;} = new ReactiveProperty<WriteableBitmap>();
// フィルター処理
public ReactiveCommand FilterCommand { get; } = new ReactiveCommand();
// クリア処理
public ReactiveCommand ClearCommand { get; } = new ReactiveCommand();
// ラプラシアンフィルタksize
public ReactiveProperty<int> LaplacianKsize { get; private set;} = new ReactiveProperty<int>(1);
private void loadPicture(string path)
{
if (_orginalMat != null) _orginalMat.Dispose();
_orginalMat = new Mat(path, ImreadModes.Grayscale);
var bitmapSource = (WriteableBitmap)BitmapSourceConverter.ToBitmapSource(_orginalMat);
bitmapSource.Freeze();
OriginalImage.Value = bitmapSource;
}
protected virtual void OnPropertyChanged(string name)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
// 加工画像の初期化
private void initializeModifiedImage()
{
if (_modifiedMat != null) _modifiedMat.Dispose();
_modifiedMat = null;
ModifiedImage.Value = null;
}
public MainWindowViewModel()
{
PropertyChanged += (o, e) => {};
PreviewDropCommand.Subscribe(e => {
if (e.Data.GetDataPresent(DataFormats.FileDrop) == false) return;
var path = ((string[])e.Data.GetData(DataFormats.FileDrop))[0];
FilterEnabled.Value = false;
loadPicture(path);
FilterEnabled.Value = true;
});
// フィルター処理
FilterCommand.Subscribe(_ => {
initializeModifiedImage();
double[,] kernel = { { 0.0, 1.0/16.0, 0.0},
{ 1.0/16.0, 12.0/16.0, 1.0/16.0},
{ 0.0, 1.0/16.0, 0.0},
};
_modifiedMat = _orginalMat.Clone();
// ぼかし処理
for(int x=0; x<BlurNumberOfTimes.Value; ++x)
{
Cv2.Filter2D(_modifiedMat, _modifiedMat, -1, InputArray.Create(kernel));
}
// ローカルミーンフィルタ
Cv2.FastNlMeansDenoising(_modifiedMat, _modifiedMat, (float)NonLocalMeanH.Value);
// ラプラシアンフィルタ
Mat edge = _modifiedMat.Clone();
Cv2.Laplacian(_modifiedMat, edge, MatType.CV_8UC1, LaplacianKsize.Value);
/*
Cv2.Laplacian(_modifiedMat, edge, MatType.CV_64FC1, LaplacianKsize.Value);
// 8bitに変換
edge = edge * 255;
edge.ConvertTo(edge, MatType.CV_8UC1);
*/
// 減算
_modifiedMat = _modifiedMat - edge;
// ローカルミーンフィルタ
Cv2.FastNlMeansDenoising(_modifiedMat, _modifiedMat, (float)NonLocalMeanH.Value);
var b = (WriteableBitmap)BitmapSourceConverter.ToBitmapSource(_modifiedMat);
b.Freeze();
ModifiedImage.Value = b;
});
// クリア処理
ClearCommand.Subscribe(_=>{
initializeModifiedImage();
});
}
}
}
他のソースファイルに変更なし。
使い方
操作方法は以前の記事のサンプルと同じになります。
C#でWPF学習中「OpenCVSharp」
PythonでOpenCVを使った画像加工をしているのですが、好みの画像となる設定を探すため、フィルターに引き渡す値の調整をし何度もスクリプトを実行しています。スクリプトだと、その調整作業が面倒なのでWPFで簡単なGUIを作ってみました。実...
前回グレースケール読み込み、ぼかし処理、ノンローカルミーンフィルタを行いましたが、今回はその後にラプラシアンフィルタで輪郭を抽出し、元画像に対し減算することで輪郭を強調しています。
ぼかし処理された画像に輪郭が強調されたことで手書きの絵のような感じになりました。
コメント