C++ Opencv 動画から静止画を出力する

まずはソース

#include <iostream>
#include <string>
#include <sstream>
#include <iomanip>
#include <opencv2/opencv.hpp>

int video_to_image(std::string result_image_name, std::string video_path, std::string ext) {

  cv::VideoCapture video(video_path);

  if (!video.isOpened()) {
    return -1;
  }

  int frame_num = video.get(cv::CAP_PROP_FRAME_COUNT);

  // {image_name}_00x.ext にするための桁数の取得
  int digit = std::to_string(frame_num).length();

  cv::Mat frame;
  int i;

  for (i = 0; i < frame_num; i++) {

    // フレームを取得する
    video >> frame;

    if (frame.empty()) {
      return -1;
    }
    // {image_name}_00x.extの文字列作成
    std::stringstream ss;
    ss << result_image_name << "_" << std::setw(digit) << std::setfill('0') << i << ext;
    std::cout << ss.str() << std::endl;

    cv::imwrite(ss.str().c_str(), frame);

  }

  return i;
}

関数解説

  cv::VideoCapture video(video_path);
  if (!video.isOpened()) {
    return -1;
  }

この部分はVideoCaptureクラスのインスタンスを生成している部分です。
videoの部分がインスタンス名になり、動画情報を保持します。
video_pathは video.mp4等をパスで指定してください。
video_pathが誤りの場合次の処理でreturn -1します。

  int frame_num = video.get(cv::CAP_PROP_FRAME_COUNT);

  // {image_name}_00x.ext にするための桁数の取得
  int digit = std::to_string(frame_num).length();

この部分は少しだけ複雑(?)で保存する画像の名前に必要な情報を処理しています。
今回保存する動画の名前をhoge_000, hoge_001, ... , hoge999のような感じにしたいと思っているので、そのためには動画には全部で何枚のフレームがあるかを確認しなければいけません。
そのためint frame_num = video.get(cv::CAP_PROP_FRAME_COUNT);でフレームの数を取得しています。
その次の処理で10枚ならhoge_01, 100枚ならhoge_001 のような形にするためにフレーム数の桁数を計算しています。

  cv::Mat frame;
  int i;

  for (i = 0; i < frame_num; i++) {

    // フレームを取得する
    video >> frame;

    if (frame.empty()) {
      return -1;
    }
    // {image_name}_00x.extの文字列作成
    std::stringstream ss;
    ss << result_image_name << "_" << std::setw(digit) << std::setfill('0') << i << ext;
    std::cout << ss.str() << std::endl;

    cv::imwrite(ss.str().c_str(), frame);

  }

  return i;

cv::Matopencvの画像情報を保持するクラスになります。
cv::Mat frame;frameという名前のインスタンスを宣言しました。こ子に切り出していくフレーム情報を格納していきます。
切り出していく処理がvideo >> frame;になります。
この処理でvideoに取り込んだ動画から1フレームを切り出してframeに格納しています。

std::stringstream ss;
ss << result_image_name << "_" << std::setw(digit) << std::setfill('0') << i << ext;

ここの処理は保存する画像の名前を生成しています。
hoge_iという名前になるのですがsetwで桁数を指定してsetfillで0埋めをするという処理になります。
%03dとかと同じ処理です。

cv::imwrite(ss.str().c_str(), frame);

ようやく書き出しです。
imwriteの引数がchar型なので変換しないといけないので、ss.str()でstring型へ、それをc_str()でchar型へ変換しています。
指定した名前でframeを書き出して終了です。

最後にカウンタiを返して、フレームの枚数を返却しています。
ここはframe_numを返しても同じです。

まとめ

解説というよりは自分用メモという感じです。

github.com

ここにメインを含めたプログラムの例を置いておきます。