OpenCV入門【3.0対応】(5)

OpenCV入門【3.0対応】(5)

初めてのOpenCV開発 ― coreモジュール【OpenCV 3.1.0】

2016年8月17日 改訂 (初版:2014/10/06)

OpenCVのcoreモジュールの概要を解説。そのMatクラスを使って画像データを扱う方法や、ユーティリティ関数、OpenCV 3.0より導入されたUMatクラスを紹介する。

@dandelion1124(http://atinfinity.github.io/
  • このエントリーをはてなブックマークに追加

1. はじめに

 前回、OpenCVを使ったアプリケーションのプロジェクト作成方法について解説を行いました。今回はOpenCVのcoreモジュールについて解説します。

 以下の表に、今回紹介するcoreモジュールの機能の概要をまとめました。

モジュール名 機能の概要
core 画像・行列データ構造の提供、配列操作、XMLおよびYAML入出力、コマンドラインパーサー、ユーティリティ機能など
モジュール(core)の概要

 また、筆者が確認した環境は以下の通りです。

項目 内容
OpenCVバージョン OpenCV 3.1.0
Visual Studio Visual Studio 2013 Update5
ビルド構成 x64Release
OS Windows 10 Pro(64bit)
筆者が確認した環境

2. Matクラス

 OpenCVでは画像データを格納するデータ構造としてMatクラスを提供しています。OpenCVの多くの関数の入出力は、Matクラスを介して行われます。そのため、このMatクラスを取り扱うに当たり、以下の3つの基本的な処理の方法について、例を用いながら解説します。

  • インスタンス生成
  • プロパティ取得
  • ピクセルへのアクセス

2.1 インスタンス生成

 ここではMatクラスのインスタンス生成方法について解説します。画像ファイルを読み込んでインスタンスを生成する方法もありますが、その方法については次回(imgcodecsモジュール)の記事で紹介します。

 そこで、画像の幅320px、高さ240px、赤色の画像データ生成を行う下記のコードを例に用いながら、インスタンス生成方法を解説します。

C++
cv::Mat img(cv::Size(320, 240), CV_8UC3, cv::Scalar(0, 0, 255));
画像の幅320px、高さ240px、赤色の画像データ生成を行うコード

 上記コードでポイントとなる項目と意味は以下の通りです。

項目 意味
cv::Size(320, 240) 画像サイズ(幅320px、高さ240px)
CV_8UC3 ビット深度=符号なしの8bit整数、チャンネル数=3
cv::Scalar(0, 0, 255) 画像の色。この例では赤色B=0G=0R=255
上記コードのポイントと意味

 以降は、画像のプロパティを指定するために用いる、

  • ビット深度・チャンネル数
  • cv::Size
  • cv::Scalar

についてもう少し詳しく説明を行います。

2.1.1 ビット深度・チャンネル数

 OpenCVでは、

CV_<ビット深度>{U|S|F}C<チャンネル数>
Uは符号なし、Sは符号付き、Fは浮動小数点数を表す

という表記によって画像のビット深度とチャンネル数を表現しています。

 そのため、前述のコードにある、

CV_8UC3

は、

  • ビット深度=符号なしの8bit整数
  • チャンネル数=3

という意味を表しています。

2.1.2 cv::Size

 OpenCVでは、cv::Sizeというデータ構造を使うことで画像のサイズを指定できます。cv::Sizeを使う場合、第1引数がwidth(画像の幅)、第2引数がheight(画像の高さ)となります。

 そのため、前述のコードにある、

cv::Size(320, 240)

画像サイズ(幅320px、高さ240px)という意味を表しています。

2.1.3 cv::Scalar

 OpenCVでは、cv::Scalarというデータ構造を使うことで画像の色を指定できます。ただしOpenCVの画像データは、RGB表色系の場合、cv::Scalarに対してBGRの順に指定する点に注意ください。

 そのため、前述のコードにある、

cv::Scalar(0, 0, 255)

は、赤色R=255G=0B=0)という意味を表しています。

