[w6d2] Transformation


(Ubuntu 18.04.6 LTS)
2022.03.22.
C++、VSコードを使用
プログラマー自主走行Defcos 3期

Geometric transformation

  • Translation transformation
    ピクセル値位置のみを移動する演算.値がcv::Matオブジェクトの最大値を超える場合は、値の入力を避けるために処理する必要があります.
  • #include <iostream>
    #include "opencv2/opencv.hpp"
    
    int main()
    {
        cv::Mat src = cv::imread("./resources/lenna.bmp",cv::IMREAD_GRAYSCALE);
    
        if (src.empty()){
            std::cerr << "Image load failed!" << std::endl;
            return -1;
        }
    
        cv::Mat dst1(src.rows,src.cols,CV_8UC1,cv::Scalar(0));
        for (int y=0;y<src.rows;y++){
            for (int x=0;x<src.cols;x++){
                int x_ = x + 50;
                int y_ = y + 100;
                if (y_>=dst1.rows || y<0) continue;
                if (x_>=dst1.cols || x<0) continue;
                dst1.at<uchar>(y_,x_) = src.at<uchar>(y,x);
            }
        }
        
        cv::Mat dst2(src.rows+100,src.cols+50,CV_8UC1,cv::Scalar(0));
        for (int y=0;y<src.rows;y++){
            for (int x=0;x<src.cols;x++){
                int x_ = x + 50;
                int y_ = y + 100;
                if (y_>=dst2.rows || y<0) continue;
                if (x_>=dst2.cols || x<0) continue;
                dst2.at<uchar>(y_,x_) = src.at<uchar>(y,x);
            }
        }
    
        cv::imshow("src",src);
        cv::imshow("dst1",dst1);
        cv::imshow("dst2",dst2);
        while(cv::waitKey()!=27) continue;
        cv::destroyAllWindows();
        return 0;
    }
    コード実行結果は以下の画像に表示されます.dst 1には、領域の外にあるため、確認コードが存在する.dst 2はdst 1ウィンドウの下に存在する.dst 2はcv::Matオブジェクトのサイズを増加させ、dst 1とdst 2のオーバーラップを決定するために同じサイズを平行に移動することができる.
  • Shear transformation
    これは画像の1つの領域を押し開く演算です.

  • #include <iostream>
    #include "opencv2/opencv.hpp"
    
    int main()
    {
        cv::Mat src = cv::imread("./resources/lenna.bmp",cv::IMREAD_GRAYSCALE);
    
        if (src.empty()){
            std::cerr << "Image load failed!" << std::endl;
            return -1;
        }
        double m = 0.5;
        cv::Mat dst1(src.rows,src.cols*3/2,CV_8UC1,cv::Scalar(0));
        for (int y=0;y<src.rows;y++){
            for (int x=0;x<src.cols;x++){
                int x_ = int(x+m*y);
                int y_ = y;
                if (y_>=dst1.rows || y<0) continue;
                if (x_>=dst1.cols || x<0) continue;
                dst1.at<uchar>(y_,x_) = src.at<uchar>(y,x);
            }
        }
        
        cv::Mat dst2;
        float aff_[] = {1,0,0,0.5,1,0};
        cv::Mat aff(2,3,CV_32F,aff_);
        cv::warpAffine(src,dst2,aff,cv::Size(src.cols,src.rows*3/2));
    
        cv::imshow("src",src);
        cv::imshow("dst1",dst1);
        cv::imshow("dst2",dst2);
        while (cv::waitKey()!=27) continue;
        cv::destroyAllWindows();
        return 0;
    }
    実行結果は以下のとおりです.dst 2の作成時にwarpAffineを使用して作成し、その後Affine変換でコンテンツを処理します.

    Scale transformation


    Scale transformationは、サイズを変換する演算です.
    #include <iostream>
    #include "opencv2/opencv.hpp"
    
    int main()
    {
        cv::Mat src = cv::imread("./resources/lenna.bmp",cv::IMREAD_GRAYSCALE);
    
        if (src.empty()){
            std::cerr << "Image load failed!" << std::endl;
            return -1;
        }
    
        cv::Mat dst1(src.rows * 2, src.cols * 2, CV_8UC1, cv::Scalar(0));
        for (int y=0;y<src.rows;y++){
            for (int x=0;x<src.cols;x++){
                int x_ = x * 2;
                int y_ = y * 2;
                dst1.at<uchar>(y_,x_) = src.at<uchar>(y,x);
            }
        }
        cv::Mat dst2 = cv::Mat::zeros(src.rows*2,src.cols*2,CV_8UC1);
        for (int y_=0;y_<dst2.rows;y_++){
            for (int x_=0;x_<dst2.cols;x_++){
                int x = x_ / 2;
                int y = y_ / 2;
                dst2.at<uchar>(y_,x_) = src.at<uchar>(y,x);
            }
        }
        cv::imshow("src",src);
        cv::imshow("dst1",dst1);
        cv::imshow("dst2",dst2);
        while (cv::waitKey()!=27) continue;
        cv::destroyAllWindows();
        return 0;
    }

    dst 1は、srcのイメージに基づいた順方向マッピングである.この場合、大きさの間の各部分が空白になり、上の部分が黒の明るさで表示され、サイズの変化が正常ではありません.dst 2は、dst 2参照src値に基づく逆マッピングを用いる.上記の画像ではdst 1よりも優れた結果が見られた.

    Interpolation


    サイズを大きくすると、上記のように空の画素をどのように処理するかを実現します.同様に、ビデオを縮小する場合も、重複する画素の輝度値を調整する.OpenCVはresize関数で補間方法を決定することができる.
    void cv::resize 	( 	InputArray  	src,
    		OutputArray  	dst,
    		Size  	dsize,
    		double  	fx = 0,
    		double  	fy = 0,
    		int  	interpolation = INTER_LINEAR 
    	) 		
    OpenCVは上記の関数を提供します.dsizeは結果画像のサイズであり、cv::size()はfx、fyによって決定される.fxとfyはそれぞれx方向とy方向のスケール係数である.Interpotationには次のようなものがあります.
  • INTER_NEAREST: nearest neighbor interpolation
  • INTER_LINEAR: bilinear interpolation
  • INTER_CUBIC: bicubic interpolation
  • INTER_AREA: resampling using pixel area relation. It may be a preferred method for image decimation, as it gives moire'-free results. But when the image is zoomed, it is similar to the INTER_NEAREST method.
  • INTER_LANCZOS4: Lanczos interpolation over 8x8 neighborhood
  • Linear、cubic、Lanczosを使用すると、参照画素数が増加し、演算量も増加するので、よりスムーズに拡大できます.縮小する場合は面積方式が望ましい.
    Interpolation全体の方法は以下の通りです.
    https://docs.opencv.org/4.x/da/d54/group__imgproc__transform.html#ga5bb5a1fea74ea38e1a5445ca803ff121
    #include <iostream>
    #include "opencv2/opencv.hpp"
    
    int main()
    {
        cv::Mat src = cv::imread("./resources/lenna.bmp",cv::IMREAD_GRAYSCALE);
    
        cv::Mat dst1,dst2,dst3,dst4;
    
        cv::resize(src,dst1,cv::Size(),4,4,cv::INTER_NEAREST);
        cv::resize(src,dst2,cv::Size(),4,4,cv::INTER_LINEAR);
        cv::resize(src,dst3,cv::Size(),4,4,cv::INTER_CUBIC);
        cv::resize(src,dst4,cv::Size(),4,4,cv::INTER_LANCZOS4);
    
        cv::putText(dst1,"INTER_NEAREST",cv::Point(410,540),cv::FONT_HERSHEY_DUPLEX,0.7,cv::Scalar(0),2);
        cv::putText(dst2,"INTER_LINEAR",cv::Point(410,540),cv::FONT_HERSHEY_DUPLEX,0.7,cv::Scalar(0),2);
        cv::putText(dst3,"INTER_CUBIC",cv::Point(410,540),cv::FONT_HERSHEY_DUPLEX,0.7,cv::Scalar(0),2);
        cv::putText(dst4,"INTER_LANCZOS4",cv::Point(410,540),cv::FONT_HERSHEY_DUPLEX,0.7,cv::Scalar(0),2);
    
    
        cv::imshow("dst1",dst1(cv::Rect(400,500,400,400)));
        cv::imshow("dst2",dst2(cv::Rect(400,500,400,400)));
        cv::imshow("dst3",dst3(cv::Rect(400,500,400,400)));
        cv::imshow("dst4",dst4(cv::Rect(400,500,400,400)));
        while (cv::waitKey()!=27) continue;
        cv::destroyAllWindows();
        return 0;
    }

    複数の補間手法を用いて,4倍増幅後,部分領域のみをチェックするコードを記述した.実行結果を確認すると,最近接点から最近接点までの場合,差は確認されやすいが,その後急激な差は見にくい.画質が重要であれば、より高いフィルターを使うことができますが、一般的にはLinearだけで十分です.

    Rotation transformation


    回転変換は、特定のポイントを基準にして回転する変換です.また、回転変換では、反転マッピングまたは補間を使用して、空白のピクセルを回避する必要があります.
    Mat cv::getRotationMatrix2D 	( 	Point2f  	center,
    		double  	angle,
    		double  	scale 
    	) 	
    上記OpenCV関数を中心とした点の中心、反時計回りの回転角度、スケールを入力し、以下の形式の2 x 3 cv::Matオブジェクトを返します.

    void cv::warpAffine 	( 	InputArray  	src,
    		OutputArray  	dst,
    		InputArray  	M,
    		Size  	dsize,
    		int  	flags = INTER_LINEAR,
    		int  	borderMode = BORDER_CONSTANT,
    		const Scalar &  	borderValue = Scalar() 
    	) 		
    その後、cv::warpAffine関数を使用して変換できます.InputArrayMに2 x 3行列を入力すればよいので、cv::getRotationMatrix 2 D関数の結果値を使用できます.dsizeは結果画像のサイズを設定し、cv::Size()に設定するとsrcと同様にflagsを使用して補間方法を設定できます.
    #include <iostream>
    #include "opencv2/opencv.hpp"
    
    void trackbar_rotate(int pos, void* userdata);
    
    int main()
    {
        cv::Mat src = cv::imread("./resources/lenna.bmp",cv::IMREAD_COLOR);
    
        if (src.empty()){
            std::cerr << "src load failed!" << std::endl;
            return -1;
        }
        cv::namedWindow("dst");
        cv::createTrackbar("rotate: ","dst",0,360,trackbar_rotate,(void*)&src);
        trackbar_rotate(0,(void*)&src);
        cv::waitKey();
        cv::destroyAllWindows();
        return 0;
    }
    
    void trackbar_rotate(int pos, void* userdata){
        const cv::Mat& src = *((cv::Mat*)userdata);
        cv::Mat dst;
        float degree = (float)pos;
        cv::Point2f pt(src.cols/2.f,src.rows/2.f);
    
        cv::Mat rot = cv::getRotationMatrix2D(pt,degree,1.0);
        cv::warpAffine(src,dst,rot,cv::Size());
    
        cv::imshow("dst",dst);
    }
    上のコードは、ビデオをロードし、タスクバーで回転を実現するコードで、実行結果は以下の通りです.

    Homogeneous coordinates


    以前に使用した形式のコピーは次のように表示されます.

    回転変換とサイズ変換のみを用いると、bがゼロベクトルとなり、複数回の演算を経ても、その変換を行列の積で表すしかない.しかし,平行シフトを大きくするとbはゼロベクトルではなく,行列の積以外の項が生じるので表現が不便である.整列座標は(x,y,1)の代わりに(x,y)で座標を表す従来の方法であり、平行移動はマトリクスの積形式で表すこともできる.次に、各演算の新しい定義の方法を示します.
    Translation transform

    Shear transfrom


    Scale transform

    Rotation transform

    各演算は行列の積で表すことができ、以前のcp::warpAffine関数は2 x 3行列を受け入れてこれらの演算を実行した.

    Flip, reflection

    void cv::flip 	( 	InputArray  	src,
    		OutputArray  	dst,
    		int  	flipCode 
    	) 		
    OpenCVはflipに関数を提供し、flipcodeに基づいて対称方向を指定します.

    0は上下対称、正数は左右対称、負数は上下対称.
    #include <iostream>
    #include "opencv2/opencv.hpp"
    
    int main()
    {
        cv::Mat src = cv::imread("./resources/lenna.bmp",cv::IMREAD_COLOR);
    
        if (src.empty()){
            std::cerr << "src load failed!" << std::endl;
            return -1;
        }
    
        cv::Mat dst1, dst2, dst3;
        cv::flip(src,dst1,0);
        cv::flip(src,dst2,1);
        cv::flip(src,dst3,-1);
        cv::imshow("src",src);
        cv::imshow("dst1",dst1);
        cv::imshow("dst2",dst1);
        cv::imshow("dst3",dst1);
        while (cv::waitKey()!=27) continue;
        cv::destroyAllWindows();
        return 0;
    }

    Affine transfromation


    シミュレーション変換は、直線と平行性を保持するジオメトリ変換であり、同一性、平行移動、反射、スケール、回転、せん断が対応します.
    https://en.wikipedia.org/wiki/Affine_transformation
    Mat cv::getAffineTransform 	( 	const Point2f  	src[],
    		const Point2f  	dst[] 
    	) 		
        
    Mat cv::getAffineTransform 	( 	InputArray  	src,
    		InputArray  	dst 
    	) 	
    3つの座標をsrcとdstで受け入れ,2×3シミュレーション行列を得た.
    cv::point 2 f src[3]またはベクトルsrcを値として与えることができる.
    void cv::warpAffine 	( 	InputArray  	src,
    		OutputArray  	dst,
    		InputArray  	M,
    		Size  	dsize,
    		int  	flags = INTER_LINEAR,
    		int  	borderMode = BORDER_CONSTANT,
    		const Scalar &  	borderValue = Scalar() 
    	) 		

    Perspective transformation


    パース変換は、3 D空間内の物体を1点基準で2 D平面に投影する線形投影です.
    Mat cv::getPerspectiveTransform 	( 	InputArray  	src,
    		InputArray  	dst,
    		int  	solveMethod = DECOMP_LU 
    	) 		
    
    Mat cv::getPerspectiveTransform 	( 	const Point2f  	src[],
    		const Point2f  	dst[],
    		int  	solveMethod = DECOMP_LU 
    	) 	
    上のgetaffineTransformと似ていますが、違いは4つの座標が必要です.

    solveMethodは下に見えます.
    https://docs.opencv.org/4.x/d2/de8/group__core__array.html#gaaf9ea5dcc392d5ae04eacb9920b9674c
    void cv::warpPerspective 	( 	InputArray  	src,
    		OutputArray  	dst,
    		InputArray  	M,
    		Size  	dsize,
    		int  	flags = INTER_LINEAR,
    		int  	borderMode = BORDER_CONSTANT,
    		const Scalar &  	borderValue = Scalar() 
    	) 	
    InputArrayMはCV 32 FまたはCV 64 Fタイプの3 x 3行列であり,他のパラメータは以前と類似している.
    //bird eye view
    #include <iostream>
    #include "opencv2/opencv.hpp"
    
    int main()
    {
        cv::VideoCapture cap("./resources/test_video.mp4");
    
        if (!cap.isOpened()){
            std::cerr << "video load failed!" << std::endl;
            return -1;
        }
    
        cv::Mat frame,dst;
    
        int w=500, h = 260;
    
        std::vector<cv::Point2f> src_pts(4);
        std::vector<cv::Point2f> dst_pts(4);
    
    	src_pts[0] = cv::Point2f(474, 400);
        src_pts[1] = cv::Point2f(710, 400);
    	src_pts[2] = cv::Point2f(866, 530);
        src_pts[3] = cv::Point2f(366, 530);
    
    	dst_pts[0] = cv::Point2f(0, 0);
        dst_pts[1] = cv::Point2f(w-1, 0);
    	dst_pts[2] = cv::Point2f(w-1, h-1);
        dst_pts[3] = cv::Point2f(0, h-1);
    
        std::vector<cv::Point> pts;
        for (auto pt : src_pts){
            pts.push_back(cv::Point(pt.x,pt.y));
        }
        cv::Mat per_mat = cv::getPerspectiveTransform(src_pts,dst_pts);
        
        while(true){
            cap >> frame;
            if (frame.empty()){
                std::cout << "empty frame" << std::endl;
                break;
            }
    
            cv::warpPerspective(frame,dst,per_mat,cv::Size(w,h));
    
            cv::polylines(frame,pts,true,cv::Scalar(255,0,0),2,cv::LINE_AA);
    
            cv::imshow("frame",frame);
            cv::imshow("dst",dst);
    
            if(cv::waitKey(10)==27) break;
        }
    }

    上のコードは、表示された矩形パースをdstの矩形に変換した結果です.