OpenCV を C++ から扱うためのサンプルコード
[History] [Last Modified] (2019/11/13 15:21:10)
ここは
趣味のプログラミングを楽しむための情報共有サービス。記事の一部は有料設定にして公開できます。 詳しくはこちらをクリック📝
Recent posts
Popular pages

概要

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

Related pages
    概要 OpenCV (C++) の基本処理について記載します。 点、ベクトル #include <opencv2/opencv.hpp> #include <iostream> int main() { cv::Point3i p(1, 2, 3); std::cout << p.x << std::endl; //=> 1 std::cout << p.y << s