2.2 プロパティ取得

 Matクラスのメソッドを呼ぶことで画像データの各種プロパティを取得できます。以降、各種プロパティを取得するメソッドについて例を用いながら紹介します。

2.2.1 rows、cols

 Matクラスは画像データだけでなく、行列も取り扱うことができます。このことからMatクラスには、行列の行数を参照するrows、列数を参照するcolsが、メンバー変数として定義されています。

 Matクラスのメンバー変数rowscolsを参照することで、画像の高さ/幅を取得できます。

  • cols: 画像の幅(行列の列数に対応)
  • rows: 画像の高さ(行列の行数に対応)

 以下に画像の幅と高さを取得するサンプルコードを示します。

C++
#include <opencv2/core.hpp>
#include <iostream>

int main(int argc, const char* argv[])
{
  // 幅320px、高さ240px、3チャンネルのインスタンスを生成する
  cv::Mat img(cv::Size(320, 240), CV_8UC3, cv::Scalar(0, 0, 255));

  // 画像の幅を表示する
  std::cout << "width: " << img.cols << std::endl;

  // 画像の高さを表示する
  std::cout << "height: " << img.rows << std::endl;

  return 0;
}
画像の幅と高さを取得するサンプルコード

 このサンプルコードを実行したときの標準出力は以下の通りです。この例では、imgは幅320px、高さ240pxなので以下のような標準出力が行われます。

コンソール
width: 320
height: 240
上記のサンプルコードを実行したときの標準出力
2.2.2 channelsメソッド

 Matクラスのchannelsメソッドにより画像のチャンネル数を取得できます。グレースケール画像の場合は「1」、カラー画像の場合は「3」、アルファチャンネル付きのカラー画像の場合は「4」が返ってきます。

 以下に画像のチャンネル数を取得するサンプルコードを示します。

C++
#include <opencv2/core.hpp>
#include <iostream>

int main(int argc, const char* argv[])
{
  // 幅320px、高さ240px、3チャンネルのインスタンスを生成する
  cv::Mat img(cv::Size(320, 240), CV_8UC3, cv::Scalar(0, 0, 255));

  // チャンネル数を表示する
  std::cout << "channels: " << img.channels() << std::endl;

  return 0;
}
画像のチャンネル数を取得するサンプルコード

 このサンプルコードを実行したときの標準出力は以下の通りです。この例では、img3チャンネルなので以下のような標準出力が行われます。

コンソール
channels: 3
上記のサンプルコードを実行したときの標準出力
2.2.3 depthメソッド

 Matクラスのdepthメソッドにより、画像のビット深度を取得できます。ただし、depthメソッドの戻り値はビット深度の値そのものではなく、OpenCV内に定義される定数値である点に注意してください。

 以下にinclude\opencv2\core\cvdef.hファイルで定義される定数の定義部を引用します。

C++
#define CV_8U   0
#define CV_8S   1
#define CV_16U  2
#define CV_16S  3
#define CV_32S  4
#define CV_32F  5
#define CV_64F  6
OpenCV内に定義される定数値(cvdef.hファイルから引用)

 また、OpenCV内に定義されるビット深度名とそれらの意味は以下の通りです。

ビット深度名 意味
CV_8U 符号なしの8bit整数( 0((~))255 )
CV_8S 符号付きの8bit整数( -128((~))127 )
CV_16U 符号なしの16bit整数( 0((~))65535 )
CV_16S 符号付きの16bit整数( -32768((~))32767 )
CV_32S 符号付きの32bit整数( -2147483648((~))2147483647 )
CV_32F 32bit浮動小数点数 ( -FLT_MAX..FLT_MAX, INF, NAN )
CV_64F 64bit浮動小数点数( -DBL_MAX..DBL_MAX, INF, NAN )
OpenCV内に定義されるビット深度名と意味

 以下に画像のビット深度を取得するサンプルコードを示します。

