手持ちの PC でしか検証していないので、環境に依存した現象かもしれません。正確さには欠けると思いますが、少なくとも自分の PC で実行する分には再現性があったので、メモ代わりに書き残しておきます。

 使用した OpenCvSharp(OpenCvSharp-AnyCPU) のバージョンは、執筆時の最新安定版である「2.4.10.20150309」です。

 リソースを破棄せずに放置しておくと、普通はそのうちガーベージコレクタが回収してくれます。しかし戻り値に Mat を返す OpenCvSharp 特有のメソッドを使うと、画像処理中に必要な量のメモリが予約できずに(メモリリークして)死ぬことがあるという現象が起こりました。画像を何枚か処理するだけなら特に問題が無かったので、てっきりその辺管理してくれるものだと思って、動画の処理にメソッドチェーンをガッツリ使ったコードを書いてしまった後にほえぇってなりました。そういう仕様なのだと思うことにして、リソースをできる限り自分で管理して明示的に開放(Dispose)するか、C# としての書き方を捨てて C++ API ライクに書くようにしたら大方問題は無くなったのですが、心が少しアンマネージドです。

目次

どういうことか

 説明するよりコードで示した方が分かりやすいと思いました。以下はメモリリークするコードと、しないコードの比較です。タスクマネージャー等でメモリの使用量を見てみるとどうなっているのかわかると思います。メモリリークしてしまうコードを実行すると、だんだんとメモリの使用量が上がっていって failed to allocate XXX bytes といったエラーが発生してしまいます。ただし、メソッドチェーンの部分以外で、メモリリークを起こす前にガーベージコレクタが働けば、きちんと動作するっぽいです。

メモリリークする


// using OpenCvSharp;
// using OpenCvSharp.CPlusPlus;
// リソースを解放しないとメモリリークする
public static void CannotAllocateMemoryMethod(int cameraId)
{
using (var capture = new VideoCapture(cameraId))
{
var srcMat = new Mat();
while (Cv2.WaitKey(1) < 0)
{
capture.Read(srcMat);
var grayMat = srcMat.CvtColor(ColorConversion.BgrToGray);
var binaryMat = grayMat.Threshold(100, 255, ThresholdType.Binary);
var dstMat = binaryMat.CvtColor(ColorConversion.GrayToBgr);
Cv2.ImShow("src", srcMat);
Cv2.ImShow("dst", dstMat);
// grayMat.Dispose();
// binaryMat.Dispose();
// dstMat.Dispose();
}
}
}
// メソッドチェーンしてもメモリリークする
public static void CannotAllocateMemoryMethod2(int cameraId)
{
using (var capture = new VideoCapture(cameraId))
{
var srcMat = new Mat();
while (Cv2.WaitKey(1) < 0)
{
capture.Read(srcMat);
srcMat.CvtColor(ColorConversion.BgrToGray)
.Threshold(100, 255, ThresholdType.Binary)
.CvtColor(ColorConversion.GrayToBgr);
Cv2.ImShow("src", srcMat);
}
}
}

メモリリークしない


// using OpenCvSharp;
// using OpenCvSharp.CPlusPlus;
// リソースを自分の管理下において明示的に開放すればメモリリークしない
public static void CanAllocateMemoryMethod(int cameraId)
{
using (var capture = new VideoCapture(cameraId))
{
var srcMat = new Mat();
while (Cv2.WaitKey(1) < 0)
{
capture.Read(srcMat);
var grayMat = srcMat.CvtColor(ColorConversion.BgrToGray);
var binaryMat = grayMat.Threshold(100, 255, ThresholdType.Binary);
var dstMat = binaryMat.CvtColor(ColorConversion.GrayToBgr);
Cv2.ImShow("src", srcMat);
Cv2.ImShow("dst", dstMat);
grayMat.Dispose();
binaryMat.Dispose();
dstMat.Dispose();
}
}
}
// OpenCV2 C++ API ライクに書いてもメモリリークしない
public static void CanAllocateMemoryMethod2(int cameraId)
{
using (var capture = new VideoCapture(cameraId))
{
var srcMat = new Mat();
var dstMat = new Mat();
while (Cv2.WaitKey(1) < 0)
{
capture.Read(srcMat);
Cv2.CvtColor(srcMat, dstMat, ColorConversion.BgrToGray);
Cv2.Threshold(dstMat, dstMat, 100, 255, ThresholdType.Binary);
Cv2.CvtColor(dstMat, dstMat, ColorConversion.GrayToBgr);
Cv2.ImShow("src", srcMat);
Cv2.ImShow("dst", dstMat);
}
}
}

 C#(OpenCvSharp) を使うときの利点として、メソッドチェーンで処理が書けるため流れが分かりやすいというのがあると思っています(ただし、チェーン中の処理でエラーが発生した場合は特定が難しいため、その辺はトレードオフですが)。それが動画に適用できないのは悲しいので、今後のアップデートに期待です。