サイトアイコン CV & Technologies

回転マッチでネジの向きを判定

今回解決したい課題は、ネジの向きの判定です。状況としては、次の画像のように3つのレーンがあり、各レーンについて流れてくるネジの向きを判定する、というものです。

テンプレートマッチにより、この課題を解決したいと思います。ネジのテンプレート画像として次のネジの画像を用います。テンプレート画像を準備する上でのポイントは、この画像の背景色を実際の解析対象での背景色と合わせることです。ここでは背景を上の画像と同じく黒くしています。

下のコードでは、レーンを1度ずつ回転させ、テンプレート画像と最もマッチした角度をネジの角度としています。最後に各レーンについて、ネジの頭が上を向いていた場合に”正しい向きです”と出力するようにしています。なぜテンプレートではなくレーンを回転させる必要があるのでしょうか。それは、テンプレートを回転させてしまうと、斜めになったときに余白ができてしまうからです。この余白も含めてテンプレートマッチが行われるため、これは邪魔になってしまいます。

テンプレートマッチは画素ごとに比較を行うので、回転させる必要があります。回転に不変なSURF特徴量などを使えば回転させずにマッチさせることができますが、やや高度な内容になるので今回は扱いません。

ネジ#0177度傾いています 正しくない向きです
ネジ#1  0度傾いています 正しい向きです
ネジ#2-176度傾いています 正しくない向きです
というように出力されるはずです。

 

ところで、画像解析と、画像処理が、厳密には異なることをご存知ですか?画像処理が画像の各ピクセルに対する演算を示すのに対し、画像解析はさらに画像から情報を取り出すことを示します。今回の例で言えば、ネジの角度を取り出す、といったようなイメージです。ディジタル画像処理という書籍がありまして、このような画像解析と画像処理の違いを含め、今回の場合でも重要な画像処理、画像解析に適した撮影方法などについても言及されています。プログラミングを習得するための本ではありませんが、実際の問題に画像解析でアプローチする上で読んでおきたい本です。

 

#include "opencv/cv.h"
#include "opencv/highgui.h"
#include <iostream>
#include <math.h>

using namespace cv;
using namespace std;

//RANE_WIDTHは各レーンの幅を表します.
#define RANE_WIDTH 50

double r(double x1,double y1,double x2,double y2){
    return pow((pow((x1-x2),2)+pow((y1-y2),2)),0.5);
}

int main(){
    
    Mat tmpl=imread("template.png");
    Mat screws=imread("screw.png");
    
    //3本のレーンがあるので3回、iでループを回しています.
    for(int i=0; i<3;i++){
        
        int roi_x=0;
        //各レーンの位置を決めます. レーンを矩形とした時の左上の点のx座標をここで指定します.
        if(i==0)roi_x=84;
        if(i==1)roi_x=155;
        if(i==2)roi_x=229;
        //レーンを切り出す処理です. Rectは切り出す位置のx座標、y座標、幅、高さを順に引数に取ります.
        //screws.rowsと書くことでscrew[Mat型]の高さを取得できます.
        Rect roi_rect = Rect(roi_x,0,50,screws.rows);
        Mat single_screw(screws.clone(),roi_rect);//実際の切り出し処理の部分
        rectangle(screws, roi_rect, Scalar(0,0,255), 1);//レーンの領域に矩形を描画(赤)

        //これから回転させるので、回転しても、はみ出ないように広いMatの中央に切り出したレーン画像をコピーする.
        Mat single_screw_large = Mat::zeros(screws.rows, screws.rows, CV_8UC3);//空のMatを用意する. 縦横ともに、レーンの高さと同じ.
        Rect copy_rect = Rect(single_screw_large.rows*0.5-RANE_WIDTH*0.5,0,RANE_WIDTH,single_screw_large.rows);//コピー先のROIの指定
        single_screw.copyTo(single_screw_large(copy_rect));//コピー処理
        
        imshow("single_screw_large", single_screw_large);//切り出した回転前のレーンを表示
        //1度ずつ回転させるときに、どの角度で最もマッチしたかを保持するための変数
        int max_angle=-1;
        double maxVal_global=-1;
        //テンプレートマッチング
        for(int angle=-180;angle<180;angle+=1){
            Mat rot_single_screw;
            float scale = 1.0;
            // 中心:画像中心
            cv::Point2f center(single_screw_large.cols*0.5, single_screw_large.rows*0.5);
            // 2次元の回転行列を計算
            const cv::Mat affine_matrix = cv::getRotationMatrix2D( center, angle, scale );
            warpAffine(single_screw_large, rot_single_screw, affine_matrix, single_screw_large.size());
            
            Mat result_img;
            matchTemplate(rot_single_screw,tmpl,result_img, CV_TM_CCOEFF_NORMED);
            Point max_pt;
            double maxVal;
            minMaxLoc(result_img, NULL, &maxVal, NULL, &max_pt);
            
            //マッチした場所に矩形を描画
            Rect roi_rect2(0, 0, tmpl.cols, tmpl.rows);
            roi_rect2.x = max_pt.x;
            roi_rect2.y = max_pt.y;
            rectangle(rot_single_screw, roi_rect2, Scalar(0,0,255*maxVal), 1);

            imshow("rot_single_screw", rot_single_screw);
            imshow("result_img", result_img);
            
            if(maxVal>maxVal_global){
                maxVal_global=maxVal;
                max_angle=angle;
            }
            waitKey(1);
        }

    return 0;
}

 

モバイルバージョンを終了