OpenCV を C++ から扱うためのサンプルコード
[履歴] [最終更新] (2019/11/14 00:21:10)
最近の投稿
注目の記事

概要

Python から扱う方法ではなく C++ で OpenCV を扱うためのサンプルコードを記載します。ビルドには cmake を用います。

Debian の場合は以下のコマンドで必要なライブラリがインストールされます。

sudo apt install libopencv-dev

画像を開いてウィンドウに表示

main.cpp

#include <opencv2/opencv.hpp>

int main() {
    cv::Mat img = cv::imread("aaa.png", -1);
    if(img.empty()) {
        return -1;
    }
    cv::namedWindow("Example", cv::WINDOW_AUTOSIZE);
    cv::imshow("Example", img);
    cv::waitKey(0);
    cv::destroyWindow("Example");
    return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.1)
project( DisplayImage )
find_package( OpenCV REQUIRED )
add_executable( DisplayImage main.cpp )
target_link_libraries( DisplayImage ${OpenCV_LIBS} )

ビルド例

mkdir -p build
cd build/
cmake ..
make

実行例

./DisplayImage

Uploaded Image

画像のピクセルの値を取得、設定

OpenCV は RGB ではなく BGR で画像を処理します

#include <opencv2/opencv.hpp>
#include <iostream>

int main() {
    cv::Mat img = cv::imread("aaa.png", -1);
    cv::Vec3b bgr = img.at<cv::Vec3b>(10, 0);
    unsigned int b = bgr[0];
    unsigned int g = bgr[1];
    unsigned int r = bgr[2];
    std::cout << "(r,g,b) = (" << r << ", " << g << ", " << b << ")" << std::endl;

    bgr[0] = 0;
    img.at<cv::Vec3b>(10, 0) = bgr;

    return 0;
}

出力例

(r,g,b) = (254, 166, 92)

Python での出力と一致することを確認

from matplotlib import pyplot as plt
import matplotlib.image as mpimg
img = mpimg.imread('aaa.png')
for i in range(3): 
    print(img.item(10,0,i) * 255)

254.00000005960464
166.00000530481339
92.00000211596489

動画を開いてフレームを連続してウィンドウに表示

#include <opencv2/opencv.hpp>

int main() {
    cv::namedWindow("Example", cv::WINDOW_AUTOSIZE);
    cv::VideoCapture cap;
    cap.open("sample.mov");
    cv::Mat frame;
    while(true) {
        cap >> frame;
        if(frame.empty()) {
            break;
        }
        cv::imshow("Example", frame);
        if((char)cv::waitKey(33) >= 0) { // wait 33 msec for key
            break;
        }
    }
    return 0;
}

Uploaded Image

動画のフレーム数、サイズ、フレームポジションを確認および設定

#include <opencv2/opencv.hpp>

int main() {
    cv::VideoCapture cap;
    cap.open("sample.mov");
    int frames = (int)cap.get(cv::CAP_PROP_FRAME_COUNT);
    int w = (int)cap.get(cv::CAP_PROP_FRAME_WIDTH);
    int h = (int)cap.get(cv::CAP_PROP_FRAME_HEIGHT);
    std::cout << "frames: " << frames << std::endl;
    std::cout << "dimensions: (" << w << ", " << h << ")" << std::endl;
    cap.set(cv::CAP_PROP_POS_FRAMES, 123);
    int current_pos = (int)cap.get(cv::CAP_PROP_POS_FRAMES);
    std::cout << "current_pos: " << current_pos << std::endl;
    return 0;
}

出力例

frames: 659
dimensions: (1080, 720)
current_pos: 123

畳み込み処理

入力画像を 5x5 の領域で走査して、ガウス関数にしたがった重みをつけて各領域内の 25 画素の値を平均した値を、出力画像における 5x5 の領域の中心の画素値とするような、平滑化の変換は以下のようになります。

#include <opencv2/opencv.hpp>

int main() {
    cv::Mat in = cv::imread("aaa.png");
    cv::namedWindow("Example-in", cv::WINDOW_AUTOSIZE);
    cv::namedWindow("Example-out", cv::WINDOW_AUTOSIZE);
    cv::imshow("Example-in", in);
    cv::Mat out;
    cv::GaussianBlur(in, out, cv::Size(5,5), 3, 3);
    cv::imshow("Example-out", out);
    cv::Mat out2;
    cv::GaussianBlur(out, out2, cv::Size(5,5), 3, 3);
    cv::imshow("Example-out2", out2);
    cv::waitKey(0);
    return 0;
}

Uploaded Image

