モーダルを閉じる工作HardwareHub ロゴ画像

工作HardwareHubは、ロボット工作や電子工作に関する情報やモノが行き交うコミュニティサイトです。さらに詳しく

利用規約プライバシーポリシー に同意したうえでログインしてください。

目次目次を開く/閉じる

OpenCV を C++ から扱うためのサンプルコード

モーダルを閉じる

ステッカーを選択してください

お支払い手続きへ
モーダルを閉じる

お支払い内容をご確認ください

購入商品
」ステッカーの表示権
メッセージ
料金
(税込)
決済方法
GooglePayマーク
決済プラットフォーム
確認事項

利用規約をご確認のうえお支払いください

※カード情報はGoogleアカウント内に保存されます。本サイトやStripeには保存されません

※記事の執筆者は購入者のユーザー名を知ることができます

※購入後のキャンセルはできません

作成日作成日
2019/11/01
最終更新最終更新
2024/04/28
記事区分記事区分
一般公開

目次

    インフラ構築と自動化が得意。TerraformとAnsibleでインフラを自動構築するお仕事が多め

    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
    

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

    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;
    }
    

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

    #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;
    }
    

    フィルタとなる 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;
    }
    

    畳み込み処理におけるカーネル関数としてデルタ関数を用いると、数ピクセル毎にサンプリングを行うことができます。結果として、例えば入力画像の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;
    }
    

    畳み込みは、輪郭線などのエッジを検出するためにも利用されます。隣接するピクセル同士の画素値の変化率を取得するような、微分を行うカーネル関数を用いれば、エッジにおける画素値の変化率が大きいという仮定のもと、エッジを検出できます。こちらのページに記載の 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;
    }
    

    動画の出力

    入力動画と同じサイズの動画を出力する例です。上記 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;
    }
    

    Likeボタン(off)0
    詳細設定を開く/閉じる
    アカウント プロフィール画像

    インフラ構築と自動化が得意。TerraformとAnsibleでインフラを自動構築するお仕事が多め

    記事の執筆者にステッカーを贈る

    有益な情報に対するお礼として、またはコメント欄における質問への返答に対するお礼として、 記事の読者は、執筆者に有料のステッカーを贈ることができます。

    >>さらに詳しくステッカーを贈る
    ステッカーを贈る コンセプト画像

    Feedbacks

    Feedbacks コンセプト画像

      ログインするとコメントを投稿できます。

      ログインする

      関連記事