c++ Opencv में एक साथ क्लस्टर किए गए लाइनों के एकाधिक बंद होने के लिए एक एकल रेखा का प्रतिनिधित्व प्राप्त करें



image-processing merge (4)

सबसे पहले मैं यह ध्यान रखना चाहता हूं कि आपकी मूल छवि थोड़ी सी कोण पर है, इसलिए आपका अपेक्षित आउटपुट मुझे थोड़ा सा लगता है। मुझे लगता है कि आप उन लाइनों के साथ ठीक हैं जो आपके आउटपुट में 100% लंबवत नहीं हैं क्योंकि वे आपके इनपुट पर थोड़ा सा हैं।

Mat image;
Mat binary = image > 125;  // Convert to binary image

// Combine similar lines
int size = 3;
Mat element = getStructuringElement( MORPH_ELLIPSE, Size( 2*size + 1, 2*size+1 ), Point( size, size ) );
morphologyEx( mask, mask, MORPH_CLOSE, element );

अब तक यह इस छवि को उत्पन्न करता है:

ये रेखाएं 90 डिग्री कोण पर नहीं हैं क्योंकि मूल छवि नहीं है।

आप लाइनों के बीच के अंतर को बंद करना भी चुन सकते हैं:

Mat out = Mat::zeros(mask.size(), mask.type());

vector<Vec4i> lines;
HoughLinesP(mask, lines, 1, CV_PI/2, 50, 50, 75);
for( size_t i = 0; i < lines.size(); i++ )
{
    Vec4i l = lines[i];
    line( out, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(255), 5, CV_AA);
}

यदि ये रेखाएं बहुत मोटी हैं, तो मैंने उन्हें सफलतापूर्वक पतला कर दिया है:

size = 15;
Mat eroded;
cv::Mat erodeElement = getStructuringElement( MORPH_ELLIPSE, cv::Size( size, size ) );
erode( mask, eroded, erodeElement );

मैंने एक छवि में लाइनों का पता लगाया और उन्हें HoughLinesP विधि का उपयोग करके OpenCv C ++ में एक अलग छवि फ़ाइल में खींचा। परिणामस्वरूप छवि का एक हिस्सा निम्नलिखित है। वास्तव में सैकड़ों छोटी और पतली रेखाएं हैं जो एक बड़ी एकल रेखा बनाती हैं।

लेकिन मुझे कुछ ऐसी लाइनें चाहिए जो उन सभी लाइनों का प्रतिनिधित्व करती हैं। एक पंक्ति बनाने के लिए करीब लाइनों को एक साथ विलय किया जाना चाहिए। उदाहरण के लिए लाइनों के ऊपर सेट को नीचे की तरह 3 अलग-अलग लाइनों द्वारा दर्शाया जाना चाहिए।

अपेक्षित आउटपुट ऊपर जैसा है। इस कार्य को कैसे पूरा करें।

अकर्साकोव के जवाब से अब तक प्रगति का परिणाम।

(परिणामस्वरूप लाइनों के अलग-अलग वर्ग अलग-अलग रंगों में खींचे जाते हैं)। ध्यान दें कि यह परिणाम मूल पूर्ण छवि है जिस पर मैं काम कर रहा हूं, लेकिन नमूना अनुभाग में मैंने सवाल में उपयोग नहीं किया था


मैं अनुशंसा करता हूं कि आप OpenCV से HoughLines का उपयोग करें।

शून्य हाफलाइन (इनपुटअरे छवि, आउटपुटअरे लाइन, डबल rho, डबल थेटा, int थ्रेसहोल्ड, डबल srn = 0, डबल stn = 0)

आप उन पंक्तियों की संभावित अभिविन्यास और स्थिति के साथ rho और theta समायोजित कर सकते हैं जिन्हें आप देखना चाहते हैं। आपके मामले में, थेटा = 9 0 डिग्री ठीक होगा (केवल लंबवत और क्षैतिज रेखाएं)।

इसके बाद, आप प्लकर निर्देशांक के साथ अद्वितीय रेखा समीकरण प्राप्त कर सकते हैं। और वहां से आप 3 केंद्रों के साथ एक के-माध्य लागू कर सकते हैं जो दूसरी छवि में लगभग 3 लाइनों को फिट करना चाहिए।