フィルタとなる 5x5 の行列はカーネルともよばれます。OpenCV にはカーネルを用いて畳み込みを行うカーネル関数が多数実装されており GaussianBlur はその一つです。変換の性質上、GaussianBlur を用いる際のカーネルのサイズは奇数である必要があります。第 4,5 引数はガウス関数の x 方向と y 方向の標準偏差です。例えば標準偏差を非常に小さくしたり、カーネルのサイズを 1x1 にしたりすると、出力画像は入力画像とほぼ同じになります。

#include <opencv2/opencv.hpp>

int main() {
    cv::Mat in = cv::imread("aaa.png");
    cv::namedWindow("Example-in", cv::WINDOW_AUTOSIZE);
    cv::namedWindow("Example-out", cv::WINDOW_AUTOSIZE);
    cv::imshow("Example-in", in);
    cv::Mat out;
    cv::GaussianBlur(in, out, cv::Size(5,5), 0.00001, 0.00001);
    cv::imshow("Example-out", out);
    cv::Mat out2;
    cv::GaussianBlur(out, out2, cv::Size(1,1), 3, 3);
    cv::imshow("Example-out2", out2);
    cv::waitKey(0);
    return 0;
}

Uploaded Image

畳み込み処理におけるカーネル関数としてデルタ関数を用いると、数ピクセル毎にサンプリングを行うことができます。結果として、例えば入力画像の2分の1のサイズの画像を出力することができ、このような変換をダウンサンプリングとよびます。隣接するピクセル同士の画素値の変化について、入力画像よりもダウンサンプリングによる出力画像の方が大きくなってしまう可能性があります。出力画像(信号)に高周波数が入ってしまうことを防ぐためには、ダウンサンプリングする前に、入力画像の平滑化を行います。つまり、入力画像の隣接するピクセル同士の画素値の変化が十分小さくなるような、ローパスフィルタをかけておきます。

cv::pyrDown() 関数を用いると、Gaussian による平滑化とダウンサンプリングを行うことができます。入力画像にダウンサンプリングを繰り返し適用していくと、解像度の異なる画像の集合が得られます。これを、画像ピラミッド、あるいはスケール空間とよびます。pyrDown の pyr はピラミッドを意味します。

#include <opencv2/opencv.hpp>

int main() {
    cv::Mat in, out;
    cv::namedWindow("Example-in", cv::WINDOW_AUTOSIZE);
    cv::namedWindow("Example-out", cv::WINDOW_AUTOSIZE);
    in = cv::imread("aaa.png");
    cv::imshow("Example-in", in);
    cv::pyrDown(in, out);
    cv::imshow("Example-out", out);
    cv::waitKey(0);
    return 0;
}

Uploaded Image

畳み込みは、輪郭線などのエッジを検出するためにも利用されます。隣接するピクセル同士の画素値の変化率を取得するような、微分を行うカーネル関数を用いれば、エッジにおける画素値の変化率が大きいという仮定のもと、エッジを検出できます。こちらのページに記載の cv::Canny はエッジ検出アルゴリズムの一つです。エッジ検出はノイズの影響を受けやすいため、内部的に Gaussian フィルタを用いて平滑化してからエッジ検出します。第3引数の閾値よりも変化率が小さい画素はエッジではないとします。第4引数の閾値よりも変化率が大きい画素はエッジであるとします。更に、エッジは連続しているという仮定のもと、変化率の大きさが第3引数と第4引数の間の画素は、第4引数の閾値よりも変化率が大きい画素と連続していればエッジであるとします。cv::Canny への入力画像のチャネル数は一つでよいため cv::cvtColor でグレー画像に変換 (cvt; convert) します。

#include <opencv2/opencv.hpp>

int main() {
    cv::Mat rgb, gry, cny;
    cv::namedWindow("Example Gray", cv::WINDOW_AUTOSIZE);
    cv::namedWindow("Example Canny", cv::WINDOW_AUTOSIZE);
    rgb = cv::imread("aaa.png");
    cv::cvtColor(rgb, gry, cv::COLOR_BGR2GRAY);
    cv::imshow("Example Gray", gry);
    cv::Canny(gry, cny, 10, 100);
    cv::imshow("Example Canny", cny);
    cv::waitKey(0);
    return 0;
}

Uploaded Image

動画の出力

入力動画と同じサイズの動画を出力する例です。上記 cv::Canny エッジ検出アルゴリズムで各フレームを変換しています。キーコード 27 は Esc です。動画の拡張子 avi に対応するコーデックは複数存在します。以下では XVID を指定しています。avi に対応するものとして、他に例えば MJPG (モーション JPG) があります。動画のコーデックは以下のように確認できます。

$ file out.avi
out.avi: RIFF (little-endian) data, AVI, 1080 x 720, >30 fps, video: XviD

$ file out.avi
out.avi: RIFF (little-endian) data, AVI, 1080 x 720, 30.00 fps, video: Motion JPEG