C++
#include <opencv2/core.hpp>
#include <iostream>
 
// ビット深度名を表示する自作関数
void printDepth(cv::Mat& img)
{
  // 画像のビット深度を取得する
  unsigned int depth = img.depth();

  std::string strDepth = 
    (
      depth == CV_8U  ? "CV_8U"  :
      depth == CV_8S  ? "CV_8S"  :
      depth == CV_16U ? "CV_16U" :
      depth == CV_16S ? "CV_16S" :
      depth == CV_32S ? "CV_32S" :
      depth == CV_32F ? "CV_32F" :
      depth == CV_64F ? "CV_64F" :
      "Other"
    );
  std::cout << "depth: " << strDepth << std::endl;
}
 
int main(int argc, const char* argv[])
{
  // 幅320px、高さ240px、3チャンネルのインスタンスを生成する
  cv::Mat img(cv::Size(320, 240), CV_8UC3, cv::Scalar(0, 0, 255));
 
  // ビット深度名を表示する自作関数
  printDepth(img);
 
  return 0;
}
ビット深度を取得するサンプルコード

 このサンプルコードを実行したときの標準出力は以下の通りです。この例では、imgのビット深度は符号なしの8bit整数であるため、以下のような標準出力が行われます。

コンソール
depth: CV_8U
上記のサンプルコードを実行したときの標準出力

2.3 ピクセルへのアクセス

 ここでは任意のピクセルの画素値にアクセスする方法の1つであるatメソッドについて紹介します。その他のアクセス方法については、

を参考ください。

2.3.1 画素値の参照

 Matクラスのatメソッドを以下のように呼び出すことで、変数imgにおける座標(x, y)の画素値を参照できます。ただし、このときatメソッドに渡すパラメーターは(y, x)の順に指定する必要がある点に注意が必要です。

C++
img.at<cv::vec3b>(y, x);
Matクラスのatメソッドを呼び出して画素値を参照する方法

 以下に画素値を参照するサンプルコードを示します。

C++
#include <opencv2/core.hpp>
#include <iostream>

int main(int argc, const char* argv[])
{
    // 幅320px、高さ240px、3チャンネルのインスタンスを生成する
    cv::Mat img(cv::Size(320, 240), CV_8UC3, cv::Scalar(0, 0, 255));

    // (1)座標(0, 100)の画素値を取得する
    // atに渡すパラメーターは(y, x)の順である点に注意
    // Matのデータは(RGB表色系の場合)BGRの順に並んでいる点に注意
    cv::Vec3b intensity = img.at<cv::Vec3b>(100, 0);
    uchar blue  = intensity.val[0];
    uchar green = intensity.val[1];
    uchar red   = intensity.val[2];

    // (2)画素値を表示する
    std::cout << "R: " << (unsigned int)red   << std::endl;
    std::cout << "G: " << (unsigned int)green << std::endl;
    std::cout << "B: " << (unsigned int)blue  << std::endl;

    return 0;
}
画素値を参照するサンプルコード

 このサンプルコードを実行したときの標準出力は以下の通りです。この例では、img(全てのピクセルが赤色の画像)における座標(0, 100)の画素値は赤色であるため、以下のような標準出力が行われます。

コンソール
R: 255
G: 0
B: 0
上記のサンプルコードを実行したときの標準出力
2.3.2 画素値の変更

 atメソッドを以下のように呼び出すことで、変数imgにおける座標(x, y)の画素値を変更できます。また、このときatメソッドに渡すパラメーターは(y, x)の順序である点と、Matのデータは(RGB表色系の場合)BGRの順に並んでいる点に注意が必要です(redValuegreenValueblueValueは変更したい画素値)。

C++
img.at<cv::vec3b>(y, x)[0] = blueValue;
img.at<cv::vec3b>(y, x)[1] = greenValue;
img.at<cv::vec3b>(y, x)[2] = redValue;
Matクラスのatメソッドを呼び出して画素値を変更する方法

 以下に画素値を変更するサンプルコードを示します。