पीएस: मैं देखूंगा कि क्या मैं आपकी छवि के साथ पूरी प्रक्रिया का परीक्षण कर सकता हूं


यदि आप छवि में रेखाओं की संख्या नहीं जानते हैं तो आप समकक्ष समूह पर लाइनों को विभाजित करने के लिए cv::partition फ़ंक्शन का उपयोग कर सकते हैं।

मैं आपको निम्नलिखित प्रक्रिया का सुझाव देता हूं:

  1. cv::partition का उपयोग कर अपनी लाइनों को विभाजित करें। आपको एक अच्छा भविष्यवाणी समारोह निर्दिष्ट करने की आवश्यकता है। यह वास्तव में उन छवियों पर निर्भर करता है जिन्हें आप छवि से निकालते हैं, लेकिन मुझे लगता है कि इसे निम्नलिखित स्थितियों की जांच करनी चाहिए:

    • रेखाओं के बीच कोण काफी छोटा होना चाहिए (उदाहरण के लिए कम 3 डिग्री)। कोण के कोसाइन की गणना करने के लिए डॉट उत्पाद का उपयोग करें।
    • सेगमेंट के केंद्रों के बीच की दूरी दो खंडों की अधिकतम लंबाई के आधे से कम होनी चाहिए।

उदाहरण के लिए, इसे निम्नानुसार कार्यान्वित किया जा सकता है:

bool isEqual(const Vec4i& _l1, const Vec4i& _l2)
{
    Vec4i l1(_l1), l2(_l2);

    float length1 = sqrtf((l1[2] - l1[0])*(l1[2] - l1[0]) + (l1[3] - l1[1])*(l1[3] - l1[1]));
    float length2 = sqrtf((l2[2] - l2[0])*(l2[2] - l2[0]) + (l2[3] - l2[1])*(l2[3] - l2[1]));

    float product = (l1[2] - l1[0])*(l2[2] - l2[0]) + (l1[3] - l1[1])*(l2[3] - l2[1]);

    if (fabs(product / (length1 * length2)) < cos(CV_PI / 30))
        return false;

    float mx1 = (l1[0] + l1[2]) * 0.5f;
    float mx2 = (l2[0] + l2[2]) * 0.5f;

    float my1 = (l1[1] + l1[3]) * 0.5f;
    float my2 = (l2[1] + l2[3]) * 0.5f;
    float dist = sqrtf((mx1 - mx2)*(mx1 - mx2) + (my1 - my2)*(my1 - my2));

    if (dist > std::max(length1, length2) * 0.5f)
        return false;

    return true;
}

vector<Vec4i> lines; लें कि आपके पास vector<Vec4i> lines; में आपकी रेखाएं vector<Vec4i> lines; । इसके बाद, आपको cv::partition को निम्नानुसार कॉल करना चाहिए:

vector<Vec4i> lines;
std::vector<int> labels;
int numberOfLines = cv::partition(lines, labels, isEqual);

आपको एक बार cv::partition को कॉल करने की आवश्यकता है और यह सभी लाइनों को क्लस्टरराइज़ करेगा। वेक्टर labels क्लस्टर के प्रत्येक लाइन लेबल के लिए स्टोर करेंगे, जिसमें यह संबंधित है। cv::partition लिए cv::partition देखें

  1. लाइन के सभी समूह प्राप्त करने के बाद आपको उन्हें मर्ज करना चाहिए। मैं समूह में सभी लाइनों के औसत कोण की गणना करने और "सीमा" बिंदुओं का अनुमान लगाने का सुझाव देता हूं। उदाहरण के लिए, यदि कोण शून्य है (यानी सभी रेखाएं लगभग क्षैतिज हैं) यह बाएं सबसे अधिक और दाएं-सबसे अधिक अंक होंगे। यह केवल इस बिंदु के बीच एक रेखा खींचने के लिए बनी हुई है।

मैंने देखा है कि आपके उदाहरणों में सभी पंक्तियां क्षैतिज या लंबवत हैं। ऐसे मामले में आप बिंदु की गणना कर सकते हैं जो सभी सेगमेंट के केंद्रों और "सीमा" बिंदुओं का औसत है, और उसके बाद केंद्र बिंदु के माध्यम से "सीमा" बिंदुओं द्वारा सीमित क्षैतिज या लंबवत रेखा खींचें।

