うぃろぅ.log

140字で綴りきれない日々の徒然備忘録

【C#】 OpenCvSharp おぼえがき 動画読込み / サムネイル表示 / キャプチャー保存

うぃろぅです。

営業から「画像処理の仕事あるけど話し聞いてみない?」と提案され、上司が概要を聞きに行ったはいいもののスケジュールが厳しそうなため断るまでがここ1週間の出来事。

私は「技術検証して」って要求仕様をぶん投げられ、わりかし楽しんで調べていたのに話が流れてしまったために詰め込んだ知識が宙ぶらりんに。なのでこちらに放流していきます。

今回はOpenCvC#用ライブラリ、OpenCvSharpについて残していきます。

github.com

公式リポジトリはこちら。ダウンロード方法も全部書いてあります。

OpenCvSharpを使う その1 (準備) - schima.hatenablog.com

パッケージ作成者の方のブログです。なんと日本の方。崇めましょう。

まずは準備と、簡単なところからということで動画のキャプチャーを出力します。

動作環境

Windows 7 64bit
Visual Studio Professional 2017
C# 7.0 OpenCvSharp

そもそもC#じゃなくてPythonやらC++やらを使えという気がしますが、C#で作られている似たようなものがあるからそれ流用できない?と言うお話だったので…。
でも5~6年前のものだったからバージョンが太古。頑張ろう。

ちなみにメモリは8GBのため、フォームプロジェクトを作るとインテリセンス表示で毎回ゲロ重になる。というか2回ほどブルスクになった。泣きそう。

仕様

  • 出力する画像の形式は.jpg.pngを選択できるようにする
  • 画像化する範囲はフレーム単位で指定する
  • 画像化する間隔は1フレーム0.1秒間隔で指定できるようにする

これが最低限の要求っぽい。もう流れたから単純に趣味で実現することにする。

成果物

  • 初期画面

    f:id:vviilloovv:20190326140015p:plain
    見た目がダサい?私もそう思います()

  • 動画読込み完了時

f:id:vviilloovv:20190326140329p:plain
動画を読み込むとこんな感じ

パスとかは隠しています。

[変換開始]を押下すると指定のディレクトリに6桁ゼロ埋めの連番を付与して出力します。多分まだバグは残っています。気づいたら直す予定。

準備

適当に新規プロジェクトを作成し、[プロジェクト]→[NuGetパッケージの管理]。

f:id:vviilloovv:20190326142042p:plain

opencvで検索して出てくるOpenCvSharp3-AnyCPUだけ入れればOK。記事作成時点でのバージョンは4。3とは。

ソース

動画サムネイル表示

opencv.jp

shimikel.hatenablog.com

note.nkmk.me

pynote.hatenablog.com

上記記事を参照しました。動画は画像が連続している、画像は行列で扱っているということを押さえておけば基本的にはOK。詳しくは先人がわかりやすく書いてくれているから読めば大体わかるはず。

動画のパスを指定できているとします。ドラッグドロップを許可するでもファイル選択窓を開くでも決め打ちでも引数でもお好きに。

動画を扱うにはVideoCaptureクラスを使えばいいらしいです。
リソース開放が必要なのでusing句を使うのが吉。

using OpenCvSharp;は追記しておきましょう。

以下のソースでは動画のキャプチャーと動画情報を取得しています。

public bool ReadMovieInfo(string moviePath)
{
  movie = new MovieInfo(moviePath);

  using(var vc = new VideoCapture(movie.Path))
  {
    if(!vc.IsOpened()) return false;

    movie.FrameRate = (int)vc.Get(CaptureProperty.Fps);
    movie.FrameCount = (int)vc.Get(CaptureProperty.FrameCount);
    movie.FrameHeight = (int)vc.Get(CaptureProperty.FrameHeight);
    movie.FrameWidth = (int)vc.Get(CaptureProperty.FrameWidth);
    movie.SecCount = movie.FrameCount / movie.FrameRate;

    // 再生時間1秒時点のキャプチャーを取得
    var frameNo = (movie.FrameRate <= movie.FrameCount) ? movie.FrameRate : 0;
    vc.Set(CaptureProperty.PosFrames, frameNo);
    vc.Read(movie.Thumbnail);
  }
  return true;
}
public class MovieInfo
{
  public string Path { get; }
  public int FrameRate { get; set; }
  public double SecCount { get; set; }
  public int FrameCount { get; set; }
  public int FrameHeight { get; set; }
  public int FrameWidth { get; set; }
  public Mat Thumbnail { get; set; } = new Mat();

