Ask Your Question

Revision history [back]

click to hide/show revision 1
initial version

Really you could use HitAndMiss morphology to find a particular pattern like 3 point junctions. Talking about the "geometric solution", is not so easy to get from contours needed points to write the equation of the line. I'll come back with some code for this

Really To apply the "geometric solution", you need to know the points to write the equation of the line, this is not so easy.

You could use HitAndMiss the hit-and-miss morphology to find a particular pattern...in this case the 3-point junctions.

Below is my implementation to achieve the following result (Src, Junctions and Final):

Source Junctions Final

See http://homepages.inf.ed.ac.uk/rbf/HIPR2/hitmiss.htm for more details about hit-and-miss

In short, you have to define the right kernel for each pattern like 3 point junctions. Talking about the "geometric solution", you want to detect. In OpenCV hit-and-miss (is available thanks to @LorenaGdL) you have to use:

  • 1 => white is not so easy to get from contours required here
  • -1 => black is required here
  • 0 => doesn't matter

Than, catch all directions/orientation rotating the kernel

Here is the code:

    // This is your sample image (objects are black)
    cv::Mat src = (cv::Mat_<UINT8>(7, 8) <<
        0, 1, 1, 1, 1, 1, 0, 0,
        1, 0, 1, 1, 1, 0, 1, 1,
        1, 1, 0, 0, 0, 1, 1, 1,
        1, 1, 1, 0, 0, 0, 1, 1,
        1, 0, 0, 1, 1, 1, 0, 1,
        0, 1, 1, 1, 1, 1, 0, 0,
        0, 1, 1, 1, 1, 1, 1, 1);