कृपया ध्यान दें कि cv::partition ओ (एन ^ 2) समय लेता है, इसलिए यदि आप बड़ी संख्या में लाइनों को संसाधित करते हैं तो इसमें काफी समय लग सकता है।

मुझे उम्मीद है इससे मदद मिलेगी। मैंने इसी तरह के कार्य के लिए इस तरह के दृष्टिकोण का उपयोग किया।


@Akarsakov उत्तर पर एक परिष्करण निर्माण है। इसके साथ एक मूल मुद्दा:

सेगमेंट के केंद्रों के बीच की दूरी दो खंडों की अधिकतम लंबाई के आधे से कम होनी चाहिए।

क्या समानांतर लंबी रेखाएं जो दृष्टिहीन हैं, समान समकक्ष वर्ग में समाप्त हो सकती हैं (जैसा कि ओपी के संपादन में दिखाया गया है)।

इसलिए जिस दृष्टिकोण को मैंने पाया वह मेरे लिए उचित है:

  1. एक line1 चारों ओर एक खिड़की (बाध्य आयताकार) का निर्माण करें।
  2. line1 कोण line1 लिए पर्याप्त है और लाइन 2 के कम से कम एक बिंदु line1 के बाध्य आयताकार के अंदर है

अक्सर छवि में एक लंबी रैखिक सुविधा जो कमजोर है, उनके बीच काफी अंतर के साथ रेखा खंडों के एक सेट द्वारा मान्यता प्राप्त (हौफ, एलएसडी) समाप्त हो जाएगी। इसे कम करने के लिए, हमारे बाध्य आयताकार दोनों दिशाओं में विस्तारित लाइन के चारों ओर बनाया गया है, जहां विस्तार को मूल रेखा चौड़ाई के एक अंश द्वारा परिभाषित किया जाता है।

bool extendedBoundingRectangleLineEquivalence(const Vec4i& _l1, const Vec4i& _l2, float extensionLengthFraction, float maxAngleDiff, float boundingRectangleThickness){

    Vec4i l1(_l1), l2(_l2);
    // extend lines by percentage of line width
    float len1 = sqrtf((l1[2] - l1[0])*(l1[2] - l1[0]) + (l1[3] - l1[1])*(l1[3] - l1[1]));
    float len2 = sqrtf((l2[2] - l2[0])*(l2[2] - l2[0]) + (l2[3] - l2[1])*(l2[3] - l2[1]));
    Vec4i el1 = extendedLine(l1, len1 * extensionLengthFraction);
    Vec4i el2 = extendedLine(l2, len2 * extensionLengthFraction);

    // reject the lines that have wide difference in angles
    float a1 = atan(linearParameters(el1)[0]);
    float a2 = atan(linearParameters(el2)[0]);
    if(fabs(a1 - a2) > maxAngleDiff * M_PI / 180.0){
        return false;
    }

    // calculate window around extended line
    // at least one point needs to inside extended bounding rectangle of other line,
    std::vector<Point2i> lineBoundingContour = boundingRectangleContour(el1, boundingRectangleThickness/2);
    return
        pointPolygonTest(lineBoundingContour, cv::Point(el2[0], el2[1]), false) == 1 ||
        pointPolygonTest(lineBoundingContour, cv::Point(el2[2], el2[3]), false) == 1;
}

जहां linearParameters, extendedLine, boundingRectangleContour निम्नलिखित हैं:

Vec2d linearParameters(Vec4i line){
    Mat a = (Mat_<double>(2, 2) <<
                line[0], 1,
                line[2], 1);
    Mat y = (Mat_<double>(2, 1) <<
                line[1],
                line[3]);
    Vec2d mc; solve(a, y, mc);
    return mc;
}

Vec4i extendedLine(Vec4i line, double d){
    // oriented left-t-right
    Vec4d _line = line[2] - line[0] < 0 ? Vec4d(line[2], line[3], line[0], line[1]) : Vec4d(line[0], line[1], line[2], line[3]);
    double m = linearParameters(_line)[0];
    // solution of pythagorean theorem and m = yd/xd
    double xd = sqrt(d * d / (m * m + 1));
    double yd = xd * m;
    return Vec4d(_line[0] - xd, _line[1] - yd , _line[2] + xd, _line[3] + yd);
}

