16.OpenCVで画像処理アプリを作ろう(画像保存機能)

こんにちは、ゆたんぽです。

今回は画像処理を行った画像の保存機能を追加していきます。

今までは画像処理した画像はアプリ上に表示されるだけで保存できませんでしたので実装していきます。

それではお願いします。


画像保存の構想

実装の前にどうやって保存するか考えを載せていきます。

イメージを図で示してみました。

①画像処理の選択


まず、HambergerMenuで作成したボタンにより、どの画像処理を行うか選択します。

この時、MainWindowViewModelではImageProcessの変数Ipeを持っており、ボタンに合わせてIpeに画像処理のクラスが格納されます。

これは、以前説明した通りシングルトンで唯一インスタンス化されたクラスですので共有されています。

②Saveボタン押下

ここが今回紹介する部分になりますが、左上にボタンを追加して、ボタンが押されたらMainWindowのIpeに作成するImageSave関数が走るようにします。

そのため、画像処理のクラスが継承しているImageProcessのクラスにImageSaveメソッドを追加します。

③ImageSaveの処理

このImageSaveメソッドでは、ImageProcessにMat型のSaveImageを持たせて、SaveImageを選択したフォルダに保存する処理を記載します。

そのため、SaveImageでは、画像処理後のMat画像をSaveImageにを保存することで画像処理後の画像を保存することができるようになります。

以上の考えで実装を進めていきます。


画像保存の実装

MainWindow.xaml

左上の部分に保存ボタンを追加します。

<mah:MetroWindow x:Class="OpenCV_Prism.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:prism="http://prismlibrary.com/"
        prism:ViewModelLocator.AutoWireViewModel="True"
        xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
        xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks"
        xmlns:mns="clr-namespace:OpenCV_Prism.MahAppsHamburgerMenu.Menus"
        Title="{Binding Title}"
        TitleCharacterCasing="Normal"
        Width="auto"
        Height="auto"
        MinWidth="900"
        MinHeight="600">
    <!--左側のボタン-->
    <mah:MetroWindow.LeftWindowCommands>
        <mah:WindowCommands>
            <!--保存ボタン-->
            <Button Command="{Binding SaveCmd}"
                    ToolTip="Save"
                    Margin="3 0 0 0">
                <iconPacks:Material Kind="Floppy"/>
            </Button>
        </mah:WindowCommands>
    </mah:MetroWindow.LeftWindowCommands>
////////////////////以下省略//////////////////////////

mah:MetroWindow.LeftWindowCommandsを使用することで左上にボタンを追加します。

ボタンの追加は通常通りで、iconPacks:Material Kind=”Floppy”を使用することでよくあるSaveボタンのアイコンにすることができます。

コマンド名は、SaveCmdです。

MainWindowViewModel

ボタンのコマンドとデータバインディングを行います。

SaveExeと紐づけて後ほどメソッドを追加します。

SaveExeには、Ipe.ImageSave()を実装しておいてください。

次で実装します。

namespace OpenCV_Prism.ViewModels
{
    public class MainWindowViewModel : BindableBase
    {
        private readonly IRegionManager _regionManager;

        ImageProcess Ipe;

        protected CompositeDisposable disposable { get; } = new CompositeDisposable();

        private string _title = "OpenCVApp";
        public string Title
        {
            get { return _title; }
            set { SetProperty(ref _title, value); }
        }
        #region 【HambergerMenu】

        /// <summary>HamburgerMenuのメニュー項目を取得します。</summary>
        public ObservableCollection<HamburgerMenuItemViewModel> MenuItems { get; } = new ObservableCollection<HamburgerMenuItemViewModel>();

        /// <summary>HamburgerMenuのオプションメニュー項目を取得します。</summary>
        public ObservableCollection<HamburgerMenuItemViewModel> OptionMenuItems { get; } = new ObservableCollection<HamburgerMenuItemViewModel>();

        /// <summary>HamburgerMenuで選択しているメニュー項目を取得・設定します。</summary>
        public ReactivePropertySlim<HamburgerMenuItemViewModel> SelectedMenu { get; set; }

        #endregion

        #region 【コマンド】

        public DelegateCommand SaveCmd { get; }

        //public DelegateCommand ShowImgageProcess { get; }//①

        //public DelegateCommand ShowImageThreshold { get; }

        #endregion

