69072


Microsoft Visual Studio Community 2019  
Version 16.6.2

TargetFramework .NET Core 3.1

上記環境に於いて、C++, C++/CLI, C#連携の画像処理プログラムのサンプルを作成しています。  
githubにソースをアップしています。  
<https://github.com/Nao05215/CamTest/tree/Development>

問題の現象は、カメラの読み込みが開始された直後から  
急激にメモリの使用量が増加し、時には2GB付近まで上がってしまいます。

[![画像の説明をここに入力](https://i.stack.imgur.com/77Z1n.jpg)](https://i.stack.imgur.com/77Z1n.jpg)

GCが働くと一旦は下がりますが、しばらく動作しつづけると徐々に上がっていくような感じです。  
(手動でGC.Collect()を呼び出し続けると350MB付近で落ち着きます)  
あまりに急激に増加するので、プログラムの書き方が悪いのではと感じていますが  
原因がどのあたりにあるのかわかっていません。  
ご指摘を頂ければと思い、投稿させていただきました。

プロジェクトの内容を下記で説明します。

C++プロジェクト(StaticLiblary)でOpenCVを使用し、画像の読み込み、処理を行います。  
m_ImageにMat形式の画像を設定し、onPropertyChangedを呼び出すことでC++/CLIに変更通知を行っています。

[code]

    void Camera::setImage(const cv::Mat& value)
    {
        m_Image = value;
        onPropertyChanged("Image", &value);
    }
    
[/code]

C++/CLIプロジェクト(DynamicLiblary)でC++プロジェクトをラップします。  
ここでC++プロジェクトの変更通知から受け取ったMat形式の画像を  
Mat -> Bitmap -> BitmapSourceに変換してImageプロパティに設定しています。

[code]

    void OnObservePropertyChanged(std::string propertyName, void* value) {
        if      (propertyName == "Image") {
            auto mat = (cv::Mat*)value;
            UpdateImage(mat);
        }
        else if (propertyName == "FrameRate") {
            FrameRate = *(double*)value;
        }
    }
    
    void UpdateImage(cv::Mat *mat) {
        auto source = gcnew Bitmap(mat->cols, mat->rows, mat->step, Imaging::PixelFormat::Format24bppRgb, (IntPtr)mat->data);
        auto hbmp = source->GetHbitmap();
        try {
            Image = System::Windows::Interop::Imaging::CreateBitmapSourceFromHBitmap(
                hbmp,
                IntPtr::Zero,
                Int32Rect::Empty,
                BitmapSizeOptions::FromEmptyOptions());
        }
        finally {
            delete(source);
            DeleteObject(hbmp);
        }
    }
    
[/code]

このクラスはINotifyPropertyChangedを継承しているので  
Imageプロパティ内でOnPropertyChangedを呼び出し、C#プロジェクトへ変更通知を送っています。

[code]

    virtual property BitmapSource^ Image {
        BitmapSource^ get() {
            return _Image;
        }
        void set(BitmapSource^ value) {
            _Image = value;
            OnPropertyChanged("Image");
        }
    }
    
[/code]

C#プロジェクト(実行アプリケーション)はWPF + Prismで作成されています。  
C++/CLIプロジェクトのクラスを監視し、変更通知を受け取ると自身のプロパティを書き換えます。  
(Image以外のプロパティを省略しています)

[code]

    public class Camera : BindableBase, ICamera
    {
        private CameraWrapper wrapper = new CameraWrapper();
    
        private BitmapSource _Image;
        public BitmapSource Image
        {
            get { return _Image; }
            set { SetProperty(ref _Image, value); }
        }
    
        public Camera()
        {
            wrapper
                .ObserveProperty(x => x.Image)
                .Subscribe(x =>
                    Image = x);
        }   
    }
    
[/code]

C#プロジェクトのViewModelクラスでは上記のモデルクラスを監視し  
変更通知を受け取ると、Viewに適した形に変換しプロパティを書き換えます。  
(Imageプロパティでは特に変換は必要ありませんが、他のプロパティで必要と想定しています)  
(ここでもImageプロパティ以外を省略しています)

[code]

    public class CameraViewModel : ViewModelBase
    {
        IRegionManager _regionManager;
        ICameraService _cameraService;
    
        ICamera Camera { get; }
    
        public ReactiveProperty<BitmapSource> Image { get; private set; }
    
        public CameraViewModel()
        {
    
        }
    
        public CameraViewModel(IRegionManager regionManager, ICameraService cameraService)
        {
            _regionManager = regionManager;
            _cameraService = cameraService;
    
            Camera = _cameraService.GetCamera();
    
            if (Camera == null) return;
    
            Image = new ReactiveProperty<BitmapSource>();
            Camera
                .ObserveProperty(x => x.Image)
                .Subscribe(UpdateImage)
                .AddTo(Disposables);
        }
    
        private void UpdateImage(BitmapSource img)
        {
            if (img == null) return;
            img.Freeze();
            Application.Current?.Dispatcher?.Invoke(() =>
            {
                Image.Value = img;
            });
        }
    }
    
[/code]

現在は画像を表示させるだけなので、ViewはUserControlにImageを貼り付けただけの  
簡単なものになっています。

[code]

    <UserControl x:Class="CamTest.Modules.Camera.Views.Camera"
                 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:CamTest.Modules.Camera.Views"
                 xmlns:vm="clr-namespace:CamTest.Modules.Camera.ViewModels"
                 xmlns:prism="http://www.codeplex.com/prism"
                 prism:ViewModelLocator.AutoWireViewModel="True"
                 mc:Ignorable="d" 
                 d:DesignHeight="450" d:DesignWidth="800"
                 d:DataContext="{d:DesignInstance Type=vm:CameraViewModel}">
        <Grid>
            <Image Source="{Binding Image.Value}"/>
        </Grid>
    </UserControl>
    
[/code]


CC BY-SA 4.0
2020-07-07T01:57:01.583
68357
2020-07-28T16:50:28.150
2020-07-07T02:13:49.277
3060
38100
question
c# c++ opencv wpf
C++, C++/CLI, C#を使用したOpenCVソリューションでメモリ使用量が急激に増加する


カメラキャプチャ用の`std::thread`を生成して`cv::waitKey(1)`でウェイトを挟みながらwhileループでフレームを読み込んでいるようですが、まず画像データ用メモリ確保の回数が多すぎることが問題だと思います。

基本的にリアルタイム画像処理では、フレームごとにメモリ確保するようなことはしません。アプリ起動時や画面リサイズ時にあらかじめ必要なサイズと枚数のバッファを確保しておき、そこにデータストリームを流し込んでいくだけにして、メモリブロック自体は使いまわします。特に.NETやJavaのようなガベージコレクション任せのプラットフォームでは、マネージメモリ解放のタイミングが制御できないので、なおさらです。

バックエンドとして利用されるOpenCVのための`cv::Mat`と、プレゼンテーション層として利用されるWPFのための[`BitmapSource`](https://docs.microsoft.com/en-
us/dotnet/api/system.windows.media.imaging.bitmapsource)は必須ですが、それ以外のメモリ確保は不要です。  
WPFでは基本的に[`System.Drawing.Bitmap`](https://docs.microsoft.com/en-
us/dotnet/api/system.drawing.bitmap)を利用する必要はありません。WPF用のWICラッパーでは書き込み可能なビットマップとして[`WriteableBitmap`](https://docs.microsoft.com/en-
us/dotnet/api/system.windows.media.imaging.writeablebitmap)が用意されているので、こちらを使います。詳しくはリファレンスとサンプルを参照してください。

なお、C++およびC++/CLIはModelの記述のみに専念して、ViewModelはC#で実装してしまったほうがよいと思います。C++/CLI側では[`INotifyPropertyChanged`](https://docs.microsoft.com/en-
us/dotnet/api/system.componentmodel.inotifypropertychanged)は使わず、コールバックはデリゲートやイベントで通知します。MSVCのC++/CLIモードは他のコンパイルオプションや標準ライブラリとの相性が悪いことが多いので、場合によってはC言語関数形式でDLLエクスポートして、C#からP/Invoke経由で使うようにしてしまったほうがよいこともあります。

そのほか、WPFの`INotifyPropertyChanged`は、バインディングソースをバックグラウンドスレッド（非UIスレッド）上で更新しても、バインディングターゲットがUIスレッド上で更新されるように取り計らってくれますが、それ以外の場面でデータ競合が起きないようにスレッドセーフ性を確保するのはアプリケーションコード側の責務です。

ちなみにC++では、`_`で始まり、その次が大文字の名前は処理系のために予約されています。予約された名前をアプリケーションコードで勝手に使った場合の結果は未定義です。

  * [識別子 - cppreference.com](https://ja.cppreference.com/w/cpp/language/identifiers)

あと、C++/CLIでは`using
namespace`は避けたほうがよいです。C++/CLIではP/Invokeを使わずに、ネイティブAPIのヘッダーを直接インクルードして混合コードを記述することのほうが多いですが、そのときに名前衝突する可能性が高くなります。面倒でも完全修飾するか、個別の`using`宣言だけにとどめておいたほうがよいです。


CC BY-SA 4.0
2020-07-28T16:50:28.150
69072
2020-07-28T16:50:28.150
15413
68357
answer