C++
#include <opencv2/core.hpp>
#include <iostream>

int main(int argc, const char* argv[])
{
    // 幅320px、高さ240px、3チャンネルのインスタンスを生成する
    cv::Mat img(cv::Size(320, 240), CV_8UC3, cv::Scalar(0, 0, 255));

    // (1)座標(0, 100)の画素値を変更する
    // atに渡すパラメーターは(y, x)の順である点に注意
    // Matのデータは(RGB表色系の場合)BGRの順に並んでいる点に注意
    img.at<cv::Vec3b>(100, 0)[0] = 255;
    img.at<cv::Vec3b>(100, 0)[1] = 0;
    img.at<cv::Vec3b>(100, 0)[2] = 0;

    // (2)座標(0, 100)の画素値を取得する
    cv::Vec3b intensity = img.at<cv::Vec3b>(100, 0);
    uchar blue  = intensity.val[0];
    uchar green = intensity.val[1];
    uchar red   = intensity.val[2];

    // (3)画素値を表示する
    std::cout << "R: " << (unsigned int)red   << std::endl;
    std::cout << "G: " << (unsigned int)green << std::endl;
    std::cout << "B: " << (unsigned int)blue  << std::endl;

    return 0;
}
画素値を変更するサンプルコード

 このサンプルコードを実行したときの標準出力は以下の通りです。この例では、(1)の処理で座標(0, 100)の画素値は青色R=0G=0B=255)に変更しているため、以下のような標準出力が行われます。

コンソール
R: 0
G: 0
B: 255
上記のサンプルコードを実行したときの標準出力

3. UMatクラス

 OpenCV 3.0よりCPU/GPU実装のカプセル化を行うための仕組みであるT-API(Transparent API)が導入されました。そのためOpenCVユーザーは、T-APIで提供されるUMatと呼ばれるデータ構造を用いて実装することでCPU/GPUどちらでも動作する処理を同一コードで記述できます。

3.1 UMatの内部処理

 図1にUMatを用いた場合の内部処理の概要を示します。

UMatの内部処理
図1 UMatの内部処理

 この図を文章で説明すると、以下のような流れになります。

  1. OpenCL*1が使える環境かどうかをチェックする
  2. OpenCLが使える環境であればOpenCVで用意しているOpenCL実装を用い、そうでなければMatの実装を用いる

 ただし、図1の青で囲んだ内部処理はOpenCV内部に隠ぺいされているため、OpenCVユーザーがこれらの処理を記述する必要はありません。

 また、図2は、CVPR 2015チュートリアルで紹介されているT-APIの計測結果です。

図2 T-APIによる高速化(CVPR 2015チュートリアル資料から引用)

 この結果より、UMat(OpenCL)を用いることで、Matを用いた場合に比べて高速化できていることが分かります。ただし、CPU、GPUのスペック含め、計測環境によって効果は異なるので高速化の度合いはあくまで参考程度とした方がよいでしょう。

 UMatを使ってグレースケール化を行うサンプルコードを以下に示します。

C++
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>

#include <iostream>

int main(int argc, char *argv[])
{
  // (1)画像データをファイル(この例では「lena.jpg」)から読み込む
  cv::Mat img = cv::imread("lena.jpg", cv::IMREAD_COLOR);

  // 画像の読み込みに失敗したらエラー終了する
  if (img.empty())
  {
    std::cerr << "Failed to open image file." << std::endl;
    return -1;
  }

  // (2)UMatのインスタンスを定義する
  cv::UMat u_img, u_gray;

  // (3)MatのデータをUMatのインスタンスにコピーする
  img.copyTo(u_img);

  // (4)UMatのインスタンスを用いてグレースケール化を行う
  cv::cvtColor(u_img, u_gray, cv::COLOR_BGR2GRAY);

  return 0;
}
UMatクラスを使ったサンプルプログラム

 また、UMatついて詳しく知りたい方は以下のURLの記事も参照ください。

