OpenCVで連番画像ファイルを扱う

この投稿は[OpenCV Advent Calendar 2016](http://qiita.com/advent-calendar/2016/opencv)の13日目の記事です。

# 連番ファイル
動画を連番の画像ファイルとして扱うというシーンがよくあります。
たとえば、3桁の連番ファイルは以下のようになります。
（実際には接頭辞、接尾辞などが付くこともあります。）

```text
001.jpg
002.jpg
003.jpg
︙
```

この記事では、このような連番の画像ファイルをOpenCVで読み書きする方法を紹介します。

# cv::imread()/cv::imwrite()
OpenCVで画像ファイルを読み込むには<code>cv::imread()</code>、書き出すには<code>cv::imwrite()</code>を使います。

## 連番の動的生成
読み書きするファイルを指定するためには、ファイル名が必要になります。
ファイル名を列挙したテキストファイルを準備しておくのも1つの手段ですが、
ここでは連番をプログラムで生成することでファイル名を作成することにします。

文字列ストリームの整形を利用して連番を生成します。
連番ファイルに合わせて接頭辞や拡張子などを付ることで連番のファイル名を作成できます。

```cpp:cpp
#include <iostream>
#include <sstream>
#include <iomanip>

int main( int argc, char* argv[] )
{
    // Generate Consecutive Number File Names ( e.g. 000.jpg, 001.jpg, ..., 998.jpg, 999.jpg )
    for( int i = 0; i < 1000; i++ ){
        std::ostringstream oss;
        oss << std::setfill( '0' ) << std::setw( 3 ) << i;
        std::cout << oss.str() + ".jpg" << std::endl;
    }

    return 0;
}
```

## cv::imread()で連番画像ファイルを読み込む
<code>cv::imread()</code>に生成した連番を使って連番画像ファイルのファイル名を指定します。
ここでは、<code>input_000.jpg</code>、<code>input_001.jpg</code>のような連番画像ファイルを読み込んでいます。
連番生成の初期値を変更することで、任意の番号から読み込むこともできます。

```cpp:cpp
#include <sstream>
#include <iomanip>
#include <opencv2/opencv.hpp>

int main( int argc, char* argv[] )
{
    while( true ){
        // Generate Consecutive Number ( e.g. 000, 001, ... )
        static int i = 0;
        std::ostringstream oss;        
        oss << std::setfill( '0' ) << std::setw( 3 ) << i++;

        // Read Image from File ( e.g. input_000.jpg, input_001.jpg, ... )
        cv::Mat image = cv::imread( "input_" + oss.str() + ".jpg" );
        if( image.empty() ){
            break;
        }

        /* Image Processing */

        cv::imshow( "image", image );
        if( cv::waitKey( 30 ) >= 0 ){
            break;
        }
    }

    cv::destroyAllWindows();

    return 0;
}
```

## cv::imwrite()で連番画像ファイルを書き出す
<code>cv::imwrite()</code>に生成した連番を使って連番画像ファイルのファイル名を指定します。
ここでは、<code>output_000.jpg</code>、<code>output_001.jpg</code>のような連番画像ファイルを書き出しています。
連番生成の初期値を変更することで、任意の番号から書き出すこともできます。

```cpp:cpp
#include <sstream>
#include <iomanip>
#include <opencv2/opencv.hpp>

int main( int argc, char* argv[] )
{
    while( true ){
        // Generate Consecutive Number ( e.g. 000, 001, ... )
        static int i = 0;
        std::ostringstream oss;
        oss << std::setfill( '0' ) << std::setw( 3 ) << i++;

        cv::Mat image = cv::Mat( 480, 640, CV_8UC3 );

        /* Image Processing */

        // Write Image to File ( e.g. output_000.jpg, output_001.jpg, ... )
        cv::imwrite( "output_" + oss.str() + ".jpg", image );

        cv::imshow( "image", image );
        if( cv::waitKey( 30 ) >= 0 ){
            break;
        }
    }

    cv::destroyAllWindows();

    return 0;
}
```

# cv::VideoCapture()/cv::VideoWriter()
OpenCVで動画ファイルを読み込むには<code>cv::VideoCapture()</code>、書き出すには<code>cv::VideoWriter()</code>を使います。
実はこの<code>cv::VideoCapture()</code>、<code>cv::VideoWriter()</code>には連番画像ファイルを扱う機能があり、動画と同じように連番画像を扱うことができます。

## 連番フォーマット指定子
たとえば<code>%03d</code>のように連番のフォーマットを指定すると、3桁の整数の数値(000~)に置き換えられます。
この数値は<code>cv::VideoCapture()</code>、<code>cv::VideoWriter()</code>で読み込み・書き出すごとにインクリメントされていきます。

```text:%03d.jpg
000.jpg
001.jpg
002.jpg
︙
```

なお、インクリメントされるのは最初のフォーマット指定子のみです。
複数のフォーマット指定子がある場合、以降のフォーマット指定子はインクリメントされません。

```text:%03d_%03d.jpg
000_000.jpg
001_000.jpg
002_000.jpg
︙
```

また、整数以外のフォーマット指定子はインクリメントされないため、連番ファイルのファイル名として扱えません。

```text:%3.2f.jpg
0.00.jpg
0.00.jpg
0.00.jpg
︙
```

## cv::VideoCapture()で連番画像ファイルを読み込む
<code>cv::VideoCapture()</code>の第1引数(filename)に連番をフォーマット指定した画像ファイル名を与えます。
第2引数(apiPreference)には連番画像を扱うことを明示的に示すため、<code>cv::CAP_IMAGES</code>を指定します。
このように指定すると、1つの画像ファイルを動画の1フレームとして読み込みます。

```cpp:cpp
#include <opencv2/opencv.hpp>

int main( int argc, char* argv[] )
{
    // Create Video Capture
    cv::VideoCapture capture( "input_%03d.jpg", cv::CAP_IMAGES );
    if( !capture.isOpened() ){
        return -1;
    }

    while( true ){
        cv::Mat image;

        // Capture Image from File ( e.g. input_000.jpg, input_001.jpg, ... )
        capture >> image;
        if( image.empty() ){
            break;
        }

        /* Image Processing */

        cv::imshow( "image", image );
        if( cv::waitKey( 30 ) >= 0 ){
            break;
        }
    }

    cv::destroyAllWindows();

    return 0;
}
```

## cv::VideoCapture()で任意の番号から連番画像ファイルを読み込む
上記の例では連番の先頭番号(000)から読み込んでいますが、任意の番号から読み込みたいシーンもあると思います。
任意の番号から連番画像ファイルを読み込むには、<code>cv::VideoCapture::set()</code>に<code>cv::CAP_PROP_POS_FRAMES</code>プロパティを設定します。
たとえば、030から順に読み込みたい場合は<code>cv::VideoCapture::set( cv::CAP_PROP_POS_FRAMES, 30 )</code>のように任意の開始番号を指定することができます。
詳しくは[cv::VideoCapture()のプロパティ](#cv::VideoCapture()のプロパティ)を参照してください。

```cpp:cpp
#include <opencv2/opencv.hpp>

int main( int argc, char* argv[] )
{
    // Create Video Capture
    cv::VideoCapture capture( "input_%03d.jpg", cv::CAP_IMAGES );
    if( !capture.isOpened() ){
        return -1;
    }

    // Set Capture Start Number ( e.g. 30 )
    const double index = 30;
    capture.set( cv::CAP_PROP_POS_FRAMES, index );

    while( true ){
        cv::Mat image;

        // Capture Image from File ( e.g. input_030.jpg, input_031.jpg, ... )
        capture >> image;
        if( image.empty() ){
            break;
        }

        /* Image Processing */

        cv::imshow( "image", image );
        if( cv::waitKey( 30 ) >= 0 ){
            break;
        }
    }

    cv::destroyAllWindows();

    return 0;
}
```

また、<code>cv::VideoCapture()</code>の第1引数(filename)にフォーマット指定子を使わずに連番ファイル名を直接指定することもできます。
たとえば、030から順に読み込みたい場合は<code>input_030.jpg</code>のように指定します。
<code>cv::VideoCapture()</code>はファイル名の整数値を連番の初期番号と判断します。
そのため、任意の番号からインクリメントして読み込んでいくことができます。


```cpp:cpp
#include <opencv2/opencv.hpp>

int main( int argc, char* argv[] )
{
    // Create Video Capture ( e.g. input_030.jpg )
    cv::VideoCapture capture( "input_030.jpg", cv::CAP_IMAGES );
    if( !capture.isOpened() ){
        return -1;
    }

    while( true ){
        cv::Mat image;

        // Capture Image from File ( e.g. input_030.jpg, input_031.jpg, ... )
        capture >> image;
        if( image.empty() ){
            break;
        }

        /* Image Processing */

        cv::imshow( "image", image );
        if( cv::waitKey( 30 ) >= 0 ){
            break;
        }
    }

    cv::destroyAllWindows();

    return 0;
}
```

## cv::VideoCapture()のプロパティ
<code>cv::VideoCapture()</code>で連番ファイルを扱う場合、以下のプロパティを取得・設定できます。

### <code>cv::VideoCapture::get( int propid )</code>
<code>cv::VideoCapture()</code>で連番ファイルを扱う場合、以下のプロパティを取得できます。
<code>cv::CAP_PROP_FRAME_COUNT</code>はディレクトリに含まれる連番ファイルと認識されたファイルの数を返します。

| プロパティID                | 取得できる値       |
|:--------------------------:|:-----------------:|
| cv::CAP_PROP_FRAME_WIDTH   | 幅                |
| cv::CAP_PROP_FRAME_HEIGHT  | 高さ              |
| cv::CAP_PROP_FRAME_COUNT   | 連番ファイル数     |
| cv::CAP_PROP_POS_FRAMES    | 連番              |
| cv::CAP_PROP_POS_AVI_RATIO     | 相対位置[0.0-1.0] |

```cpp:cpp
#include <iostream>
#include <opencv2/opencv.hpp>

int main( int argc, char* argv[] )
{
    // Create Video Capture
    cv::VideoCapture capture( "input_%03d.jpg", cv::CAP_IMAGES );
    if( !capture.isOpened() ){
        return -1;
    }

    // Get Image Width ( e.g. 640 )
    const int width = static_cast<int>( capture.get( cv::CAP_PROP_FRAME_WIDTH ) );
    std::cout << "width : " << width << std::endl;

    // Get Image Height ( e.g. 480 )
    const int height = static_cast<int>( capture.get( cv::CAP_PROP_FRAME_HEIGHT ) );
    std::cout << "height : " << height << std::endl;

    // Get Image Counts ( e.g. 100 )
    const int counts = static_cast<int>( capture.get( cv::CAP_PROP_FRAME_COUNT ) );
    std::cout << "counts : " << counts << std::endl;

    while( true ){
        cv::Mat image;

        // Get Current Index ( e.g. 0, 1, ... )
        const int index = static_cast<int>( capture.get( cv::CAP_PROP_POS_FRAMES  ) );
        std::cout << "index : " << index << std::endl;

        // Get Current Relative Position ( e.g. 0.00, 0.01, ...)
        const double position = capture.get( cv::CAP_PROP_POS_AVI_RATIO );
        std::cout << "position : " << position << std::endl;

        // Capture Image from File ( e.g. input_000.jpg, input_001.jpg, ... )
        capture >> image;
        if( image.empty() ){
            break;
        }

        /* Image Processing */

        cv::imshow( "image", image );
        if( cv::waitKey( 30 ) >= 0 ){
            break;
        }
    }

    cv::destroyAllWindows();

    return 0;
}
```

### <code>cv::VideoCapture::set( int propid, double value )</code>
<code>cv::VideoCapture()</code>で連番ファイルを扱う場合、以下のプロパティを設定できます。
<code>cv::CAP_PROP_POS_FRAMES</code>や<code>cv::PROP_POS_AVI_RATIO</code>で番号や位置を指定することで、任意の番号のファイルから読み込みをはじめることができます。

| プロパティID                | 設定できる値       |
|:--------------------------:|:-----------------:|
| cv::CAP_PROP_POS_FRAMES    | 連番              |
| cv::CAP_PROP_POS_AVI_RATIO     | 相対位置[0.0-1.0] |

```cpp:cpp
#include <opencv2/opencv.hpp>

int main( int argc, char* argv[] )
{
    // Create Video Capture
    cv::VideoCapture capture( "input_%03d.jpg", cv::CAP_IMAGES );
    if( !capture.isOpened() ){
        return -1;
    }

    // Set Capture Start Number ( e.g. 30 )
    const int index = 30;
    capture.set( cv::CAP_PROP_POS_FRAMES, static_cast<double>( index ) );

    // Set Capture Start Relative Position ( e.g. 30% )
    const double position = 0.30;
    capture.set( cv::CAP_PROP_POS_FRAMES, position );

    while( true ){
        cv::Mat image;

        // Capture Image from File ( e.g. input_030.jpg, input_031.jpg, ... )
        capture >> image;
        if( image.empty() ){
            break;
        }

        /* Image Processing */

        cv::imshow( "image", image );
        if( cv::waitKey( 30 ) >= 0 ){
            break;
        }
    }

    cv::destroyAllWindows();

    return 0;
}
```

## cv::VideoWriter()で連番画像ファイルに書き出す
<code>cv::VideoWriter()</code>の第1引数(filename)に連番をフォーマット指定した画像ファイル名、第2引数(fourcc)に0、第3引数(fps)に0.0を与えます。（第2引数または第3引数が0であればよい。）
このように指定すると、動画の1フレームを1つの画像ファイルとして書き出します。

```cpp:cpp
#include <opencv2/opencv.hpp>

int main( int argc, char* argv[] )
{
    // Create Video Writer
    cv::VideoWriter writer( "output_%03d.jpg", 0, 0.0, cv::Size( 640, 480 ) );
    if( !writer.isOpened() ){
        return -1;
    }

    while( true ){
        cv::Mat image = cv::Mat( 480, 640, CV_8UC3 );

        /* Image Processing */

        // Write Image to File ( e.g. output_000.jpg, output_001.jpg, ... )
        writer << image;

        cv::imshow( "image", image );
        if( cv::waitKey( 30 ) >= 0 ){
            break;
        }
    }

    cv::destroyAllWindows();

    return 0;
}
```

## cv::VideoWriter()で任意の番号から連番画像ファイルを書き出す
連番ファイル書き出すときも同様に、任意の番号から書き出したいシーンもあると思います。
その場合も同じように、<code>cv::VideoWriter()</code>の第1引数(filename)にフォーマット指定子を使わずに連番ファイル名を直接指定します。
たとえば、030から順に書き出したい場合は<code>output_030.jpg</code>のように指定します。
<code>cv::VideoWriter()</code>はファイル名の整数値を連番の初期番号と判断します。
そのため、任意の番号からインクリメントして書き出していくことができます。

```cpp:cpp
#include <opencv2/opencv.hpp>

int main( int argc, char* argv[] )
{
    // Create Video Writer ( e.g. output_030.jpg )
    cv::VideoWriter writer( "output_030.jpg", 0, 0.0, cv::Size( 640, 480 ) );
    if( !writer.isOpened() ){
        return -1;
    }

    while( true ){
        cv::Mat image = cv::Mat( 480, 640, CV_8UC3 );

        /* Image Processing */

        // Write Image to File ( e.g. output_030.jpg, output_031.jpg, ... )
        writer << image;

        cv::imshow( "image", image );
        if( cv::waitKey( 30 ) >= 0 ){
            break;
        }
    }

    cv::destroyAllWindows();

    return 0;
}
```

## cv::VideoWriter()のプロパティ
<code>cv::VideoWriter()</code>で連番ファイルを扱う場合、取得・設定できるプロパティはありません。

## おまけ：未実装の機能について
<code>cv::imwrite()</code>では第3引数(params)にエンコードパラメータを設定することができます。
しかし、OpenCV 3.1の実装では<code>cv::VideoWriter()</code>でこのパラメータを設定することはできません。
本来であれば<code>cv::VideoWriter::set()</code>で<code>cv::imwrite()</code>と同等の設定をできるように実装されているべきです。

OpenCVはオープンソースのライブラリです。
そうです、実装すればいいのです。

→ 実装しました。(｀・ω・´)b

ぷるりくがまーじされたのでOpenCV 3.2からパラメータを設定できますヽ(๑╹◡╹)ﾉ
[Add support image save parameters in VideoWriter #7761 | OpenCV](https://github.com/opencv/opencv/pull/7761)

```cpp:cpp
#include <opencv2/opencv.hpp>

int main( int argc, char* argv[] )
{
    // Create Video Writer
    cv::VideoWriter writer( "output_%03d.jpg", 0, 0.0, cv::Size( 640, 480 ) );
    if( !writer.isOpened() ){
        return -1;
    }

    // Set Write Image Parameters ( Available from OpenCV Next Version )
    writer.set( cv::CAP_PROP_IMAGES_BASE + cv::IMWRITE_JPEG_QUALITY, 80 ); // PR#7761(Merged)

    while( true ){
        cv::Mat image = cv::Mat( 480, 640, CV_8UC3 );

        /* Image Processing */

        // Write Image to File
        writer << image;

        cv::imshow( "image", image );
        if( cv::waitKey( 30 ) >= 0 ){
            break;
        }
    }

    cv::destroyAllWindows();

    return 0;
}
```

# まとめ
好きな方法を使えばいいよ。

# 備考
以下の環境で確認しました。

* Visual Studio 2015 
* OpenCV 3.1.0