まさかろぐ

プログラムのこと、DTMのこととか

カテゴリー: C++

OpenCV&C++ でアクリルキーホルダーの重心を調べる

ゆいのあ氏(@yuinore)が書いた↓の記事を見て、C++だとどれくらい長くなるのかな~と適当につぶやいたところ、うえぽん氏(@imaginary_uepon)が勢いで書いてくれました。

うえぽん氏が長くなるよう?Windows Imaging Componentを使って低レイヤーな部分も含めて書いてくれたので、私はOpenCVを使うとどれくらい短くなるか試してみました(ぶち壊し…)。

プログラム

以下がOpenCVでアクリルキーホルダーの重心を調べるプログラムです。

#include <iostream>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/core.hpp>

int main()
{
    cv::Mat image = imread("image.png", cv::IMREAD_UNCHANGED);

    // アルファ値を重みとした座標合計, およびアルファ値の合計を算出
    cv::Vec2d posSum(0, 0);
    double alphaSum = 0;
    for (int i = 0; i < image.rows; ++i)
    {
        for (int j = 0; j < image.cols; ++j)
        {
            double alpha = image.at<cv::Vec4b>(i, j)[3] / 255.0;
            posSum += cv::Vec2d(j, i) * alpha;
            alphaSum += alpha;
        }
    }

    if (alphaSum > 0)
    {
        // 座標の重み付き平均(=重心)を算出
        cv::Vec2d center = posSum / alphaSum;

        // ピクセルの大きさを考慮して0.5px右下にずらす
        center += cv::Vec2d(0.5, 0.5);

        std::cout << center << std::endl;
    }
    else
    {
        std::cout << "error: no alpha" << std::endl;
    }

    return 0;
}

40行くらい。
OpenCVはアルファブレンド付き画像に完全対応しているわけではないようなので、アルファ値をとるにはIMREAD_UNCHANGEDで読み込む必要があります。
この際、エンディアンの影響なのか縦横が逆になったりr/g/bが逆向き(BGRA)になったりするので注意が必要です。

試してみる

適当に描いた画像で試してみたところ、以下の座標になりました。だいたいあってそう。

AKAZE/ORB/BRISKでホモグラフィー行列推定(OpenCV3)

OpenCVで特徴点マッチングを行い、一方の画像がどこにあるかをボックスで表示するC++のサンプルを探していたところ、下のページに行き着きました。
しかし、OpenCV2時代のものらしく動かなかったので、今回OpenCV3用に修正しました。

どうやらOpenCV3からは特徴点の種類を簡単に変更できるようにするため、クラスの構成が変更されたみたいです。

OpenCV3用に修正したプログラム

#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv/cv.hpp>