4. ユーティリティ関数

 OpenCVのcoreモジュールには便利なユーティリティ関数が用意されています。この章では、以下のユーティリティ関数について紹介します。それ以外の関数については「公式ドキュメント(OpenCV 3.1.0)(英語)」を参照ください。

関数名 機能
cv::getBuildInformation OpenCVのビルド情報を表示する
cv::checkHardwareSupport CPU機能を取得する
coreモジュールに用意されている便利なユーティリティ関数

4.1 cv::getBuildInformation関数

 OpenCVはユーザー自身で機能をカスタマイズしてビルドすることができます。そのため、公式にbugを報告する場合など、ビルド情報(ユーザーがOpenCVをビルドした環境、コンパイラー、依存ライブラリのバージョン、カスタマイズ情報など)をできるだけ正しく伝えることが重要になります。

 そのような理由から、OpenCVにはビルド情報を取得するためのcv::getBuildInformationというユーティリティ関数が用意されています。この関数を使ったサンプルプログラムは以下の通りです。

C++
#include <opencv2/core.hpp>
#include <iostream>

int main(int argc, const char* argv[])
{
  // ビルド情報を表示する
  std::cout << cv::getBuildInformation() << std::endl;

  return 0;
}
cv::getBuildInformationユーティリティ関数を使ったサンプルプログラム

 また、上記サンプルプログラムの出力結果(抜粋)を以下に示します。ユーザーの環境、ビルド設定によって表示は異なる場合があります。

コンソール
General configuration for OpenCV 3.1.0 =====================================
  Platform:
    Host:                        Windows 10.0.10586 AMD64
    CMake:                       3.4.1
    CMake generator:             Visual Studio 12 2013 Win64
    CMake build tool:            C:/Program Files (x86)/MSBuild/12.0/bin/MSBuild.exe
    MSVC:                        1800

  Media I/O:
    ZLib:                        build (ver 1.2.8)
    JPEG:                        build (ver 90)
    WEBP:                        build (ver 0.3.1)
    PNG:                         build (ver 1.6.19)
    TIFF:                        build (ver 42 - 4.0.2)
    JPEG 2000:                   build (ver 1.900.1)
    OpenEXR:                     build (ver 1.7.1)
    GDAL:                        NO

  Parallel framework:            TBB (ver 4.4 interface 9002)

  Other third-party libraries:
    Use IPP:                     9.0.1 [9.0.1]
         at:                     C:/dev/opencv-3.1.0/3rdparty/ippicv/unpack/ippicv_win
    Use IPP Async:               NO
    Use Eigen:                   YES (ver 3.2.7)
    Use Cuda:                    YES (ver 7.5)
    Use OpenCL:                  YES
    Use custom HAL:              NO
-----------------------------------------------------------------
上記のサンプルプログラムの出力結果(抜粋)

 「Platform」から、OpenCVのビルドに用いたコンパイラーやCMakeのバージョンなどが分かります。前述の出力例の場合、

コンソール
Platform:
  Host:                        Windows 10.0.10586 AMD64
  CMake:                       3.4.1
  CMake generator:             Visual Studio 12 2013 Win64
Platformに関する出力のみ抜粋

という表示が行われているため、

  • Windows 10(64bit)上でビルドが行われた
  • CMake 3.4.1を使用した
  • コンパイラーはVisual Studio 2013(x64ビルド)を使用した

ということが読み取れます。

 同様に「Media I/O」からは、各種画像フォーマットをエンコード/デコードするためのライブラリの導入状況、「Other third-party libraries」からは各種並列処理フレームワークなどの導入状況を把握することができます。

4.2 cv::checkHardwareSupport関数

 OpenCVにはCPU機能を取得するcv::checkHardwareSupportというユーティリティ関数が用意されています。指定したCPU機能をサポートしている場合、checkHardwareSupport関数はtrueを返します。

 また、この関数でサポート状況を取得できるCPU機能は以下の通りです。

