OpenCV実践シリーズ - 直線の近似

OpenCV 戦闘 - 直線のフィッティング

0. 序文

一部のコンピューター ビジョンアプリケーションでは、画像内の線を検出するだけでなく、線の位置と方向を正確に推定することも必要です。このセクションでは、指定された一連の点に最もよく適合するラインを見つける方法について説明します。

1. 直線フィッティング

最初に行うことは、直線に沿って整列する可能性が高い画像内の点を特定することです。線分はハフ変換を使用して検出できます。を使用してcv::HoughLinesP検出された線分は、linesベクトルに含まれますstd::vector<cv::Vec4i>

(1)可能な点のセット、たとえば1線分を抽出するには、黒い画像上に白い線を描き、Canny線の検出に使用される輪郭画像とそれを交差させます。

int n = 0;
// 提取探测到的第一条线段的轮廓像素
cv::Mat oneline(image.size(), CV_8U, cv::Scalar(0));
cv::line(oneline, cv::Point(li[n][0], li[n][1]), cv::Point(li[n][2], li[n][3]), cv::Scalar(255), 3);
cv::bitwise_and(contours, oneline, oneline);

上記のコードには、指定された線に関連付けることができる点のみを含めることができます。許容範囲を導入するために、特定の太さ (線の幅は ) の線を描画し、定義された近傍内のすべての点が受け入れられるようにします3結果は以下の図に示されています (観察しやすいように、画像のピクセル値は反転されています)。

線に関連付けられた点
(2)二重ループを通じて、このセットの中点の座標をcv::Point関数の に挿入しますstd::vector(浮動小数点座標も使用できます。つまりcv::Point2f)。

std::vector<cv::Point> points;
//  迭代像素以获得所有点位置
for (int y=0; y<oneline.rows; y++) {
    
    
    uchar* rowPtr = oneline.ptr<uchar>(y);
    for (int x=0; x<oneline.cols; x++) {
    
    
        if (rowPtr[x]) {
    
    
            points.push_back(cv::Point(x, y));
        }
    }
}

(3)次の関数を呼び出すと、cv::fitLine最も適合する線分を簡単に見つけることができます。

// 拟合直线
cv::Vec4f line;
cv::fitLine(points, line, cv::DIST_L2, 0, 0.01, 0.01);

上記のコードは、単位方向ベクトル (cv::Vec4f最初の 2 つの値) と線上の点の座標 (最後の 2 つの値)cv::Vec4fの形式で線分方程式のパラメーターを提供します。結果の例では、方向ベクトルは(0.83, 0.55)、点座標は です(366.1, 289.1)cv::fitLine関数の最後の 2 つのパラメーターは、必要なセグメント パラメーターの精度を指定するために使用されます。

(4)線分方程式を使用して特定のプロパティを計算することができ、例として、画像上に適合した線を描くことができます。たとえば、長さ100ピクセルと幅ピクセルで3黒い線分を描画するには、次のようにします。

int x0 = line[2];
int y0 = line[3];
int x1 = x0+100*line[0];
int y1 = y1+100*line[1];
// 绘制直线
cv::line(image, cv::Point(x0, y0), cv::Point(x1, y1), 0, 2);

描画結果は次の図に示されています。

直線を当てはめる
一連の点を線に当てはめるのは数学の古典的な問題であり、cv::fitLineこの関数は各点から線までの距離の合計を最小化することによって線を当てはめます。
さまざまな距離関数を使用できます。ユークリッド距離 ( CV_DIST_L2) は計算が最も簡単で、標準の最小二乗直線近似に対応します。点セットに外れ値 (つまり、ラインに属さない点) が含まれている場合、他の外れ値の影響が少ない距離関数を選択できます。最小化は、Mラインからの距離に反比例する重みを使用して重み付き最小二乗問題を反復的に解く推定手法に基づいています。
この関数を使用すると、点の集合を直線にcv::fitLine当てはめることもできます。このとき、入力はまたは型のデータの集合であり、出力の型は ですこの関数は、一連の点を楕円に適合させ、その楕円が内接する回転された長方形 (インスタンス) を返します。3Dcv::Point3icv::Point3fstd::Vec6fcv::fitEllipse2Dcv::RotatedRect

cv::RotatedRect rrect= cv::fitEllipse(cv::Mat(points));
cv::ellipse(image,rrect,cv::Scalar(0));

cv::ellipse計算された楕円関数グラフをプロットする関数。

2. 完全なコード

ライブラリ ファイルlinefinder.hと行検出edgedetector.hセクションを参照できます。主な関数コードは次のとおりです。fitLine.cpp

#include <iostream>
#include <vector>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include "linefinder.h"
#include "edgedetector.h"

#define PI 3.1415926