main.cpp

#include <opencv2/opencv.hpp>
#include <iostream>

int main() {
    cv::namedWindow("Example-in", cv::WINDOW_AUTOSIZE);
    cv::namedWindow("Example-out", cv::WINDOW_AUTOSIZE);

    cv::VideoCapture capture("sample.mov");

    double fps = capture.get(cv::CAP_PROP_FPS);
    cv::Size size((int)capture.get(cv::CAP_PROP_FRAME_WIDTH),
                  (int)capture.get(cv::CAP_PROP_FRAME_HEIGHT));

    cv::VideoWriter writer;
    writer.open("out.avi", CV_FOURCC('X', 'V', 'I', 'D'), fps, size, false);

    cv::Mat bgr, gry, cny;
    while(true) {
        capture >> bgr;
        if(bgr.empty()) {
            break;
        }
        cv::imshow("Example-in", bgr);
        cv::cvtColor(bgr, gry, cv::COLOR_BGR2GRAY);
        cv::Canny(gry, cny, 10, 100);
        cv::imshow("Example-out", cny);
        writer << cny;
        char c = (char)cv::waitKey(33);
        if(c == 27) {
            break;
        }
    }
    writer.release();
    capture.release();
    return 0;
}

Uploaded Image

関連ページ
    概要 m x n 行列 A は、特異値分解 SVD (Singular Value Decomposition) によって次の形式に分解できます。 A = U \cdot W \cdot V^T W は対角行列で、対角成分は特異値とよばれます。特異値のうち大きい方からいくつかのみを残して残りを 0 にすることで、もとの行列 A を近似できます。
    概要 周期関数は三角関数の無限級数和で展開できることが知られており、フーリエ級数展開とよばれます。更に、連続した非周期関数にも適用できるようにフーリエ級数展開の考え方を拡張することができ、フーリエ変換とよばれます。これを離散化した離散フーリエ変換 (DFT: Discrete Fourier Transform) を用いると、デジタル信号の周波数解析を行うことができます。
    概要 サポートベクタマシン (SVM; Support Vector Machine) は分類アルゴリズムの一つです。二つのクラスに分類されたデータをもとに分類器を構成します。その分類器を用いると、未知のデータを二つのクラスに分類できます。OpenCV3 C++ に実装されている SVM アルゴリズムを利用して、手書き数字を 0-9 のいずれかに分類してみます。
    概要 主成分分析 PCA (Principal Component Analysis) を利用すると、多次元のデータから興味のある特徴のみを抽出して、より低次元のデータに変換できる可能性があります。PCA の応用例の一つとして、物体の方向検出があります。画像をグレースケールに変換して物体の輪郭を検出した後に、輪郭を形成する各点を x座標、y座標を持つ二次元データとみなします。この二次元データに対
    概要 OpenCV3 C++ を用いて画像から特定の色の領域を取り出す方法のうち、HSV 色空間における色相を指定する方法と、バックプロジェクション (逆投影法) を利用する方法の二つを記載します。 HSV 色空間における色相を指定する方法 色を表現する空間には RGB の他に HSV (Hue 色相、Saturation 彩度、Value 明度)
    概要 OpenCV3 C++ を用いて基本的な画像変換を行います。 サイズの変更 (resize) #include <opencv2/opencv.hpp> int main() { cv::Mat img = cv::imread("aaa.png", -1); if(img.empty()) { return -1; } cv::M
    概要 cv::Canny などで検出したエッジをもとに cv::findContours で輪郭を計算できます。輪郭に関連した処理の例を記載します。 輪郭の描画 #include <opencv2/opencv.hpp> #include <iostream> int main() { cv::Mat src = cv::imread("aaa.png", cv::IMREAD_
    概要 OpenCV (C++) の基本的なデータ型について記載します。固定長の配列と、動的にメモリ領域を確保する可変長の配列があります。 固定長配列 点クラス 点クラスは、メンバ変数に .x,y,z でアクセスできる、固定長配列の一つです。 #include <opencv2/opencv.hpp> #include <iostream> int main() { cv::Poi
    概要 こちらのページで基本的な使い方を把握した PyTorch を用いて、手書き数字の分類を行ってみます。サポートベクターマシンを用いた場合は HOG などの特徴量を考える必要がありましたが、ディープラーニングでは十分な質の良いデータがあればその必要がありません。 MNIST データの読み込み 手書き数字のデータとして、
    概要 G検定のシラバスにおける「ディープラーニングの概要」および「ディープラーニングの手法」に関連する事項を記載します。 シグモイド関数を用いた学習における勾配消失について ディープラーニングで用いられる活性化関数の一つに、シグモイド関数があります。 y = \frac{1}{1 + \exp(-x)}