定義名 対応するCPU機能
CV_CPU_MMX MMX
CV_CPU_SSE SSE
CV_CPU_SSE2 SSE2
CV_CPU_SSE3 SSE3
CV_CPU_SSSE3 SSSE3
CV_CPU_SSE4_1 SSE4.1
CV_CPU_SSE4_2 SSE4.2
CV_CPU_POPCNT POPCOUNT
CV_CPU_AVX AVX
CV_CPU_AVX2 AVX2
CV_CPU_NEON NEON
cv::checkHardwareSupport関数でサポート状況を取得できるCPU機能

 この関数を使ってCPUがSSE2をサポートしているかを調べるサンプルプログラムは以下の通りです。

C++
#include <opencv2/core.hpp>
#include <iostream>

int main(int argc, const char* argv[])
{
  // SSE2をサポートしているCPUかどうかをチェックする
  bool hasSSE2 = cv::checkHardwareSupport(CV_CPU_SSE2);

  // チェック結果を表示する
  std::cout << "hasSSE2: " << hasSSE2 << std::endl;

  return 0;
}
cv::checkHardwareSupport関数を使ってCPUがSSE2をサポートしているかを調べるサンプルプログラム

4.3 その他のユーティリティ関数

 coreモジュールにはエラー処理、並列処理、排他処理を記述するための機能も用意されています。詳細を知りたい方は筆者がまとめた以下の記事を参照ください。

 また、ここでは触れていませんがOpenCVをOpenGLやDirectXと連携するためのユーティリティ機能も用意されています。詳細は以下の公式ドキュメントを参照ください。

5. おわりに

 今回は、OpenCVのcoreモジュールを紹介しました。次回は、imgcodecsモジュール、highguiモジュールの機能について例を用いながら解説します。

※以下では、本稿の前後を合わせて5回分(第3回~第7回)のみ表示しています。
 連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。

3. OpenCVの環境構築(OpenCV 3.0/3.1)

OpenCV 3環境の構築方法として、「公式パッケージを使ったインストール」「CMakeを使ったライブラリのビルド」「NuGetを使ったパッケージインストール」の3ケースを解説。バージョン2.4.9から3.0/3.1に合わせて改訂。

4. 初めてのOpenCV開発 ― Visual Studio/CMake/NuGetでプロジェクト作成【OpenCV 3.0/3.1】

OpenCVを使ったアプリケーションのプロジェクト作成方法として、「Visual Studio」「CMake」「NuGet」の3つの方法を解説する。

5. 【現在、表示中】≫ 初めてのOpenCV開発 ― coreモジュール【OpenCV 3.1.0】

OpenCVのcoreモジュールの概要を解説。そのMatクラスを使って画像データを扱う方法や、ユーティリティ関数、OpenCV 3.0より導入されたUMatクラスを紹介する。

6. 初めてのOpenCV開発 ― highgui/imgcodecs/videoioモジュール【OpenCV 3.1.0】

OpenCVのhighgui、imgcodecs、videoioという3つのモジュールの概要を解説。GUI機能、画像ファイル/動画ファイルの入出力機能、カメラキャプチャ機能などのAPIと、その基本的な使い方を説明する。

7. 初めてのOpenCV開発 ― デバッグ機能およびデバッグ支援プラグイン【OpenCV 3.1.0】

OpenCVを用いたアプリケーションの開発で役立つにデバッグ機能やデバッグ支援プラグインを紹介する。また、問題が生じた場合の対処法について説明する。

サイトからのお知らせ

Twitterでつぶやこう!


Build Insider賛同企業・団体

Build Insiderは、以下の企業・団体の支援を受けて活動しています(募集概要)。

ゴールドレベル

  • グレープシティ株式会社
  • 日本マイクロソフト株式会社