int main() {
    
    
    // 读取输入图像
    cv::Mat image = cv::imread("road.jpg", 0);
    if (!image.data) return 0;
    cv::namedWindow("Original Image");
    cv::imshow("Original Image", image);
    // 计算 Sobel
    EdgeDetector ed;
    ed.computeSobel(image);
    cv::namedWindow("Sobel (orientation)");
    cv::imshow("Sobel (orientation)", ed.getSobelOrientationImage());
    cv::imwrite("ori.png", ed.getSobelOrientationImage());
    // 低阈值 Sobel
    cv::namedWindow("Sobel (low threshold)");
    cv::imshow("Sobel (low threshold)", ed.getBinaryMap(125));
    // 高阈值 Sobel
    cv::namedWindow("Sobel (high threshold)");
    cv::imshow("Sobel (high threshold)", ed.getBinaryMap(350));
    // 应用 Canny 算法
    cv::Mat contours;
    cv::Canny(image,    // 灰度图像
            contours,   // 输出图像
            125,        // 低阈值
            350);       // 高阈值
    cv::namedWindow("Canny Contours");
    cv::imshow("Canny Contours", 255-contours);
    // 霍夫变换
    std::vector<cv::Vec2f> lines;
    cv::HoughLines(contours, lines, 1, PI/180, 50);
    // 绘制检测结果
    cv::Mat result(contours.rows, contours.cols, CV_8U, cv::Scalar(255));
    image.copyTo(result);
    std::cout << "Lines detected: " << lines.size() << std::endl;
    std::vector<cv::Vec2f>::const_iterator it = lines.begin();
    while (it!=lines.end()) {
    
    
        float rho = (*it)[0];
        float theta = (*it)[1];
        if (theta < PI/4. || theta > 3.*PI/4.) {
    
        // 竖线
            // 直线与图像第一行交点
            cv::Point pt1(rho/cos(theta), 0);
            // 直线与图像最后一行交点
            cv::Point pt2((rho-result.rows*sin(theta))/cos(theta), result.rows);
            cv::line(result, pt1, pt2, cv::Scalar(255), 1);
        } else {
    
            // 横线
            // 直线与图像第一列交点
            cv::Point pt1(0, rho/sin(theta));
            // 直线与图像最后一列交点
            cv::Point pt2(result.cols, (rho-result.cols*cos(theta))/sin(theta));
            cv::line(result, pt1, pt2, cv::Scalar(255), 1);
        }
        std::cout << "line: (" << rho << "," << theta << ")" << std::endl;
        ++it;
    }
    cv::namedWindow("Lines with Hough");
    cv::imshow("Lines with Hough", result);
    // 创建 LineFinder 实例
    LineFinder ld;
    // 设置概率霍夫变换
    ld.setLineLengthAndGap(100, 20);
    ld.setMinVote(60);
    // 直线检测
    std::vector<cv::Vec4i> li = ld.findLines(contours);
    ld.drawDetectedLines(image);
    cv::namedWindow("Lines with HoughP");
    cv::imshow("Lines with HoughP", image);
    std::vector<cv::Vec4i>::const_iterator it2 = li.begin();
    while (it2 != li.end()) {
    
    
        std::cout << "(" << (*it2)[0] << ", " << 
                (*it2)[1] << ") - (" << (*it2)[2] << 
                ", " << (*it2)[3] << ")" << std::endl;
        ++it2;
    }
    image = cv::imread("road.jpg", 0);
    int n = 0;
    cv::line(image, cv::Point(li[n][0], li[n][1]), cv::Point(li[n][2], li[n][3]), cv::Scalar(255), 5);
    cv::namedWindow("One line of the Image");
    cv::imshow("One line of the Image", image);
    // 提取探测到的第一条线段的轮廓像素
    cv::Mat oneline(image.size(), CV_8U, cv::Scalar(0));
    cv::line(oneline, cv::Point(li[n][0], li[n][1]), cv::Point(li[n][2], li[n][3]), cv::Scalar(255), 3);
    cv::bitwise_and(contours, oneline, oneline);
    cv::namedWindow("One line");
    cv::imshow("One line", 255-oneline);
    std::vector<cv::Point> points;
    //  迭代像素以获得所有点位置
    for (int y=0; y<oneline.rows; y++) {
    
    
        uchar* rowPtr = oneline.ptr<uchar>(y);
        for (int x=0; x<oneline.cols; x++) {
    
    
            if (rowPtr[x]) {
    
    
                points.push_back(cv::Point(x, y));
            }
        }
    }
    // 拟合直线
    cv::Vec4f line;
    cv::fitLine(points, line, cv::DIST_L2, 0, 0.01, 0.01);
    std::cout << "line: (" << line[0] << ", " << line[1] << 
            ") (" << line[2] << ", " << line[3] << std::endl;
    // 直线上的点
    int x0 = line[2];
    int y0 = line[3];
    int x1 = x0+100*line[0];
    int y1 = y0+100*line[1];
    image = cv::imread("road.jpg", 0);
    // 绘制直线
    cv::line(image, cv::Point(x0, y0), cv::Point(x1, y1), 0, 2);
    cv::namedWindow("Fitted line");
    cv::imshow("Fitted line", image);
    cv::waitKey();
    return 0;
}

転送: https://blog.csdn.net/LOVEmy134611/article/details/128808279?spm=1001.2014.3001.5501

おすすめ

転載: blog.csdn.net/m0_58523831/article/details/129660972