// --------------------------------------------------------------------------
// main(Number of arguments, Argument values)
// Description  : This is the entry point of the program.
// Return value : SUCCESS:0  ERROR:-1
// --------------------------------------------------------------------------
int main(int argc, char **argv)
{
    // ORB
    auto detector = cv::ORB::create();
 
    // 読み込み
    cv::Mat image1 = cv::imread("box.png");
    cv::Mat image2 = cv::imread("box_in_scene.png");

    // 特徴点1
    std::vector<cv::KeyPoint> keypointsA;
    cv::Mat descriptorsA;
    detector->detectAndCompute(image1, cv::noArray(), keypointsA, descriptorsA);
 
    // 特徴点2
    std::vector<cv::KeyPoint> keypointsB;
    cv::Mat descriptorsB;
    detector->detectAndCompute(image2, cv::noArray(), keypointsB, descriptorsB);
 
    // マッチング
    cv::BFMatcher matcher(cv::NORM_HAMMING, true);
    std::vector<cv::DMatch> matches;
    matcher.match(descriptorsA, descriptorsB, matches);
 
    // 最小距離
    double min_dist = DBL_MAX;
    for (int i = 0; i < (int)matches.size(); i++) { 
        double dist = matches[i].distance;
        if (dist < min_dist) min_dist = dist;
    }
 
    // 良いペアのみ残す
    std::vector<cv::DMatch> good_matches;
    for (int i = 0; i < (int)matches.size(); i++) { 
        if (matches[i].distance < 3.0 * min_dist) good_matches.push_back(matches[i]);
    }
 
    // 対応点の表示
    cv::Mat img_matches;
    cv::drawMatches(image1, keypointsA, image2, keypointsB, good_matches, img_matches, cv::Scalar::all(-1), cv::Scalar::all(-1), std::vector<char>(), 0*cv::DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
     
    // 十分な対応点がある
    if (good_matches.size() > 10) {
        std::vector<cv::Point2f> obj, scene;
        for (int i = 0; i < (int)good_matches.size(); i++) {
            obj.push_back(keypointsA[good_matches[i].queryIdx].pt);
            scene.push_back(keypointsB[good_matches[i].trainIdx].pt);
        }
 
        // ホモグラフィー行列を計算
        cv::Mat H = cv::findHomography(obj, scene, cv::RANSAC);
        //std::cout << H << std::endl;
 
        // 行列が空ではない
        if (!H.empty()) {
            std::vector<cv::Point2d> obj_corners(4), scene_corners(4);
            obj_corners[0] = scene_corners[0] = cv::Point2d(0,           0);
            obj_corners[1] = scene_corners[1] = cv::Point2d(image1.cols, 0);
            obj_corners[2] = scene_corners[2] = cv::Point2d(image1.cols, image1.rows );
            obj_corners[3] = scene_corners[3] = cv::Point2d(0,           image1.rows);
 
            // ホモグラフィ行列の推定
            cv::perspectiveTransform(obj_corners, scene_corners, H);
 
            // 緑の線で囲む (開始点を元画像が左にあるので右にオフセット)
            cv::line(img_matches, scene_corners[0] + cv::Point2d(image1.cols, 0), scene_corners[1] + cv::Point2d(image1.cols, 0), cv::Scalar(0, 255, 0), 4);
            cv::line(img_matches, scene_corners[1] + cv::Point2d(image1.cols, 0), scene_corners[2] + cv::Point2d(image1.cols, 0), cv::Scalar(0, 255, 0), 4);
            cv::line(img_matches, scene_corners[2] + cv::Point2d(image1.cols, 0), scene_corners[3] + cv::Point2d(image1.cols, 0), cv::Scalar(0, 255, 0), 4);
            cv::line(img_matches, scene_corners[3] + cv::Point2d(image1.cols, 0), scene_corners[0] + cv::Point2d(image1.cols, 0), cv::Scalar(0, 255, 0), 4);
        }
    }
 
    // 表示
    cv::imshow("camera", img_matches);
    cv::waitKey(0);
 
    return 0;
}

元記事のプログラムからの変更箇所はinclude部分と13~28行目の特徴点抽出部分のコードです。OpenCV3ではクラスの構成が少し変わっています。

gccの場合は以下のコマンドでコンパイルが通ると思います。

$ g++ [ファイル名].cpp `pkg-config --cflags --libs opencv`

特徴点の種類について

はじめのauto detector = cv::ORB::create();の中の「ORB」を変更するだけで使用する特徴点の種類を変更できます。AKAZE/ORB/BRISKの3種類が使用可能のようです。

特徴量で有名どころのSIFT/SURFは特許が取られているらしく、商用利用不可らしいです。その関係でopencv_contribという拡張モジュールに隔離されているので、使用するには別途これを導入する必要があるようです。

試してみる

とりあえずAKAZEとORBとBRISKを試してみた↓

AKAZE:

ORB:

BRISK:

BRISKだけやたら点が多い…
ORBも写真のクッキーの部分の特徴を全然取ってくれていないので、とりあえずこの写真を見る限りではAKAZEが良さそう?
(このあたりは屋外・屋内とか被写体によって変わってきそう)

© 2016-2018 M'z Koo-boo.