void GetJunctions(const Mat &src, Mat &dst)
{
    // the hit-and-miss kernels to locate 3-points junctions
    // to be used in each directions
    // NOTE: float type is needed points to write the equation of the line. I'll come back due to limitation/bug in warpAffine with some code signed char
    cv::Mat k1 = (cv::Mat_<float>(3, 3) <<
        0,  1,  0,
        0,  1,  0,
        1,  0,  1);
    cv::Mat k2 = (cv::Mat_<float>(3, 3) <<
        1,  0,  0,
        0,  1,  0,
        1,  0,  1);
    cv::Mat k3 = (cv::Mat_<float>(3, 3) <<
        0, -1,  1,
        1,  1, -1,
        0,  1, 0);
    // Some useful declarations
    Mat tmp, rotMat;
    Size ksize = k1.size();
    Point center = Point(ksize.width / 2, ksize.height / 2);
    // 90 degrees rotation matrix
    rotMat = getRotationMatrix2D(center, 90, 1);
    // dst accumulates all matches
    dst = Mat::zeros(src.size(), CV_16UC1);
    // Do hit & miss for this

all possible directions (0,90,180,270) for (int i = 0; i < 3; i++) { morphologyEx(src, tmp, MORPH_HITMISS, Mat_<INT8>(k1), Point(-1, -1), 1, BORDER_CONSTANT, 0); add(dst, tmp, dst, noArray(), dst.type()); morphologyEx(src, tmp, MORPH_HITMISS, Mat_<INT8>(k2), Point(-1, -1), 1, BORDER_CONSTANT, 0); add(dst, tmp, dst, noArray(), dst.type()); morphologyEx(src, tmp, MORPH_HITMISS, Mat_<INT8>(k3), Point(-1, -1), 1, BORDER_CONSTANT, 0); add(dst, tmp, dst, noArray(), dst.type()); // Rotate the kernels (90deg) warpAffine(k1, k1, rotMat, ksize); warpAffine(k2, k2, rotMat, ksize); warpAffine(k3, k3, rotMat, ksize); } // normalize the accumulator to 0..255 cv::normalize(dst, tmp, 0, 255, cv::NORM_MINMAX); tmp.convertTo(dst, src.type()); } int main() { src *= 255; // Morphology logic is: white objects on black foreground src = ~src; // Get junctions Mat junctionsScore, dst; vector<Point> junctionsPoint;Point pt; GetJunctions(src, junctionsScore); // Draw markers where junction score is non zero cvtColor(src, dst, CV_GRAY2BGR); findNonZero(junctionsScore, junctionsPoint); for (size_t i = 0; i < junctionsPoint.size(); i++) { pt = junctionsPoint[i]; char score = junctionsScore.at<uchar>(pt); dst.at<Vec3b>(pt) = Vec3b(0, 0, score); } // show the result string winDst="Dst",winSrc = "Src", winJunc = "Junctions"; namedWindow(winSrc, WINDOW_NORMAL | WINDOW_KEEPRATIO); namedWindow(winJunc, WINDOW_NORMAL | WINDOW_KEEPRATIO); namedWindow(winDst, WINDOW_NORMAL | WINDOW_KEEPRATIO); int scale = 24; resizeWindow(winSrc, scale*src.cols, scale*src.rows); resizeWindow(winJunc, scale*src.cols, scale*src.rows); resizeWindow(winDst, scale*src.cols, scale*src.rows); imshow(winSrc, src); imshow(winJunc, junctionsScore); imshow(winDst, dst); waitKey(0); return 0; }

To apply the "geometric solution", you need to know the points to write the equation of the line, this is not so easy.

You could use the hit-and-miss morphology to find a particular pattern...in this case the 3-point junctions.

Below is my implementation to achieve the following result (Src, Junctions and Final):

Source Junctions Final

See http://homepages.inf.ed.ac.uk/rbf/HIPR2/hitmiss.htm for more details about hit-and-miss

In short, you have to define the right kernel for each pattern you want to detect. In OpenCV hit-and-miss (is available thanks to @LorenaGdL) @LorenaGdL ) you have to use:

  • 1 => white is required here
  • -1 => black is required here
  • 0 => doesn't matter

Than, catch all directions/orientation rotating the kernel

Here is the code:

    // This is your sample image (objects are black)
    cv::Mat src = (cv::Mat_<UINT8>(7, 8) <<
        0, 1, 1, 1, 1, 1, 0, 0,
        1, 0, 1, 1, 1, 0, 1, 1,
        1, 1, 0, 0, 0, 1, 1, 1,
        1, 1, 1, 0, 0, 0, 1, 1,
        1, 0, 0, 1, 1, 1, 0, 1,
        0, 1, 1, 1, 1, 1, 0, 0,
        0, 1, 1, 1, 1, 1, 1, 1);
void GetJunctions(const Mat &src, Mat &dst)
{
    // the hit-and-miss kernels to locate 3-points junctions
    // to be used in each directions
    // NOTE: float type is needed due to limitation/bug in warpAffine with signed char
    cv::Mat k1 = (cv::Mat_<float>(3, 3) <<
        0,  1,  0,
        0,  1,  0,
        1,  0,  1);
    cv::Mat k2 = (cv::Mat_<float>(3, 3) <<
        1,  0,  0,
        0,  1,  0,
        1,  0,  1);
    cv::Mat k3 = (cv::Mat_<float>(3, 3) <<
        0, -1,  1,
        1,  1, -1,
        0,  1, 0);
    // Some useful declarations
    Mat tmp, rotMat;
    Size ksize = k1.size();
    Point center = Point(ksize.width / 2, ksize.height / 2);
    // 90 degrees rotation matrix
    rotMat = getRotationMatrix2D(center, 90, 1);
    // dst accumulates all matches
    dst = Mat::zeros(src.size(), CV_16UC1);
    // Do hit & miss for all possible directions (0,90,180,270)
    int op = MORPH_HITMISS;
    for (int i = 0; i < 3; i++)
    {
        morphologyEx(src, tmp, MORPH_HITMISS, op, Mat_<INT8>(k1), Point(-1, -1), 1, BORDER_CONSTANT, 0);
        add(dst, tmp, dst, noArray(), dst.type());
        morphologyEx(src, tmp, MORPH_HITMISS, op, Mat_<INT8>(k2), Point(-1, -1), 1, BORDER_CONSTANT, 0);
        add(dst, tmp, dst, noArray(), dst.type());
        morphologyEx(src, tmp, MORPH_HITMISS, op, Mat_<INT8>(k3), Point(-1, -1), 1, BORDER_CONSTANT, 0);
        add(dst, tmp, dst, noArray(), dst.type());
        // Rotate the kernels (90deg)
        warpAffine(k1, k1, rotMat, ksize);
        warpAffine(k2, k2, rotMat, ksize);
        warpAffine(k3, k3, rotMat, ksize);
    }
    // normalize the accumulator to 0..255
    cv::normalize(dst, tmp, 0, 255, cv::NORM_MINMAX);
    tmp.convertTo(dst, src.type());
}
int main()
{
    src *= 255;
    // Morphology logic is: white objects on black foreground
    src = ~src;
    // Get junctions
    Mat junctionsScore, dst;
    vector<Point> junctionsPoint;Point pt;
    GetJunctions(src, junctionsScore);
    // Draw markers where junction score is non zero
    cvtColor(src, dst, CV_GRAY2BGR);
    findNonZero(junctionsScore, junctionsPoint);
    for (size_t i = 0; i < junctionsPoint.size(); i++)
    {
        pt = junctionsPoint[i];
        char score = junctionsScore.at<uchar>(pt);
        dst.at<Vec3b>(pt) = Vec3b(0, 0, score);
    }
    // show the result
    string winDst="Dst",winSrc = "Src", winJunc = "Junctions";
    namedWindow(winSrc, WINDOW_NORMAL | WINDOW_KEEPRATIO);
    namedWindow(winJunc, WINDOW_NORMAL | WINDOW_KEEPRATIO);
    namedWindow(winDst, WINDOW_NORMAL | WINDOW_KEEPRATIO);
    int scale = 24;
    resizeWindow(winSrc, scale*src.cols, scale*src.rows);
    resizeWindow(winJunc, scale*src.cols, scale*src.rows);
    resizeWindow(winDst, scale*src.cols, scale*src.rows);
    imshow(winSrc, src);
    imshow(winJunc, junctionsScore);
    imshow(winDst, dst);
    waitKey(0);
    return 0;
}