        public MainWindowViewModel(IRegionManager regionManager)
        {
            Ipe = AppSetting.Instance.IPr;

            this.initialilzeMenu();

            SelectedMenu = new ReactivePropertySlim<HamburgerMenuItemViewModel>(null)
                .AddTo(this.disposable);

            this.SelectedMenu.Subscribe(i => this.onSelectedMenu(i));

            _regionManager = regionManager;

            _regionManager.RegisterViewWithRegion("DisplayArea", typeof(DisplayArea));

            SaveCmd = new DelegateCommand(SaveExe);
        }
///////////////////////////////中略////////////////////////////////////////
        private void SaveExe()
        {
            Ipe.ImageSave();
        }
    }
}

ImageProcessへImageSaveメソッドを追加 

ImageProcessにImageSaveメソッドを作ります。

その前に保存するための画像プロパティSaveImageを作成します。

SaveImageはMat型です。

このプロパティには画像選択時と画像処理後のMat型画像を入れるようにしていきます。

namespace OpenCV_Prism.Abstract
{
    [AddINotifyPropertyChangedInterface]

    public abstract class ImageProcess :UnitBase,INotifyPropertyChanged
    {
        #region 【メンバー】

        protected BitmapImage  _originImage;

        #endregion

        #region 【プロパティ】

        public BitmapImage Image { get; set; }

        public Mat SaveImage { get; set; }

        /// <summary>
        /// パラメータ管理
        /// </summary>
        public ParamManager PrmManager { get; protected set; }

        #endregion

        #region 【コンストラクタ】

        public ImageProcess()
        {
            _originImage = new BitmapImage();
            Image = new BitmapImage();
            SaveImage = new Mat();

            PrmManager = new ParamManager();
        }

        #endregion

///////////////////////////////中略///////////////////////////////////////////

        //画像保存メソッド
        public void ImageSave()
        {
            if ( SaveImage.Height == 0)
            {
                MessageBox.Show("保存する画像がありません");
                return;
            }

            SaveFileDialog openFileDialog = new SaveFileDialog();

            // ダイアログのタイトル
            openFileDialog.Title = "保存先のフォルダを選択してください。";

            // デフォルトのフォルダ
            openFileDialog.InitialDirectory = @"C:\Users\Public\Pictures";

            //現在日時取得
            DateTime dt = DateTime.Now;

            string nowTime = dt.ToString("_yyyy_MMdd_HHmm");

            string saveFileName = this.ToString() + "_" + nowTime;

            // ダイアログボックスに表示する文字列
            openFileDialog.FileName = saveFileName + ".jpg";

            // フォルダのみを表示
            openFileDialog.Filter = "画像|*.jpg";

            // 存在しないファイル指定時の警告
            openFileDialog.CheckFileExists = false;

            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {

                //保存ファイルの取得
                var imageSavePath = openFileDialog.FileName;

                //画像の保存
                Cv2.ImWrite(imageSavePath, SaveImage);

                MessageBox.Show("画像を保存しました");
            }
        }

        //画像処理
        public abstract void myRun();

        #endregion

    }
}

ハイライト部分のプロパティ追加でSaveImageに保存したい画像が格納されるようになります。

ここで、保存処理を行っています。

次にImageSaveです。

まず、If文でSaveImageに画像が入っていないか調べています。

画像がない場合、MessageBoxで知らせます。

次にSaveFileDialogをインスタンス化しています。

これは、保存するフォルダを開くためのものです。

openFileDialog.Titleでタイトルを記載し、openFileDialog.InitialDirectoryでデフォルトのフォルダを設定できます。

saveFileNameでは現在時刻と自分自身を保存する画像名前として準備しています。

openFileDialog.FileNameではダイアログを開いた際に表示される文字列を追加できます。

ここで先ほどのsaveFileNameを使用しています。

openFileDialog.Filter部分では、表示するファイルにフィルターをかけることができます。

今回の場合、画像が表示されればよいので保存形式の.jpgのみ表示するように設定しています。

openFileDialog.CheckFileExistsは特になくてもよいです。

次に、if (openFileDialog.ShowDialog() == DialogResult.OK)部分でフォルダを表示し、OKボタンが押されることを確認しています。

openFileDialog.FileNameで保存する対象のパスが取得できるのでimageSavePathに格納し、Cv2.ImWriteで画像とパスを指定することで任意のフォルダへ画像の保存を行っています。

その後、保存を通知してあげましょう。

以上で実装は終わりです。

後は、各画像処理後にSaveImageに画像処理した画像を格納することを行ってください。

動作確認

以下のように画像が保存されれば成功です。


まとめ&次回予告

今回は保存機能を追加しました。

画像処理の後で保存できるようになるので他の処理を加えた後に他の画像処理をするなど汎用性は上がると思います。

メモリ上で画像の受け渡しできた方がよいですけどね、、、

そういったところも今後実装していきます。

次回は、OpenCVでラプラシアフィルタの追加をやっていこうと思います。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です