  public MovieInfo(string path)
  {
      Path = path;
  }
}

動画情報は独自クラスのプロパティに設定しています。

VideoCapture#Set(CaptureProperty, int/double)で動画の参照する箇所を設定し、VideoCapture#Read(Mat)で引数に指定したMatに画像が設定されます。

これを単純に表示窓に出すだけならCv2#ImShow(string, Mat)でできますが、フォーム上に表示するなら一工夫が必要です。

プログラムジャンク箱: MatをFormのpictureBoxに表示

こちらの記事をパク参考にしてPictureBoxに表示させます。

using OpenCvSharp.Extensions;を追記してから以下を作成。

public void DisplayThumbnail(PictureBox pictureBox)
{
  var dispSize = new Size(pictureBox.Width, pictureBox.Height);
  var dispImg = movie.Thumbnail.Resize(dispSize);
  pictureBox.Image = BitmapConverter.ToBitmap(dispImg);
}

サムネイル表示に関してはこれでOK。

動画キャプチャー出力

あらかじめ

  • 出力フォーマット
  • キャプチャー開始フレーム
  • キャプチャー取得間隔
  • 出力キャプチャー数

を計算/設定しておくものとします。

以下ソース。

留意点をいくつか。

  • movieは上記MovieInfoクラスのインスタンスです。
  • outDirectoryPathは出力先のディレクトリパスです。適当に設定します。
  • 出力フォーマットはインデックス番号で扱っていますが悪手です。ごめん。
/// <summary>
/// 動画キャプチャー保存
/// </summary>
/// <param name="outFormat">出力形式</param>
/// <param name="start">開始フレーム</param>
/// <param name="interval">キャプチャー取得間隔</param>
/// <param name="outCount">出力数</param>
public void SaveCapture(int outFormat, int start, int interval, int outCount)
{
  // 出力形式が2種類のため三項演算子
  var fileName = Path.GetFileNameWithoutExtension(movie.Path);
  var ext = (outFormat == OUT_FORMAT_JPEG) ? ".jpg" : ".png";

  // 動画読込み
  using(var vc = new VideoCapture(movie.Path))
  {
    if (!vc.IsOpened())
    {
      CautionBox.Show("動画読込みに失敗しました");
      return;
    }
    var frameNo = start;
    var outCap = new Mat();

    // キャプチャー保存
    Enumerable.Range(1, outCount).ToList().ForEach(i =>
    {
      vc.Set(CaptureProperty.PosFrames, frameNo);
      vc.Read(outCap);
      var outName = $"{fileName}_{i:D6}{ext}";
      var outPath = Path.Combine(outDirectoryPath, outName);
      Cv2.ImWrite(outPath, outCap);
      frameNo += interval;
    });
  }
  MessageBox.Show("出力完了", "", MessageBoxButtons.OK, MessageBoxIcon.Information);
}

CautionBoxクラスも自作。ボタンとアイコンが同じものを使いまわすので作っちゃった方がコピペより美しい(と思い込んでいるの)です。

public static class CautionBox
{
  public static void Show(string msg)
  {
    MessageBox.Show(msg, "", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
  }
}

Cv2#ImWrite(string, Mat) で指定した名前で出力します。特別なことをしなくとも.jpg.pngはファイル名にそれぞれの拡張子を付与するだけで表示できました。すごい。


今回はここまで。「リセット判定」とか「黒画判定」という単語からわかる方もいるとは思いますが画像処理と言うよりは映像処理系です。引き続きお勉強していきます。

OpenCvSharp、最近の情報が調べてもあんまり出てこないんですよね。まぁPythonとかC++とかを使えって話だと思いますが。仕事で使わないにしても画像処理を学ぶいい機会だと考えているので続けていきたい所存。状況が変わったら無理になりますが。

ではまた。