std::vector<Point2i> boundingRectangleContour(Vec4i line, float d){
    // finds coordinates of perpendicular lines with length d in both line points
    // https://math.stackexchange.com/a/2043065/183923

    Vec2f mc = linearParameters(line);
    float m = mc[0];
    float factor = sqrtf(
        (d * d) / (1 + (1 / (m * m)))
    );

    float x3, y3, x4, y4, x5, y5, x6, y6;
    // special case(vertical perpendicular line) when -1/m -> -infinity
    if(m == 0){
        x3 = line[0]; y3 = line[1] + d;
        x4 = line[0]; y4 = line[1] - d;
        x5 = line[2]; y5 = line[3] + d;
        x6 = line[2]; y6 = line[3] - d;
    } else {
        // slope of perpendicular lines
        float m_per = - 1/m;

        // y1 = m_per * x1 + c_per
        float c_per1 = line[1] - m_per * line[0];
        float c_per2 = line[3] - m_per * line[2];

        // coordinates of perpendicular lines
        x3 = line[0] + factor; y3 = m_per * x3 + c_per1;
        x4 = line[0] - factor; y4 = m_per * x4 + c_per1;
        x5 = line[2] + factor; y5 = m_per * x5 + c_per2;
        x6 = line[2] - factor; y6 = m_per * x6 + c_per2;
    }

    return std::vector<Point2i> {
        Point2i(x3, y3),
        Point2i(x4, y4),
        Point2i(x6, y6),
        Point2i(x5, y5)
    };
}

भाग लेने के लिए, कॉल करें:

std::vector<int> labels;
int equilavenceClassesCount = cv::partition(linesWithoutSmall, labels, [](const Vec4i l1, const Vec4i l2){
    return extendedBoundingRectangleLineEquivalence(
        l1, l2,
        // line extension length - as fraction of original line width
        0.2,
        // maximum allowed angle difference for lines to be considered in same equivalence class
        2.0,
        // thickness of bounding rectangle around each line
        10);
});

अब, प्रत्येक समकक्ष वर्ग को एक पंक्ति में कम करने के लिए, हम इसके बाहर एक बिंदु क्लाउड बनाते हैं और एक पंक्ति फिट पाते हैं:

// fit line to each equivalence class point cloud
std::vector<Vec4i> reducedLines = std::accumulate(pointClouds.begin(), pointClouds.end(), std::vector<Vec4i>{}, [](std::vector<Vec4i> target, const std::vector<Point2i>& _pointCloud){
    std::vector<Point2i> pointCloud = _pointCloud;

    //lineParams: [vx,vy, x0,y0]: (normalized vector, point on our contour)
    // (x,y) = (x0,y0) + t*(vx,vy), t -> (-inf; inf)
    Vec4f lineParams; fitLine(pointCloud, lineParams, CV_DIST_L2, 0, 0.01, 0.01);

    // derive the bounding xs of point cloud
    decltype(pointCloud)::iterator minXP, maxXP;
    std::tie(minXP, maxXP) = std::minmax_element(pointCloud.begin(), pointCloud.end(), [](const Point2i& p1, const Point2i& p2){ return p1.x < p2.x; });

    // derive y coords of fitted line
    float m = lineParams[1] / lineParams[0];
    int y1 = ((minXP->x - lineParams[2]) * m) + lineParams[3];
    int y2 = ((maxXP->x - lineParams[2]) * m) + lineParams[3];

    target.push_back(Vec4i(minXP->x, y1, maxXP->x, y2));
    return target;
});

प्रदर्शन:

विभाजित विभाजन रेखा (छोटी लाइनों के साथ फ़िल्टर किया गया):

कम किया हुआ:

प्रदर्शन कोड:

int main(int argc, const char* argv[]){

    if(argc < 2){
        std::cout << "img filepath should be present in args" << std::endl;
    }

    Mat image = imread(argv[1]);
    Mat smallerImage; resize(image, smallerImage, cv::Size(), 0.5, 0.5, INTER_CUBIC);
    Mat target = smallerImage.clone();

    namedWindow("Detected Lines", WINDOW_NORMAL);
    namedWindow("Reduced Lines", WINDOW_NORMAL);
    Mat detectedLinesImg = Mat::zeros(target.rows, target.cols, CV_8UC3);
    Mat reducedLinesImg = detectedLinesImg.clone();

    // delect lines in any reasonable way
    Mat grayscale; cvtColor(target, grayscale, CV_BGRA2GRAY);
    Ptr<LineSegmentDetector> detector = createLineSegmentDetector(LSD_REFINE_NONE);
    std::vector<Vec4i> lines; detector->detect(grayscale, lines);

    // remove small lines
    std::vector<Vec4i> linesWithoutSmall;
    std::copy_if (lines.begin(), lines.end(), std::back_inserter(linesWithoutSmall), [](Vec4f line){
        float length = sqrtf((line[2] - line[0]) * (line[2] - line[0])
                             + (line[3] - line[1]) * (line[3] - line[1]));
        return length > 30;
    });

    std::cout << "Detected: " << linesWithoutSmall.size() << std::endl;

    // partition via our partitioning function
    std::vector<int> labels;
    int equilavenceClassesCount = cv::partition(linesWithoutSmall, labels, [](const Vec4i l1, const Vec4i l2){
        return extendedBoundingRectangleLineEquivalence(
            l1, l2,
            // line extension length - as fraction of original line width
            0.2,
            // maximum allowed angle difference for lines to be considered in same equivalence class
            2.0,
            // thickness of bounding rectangle around each line
            10);
    });

    std::cout << "Equivalence classes: " << equilavenceClassesCount << std::endl;

    // grab a random colour for each equivalence class
    RNG rng(215526);
    std::vector<Scalar> colors(equilavenceClassesCount);
    for (int i = 0; i < equilavenceClassesCount; i++){
        colors[i] = Scalar(rng.uniform(30,255), rng.uniform(30, 255), rng.uniform(30, 255));;
    }

    // draw original detected lines
    for (int i = 0; i < linesWithoutSmall.size(); i++){
        Vec4i& detectedLine = linesWithoutSmall[i];
        line(detectedLinesImg,
             cv::Point(detectedLine[0], detectedLine[1]),
             cv::Point(detectedLine[2], detectedLine[3]), colors[labels[i]], 2);
    }

    // build point clouds out of each equivalence classes
    std::vector<std::vector<Point2i>> pointClouds(equilavenceClassesCount);
    for (int i = 0; i < linesWithoutSmall.size(); i++){
        Vec4i& detectedLine = linesWithoutSmall[i];
        pointClouds[labels[i]].push_back(Point2i(detectedLine[0], detectedLine[1]));
        pointClouds[labels[i]].push_back(Point2i(detectedLine[2], detectedLine[3]));
    }

    // fit line to each equivalence class point cloud
    std::vector<Vec4i> reducedLines = std::accumulate(pointClouds.begin(), pointClouds.end(), std::vector<Vec4i>{}, [](std::vector<Vec4i> target, const std::vector<Point2i>& _pointCloud){
        std::vector<Point2i> pointCloud = _pointCloud;

        //lineParams: [vx,vy, x0,y0]: (normalized vector, point on our contour)
        // (x,y) = (x0,y0) + t*(vx,vy), t -> (-inf; inf)
        Vec4f lineParams; fitLine(pointCloud, lineParams, CV_DIST_L2, 0, 0.01, 0.01);

        // derive the bounding xs of point cloud
        decltype(pointCloud)::iterator minXP, maxXP;
        std::tie(minXP, maxXP) = std::minmax_element(pointCloud.begin(), pointCloud.end(), [](const Point2i& p1, const Point2i& p2){ return p1.x < p2.x; });

        // derive y coords of fitted line
        float m = lineParams[1] / lineParams[0];
        int y1 = ((minXP->x - lineParams[2]) * m) + lineParams[3];
        int y2 = ((maxXP->x - lineParams[2]) * m) + lineParams[3];

        target.push_back(Vec4i(minXP->x, y1, maxXP->x, y2));
        return target;
    });

    for(Vec4i reduced: reducedLines){
        line(reducedLinesImg, Point(reduced[0], reduced[1]), Point(reduced[2], reduced[3]), Scalar(255, 255, 255), 2);
    }

    imshow("Detected Lines", detectedLinesImg);
    imshow("Reduced Lines", reducedLinesImg);
    waitKey();

    return 0;
}




line