2

I have retrieved all pixels from a png image which looks like this...enter image description here

I started to get all filled lines horizontal to get the start end pixel of each line .... as I scan horizontal I get also the vertical lines in horizontal layer.... I guess it is possible to code all manually but it takes time.... my question is has anyone experience with opencv,imageJ or other to tell if any of this libs could solve the problem... I am also open for any algorithm suggestion.... (using Java)....

-> The biggest problem is the thickness of the lines between 1 and 4 pixels otherwise I could easily retrieve the joint points

Karussell
  • 16,303
  • 14
  • 88
  • 188
Fendrix
  • 546
  • 2
  • 9
  • 31

3 Answers3

3

Here is a possible solution using image morphology. The code below is in C++ since I only have little experience with Java.

To solve your problem, you need:

  • Thinning - to reduce thick lines to one-pixel width lines.
  • Hit-or-miss transform - for finding patterns in binary image i.e the corner and joint points.

The bad news is both operations is not yet supported in OpenCV as of version 2.4.3. The good news is I have implemented both operations, the code is available on my blog:

I will be using my thinning() and hitmiss() functions and your test image.

After loading the image, convert it to single-channel binary image.

cv::Mat im = cv::imread("D1Xnm.png");
cv::Mat bw;
cv::cvtColor(im, bw, CV_BGR2GRAY);
cv::threshold(~bw, bw, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);

enter image description here

Since the width of the lines vary from 1 to 4 pixels perform thinning to get one-width lines.

thinning(bw);

enter image description here

From the thinned image, notice that there are perfect and not perfect joint points as shown in the figure below.

enter image description here enter image description here

To cover both the perfect and imperfect joint points, we need the following kernels for the hit-or-miss transform.

std::vector<cv::Mat> k;
k.push_back((cv::Mat_<char>(5,5) << -1,-1, 0,-1,-1,
                                    -1,-1, 0,-1,-1,
                                     0, 0, 0, 0, 1,
                                    -1,-1, 0, 0,-1,
                                    -1,-1, 1,-1,-1 ));

k.push_back((cv::Mat_<char>(5,5) << -1,-1, 0,-1,-1,
                                    -1,-1, 0,-1,-1,
                                     1, 0, 0, 0, 0,
                                    -1, 0, 0,-1,-1,
                                    -1,-1, 1,-1,-1 ));

k.push_back((cv::Mat_<char>(5,5) << -1,-1, 1,-1,-1,
                                    -1,-1, 0,-1,-1,
                                     1, 0, 0, 0, 0,
                                    -1, 0, 0,-1,-1,
                                    -1,-1, 0,-1,-1 ));

k.push_back((cv::Mat_<char>(5,5) << -1,-1, 1,-1,-1,
                                    -1,-1, 0,-1,-1,
                                     0, 0, 0, 0, 1,
                                    -1,-1, 0, 0,-1,
                                    -1,-1, 0,-1,-1 ));

cv::Mat dst = cv::Mat::zeros(bw.size(), CV_8U);

for (int i = 0; i < k.size(); i++)
{
    cv::Mat tmp;
    hitmiss(bw, tmp, k[i]);
    dst |= tmp;
}

enter image description here

Open the original image to make the result clearer.

The joint points successfully located, draw it on the original image.

std::vector<std::vector<cv::Point> > cnt;
cv::findContours(dst.clone(), cnt, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
cv::drawContours(im, cnt, -1, CV_RGB(255,0,0), 10);

enter image description here


For the sake of completeness, here is the full code. With some efforts you can port it to Java.

#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

void thinningIteration(cv::Mat& im, int iter)
{
    cv::Mat marker = cv::Mat::zeros(im.size(), CV_8UC1);

    for (int i = 1; i < im.rows; i++)
    {
        for (int j = 1; j < im.cols; j++)
        {
            uchar p2 = im.at<uchar>(i-1, j);
            uchar p3 = im.at<uchar>(i-1, j+1);
            uchar p4 = im.at<uchar>(i, j+1);
            uchar p5 = im.at<uchar>(i+1, j+1);
            uchar p6 = im.at<uchar>(i+1, j);
            uchar p7 = im.at<uchar>(i+1, j-1);
            uchar p8 = im.at<uchar>(i, j-1);
            uchar p9 = im.at<uchar>(i-1, j-1);

            int A  = (p2 == 0 && p3 == 1) + (p3 == 0 && p4 == 1) + 
                     (p4 == 0 && p5 == 1) + (p5 == 0 && p6 == 1) + 
                     (p6 == 0 && p7 == 1) + (p7 == 0 && p8 == 1) +
                     (p8 == 0 && p9 == 1) + (p9 == 0 && p2 == 1);
            int B  = p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9;
            int m1 = iter == 0 ? (p2 * p4 * p6) : (p2 * p4 * p8);
            int m2 = iter == 0 ? (p4 * p6 * p8) : (p2 * p6 * p8);

            if (A == 1 && (B >= 2 && B <= 6) && m1 == 0 && m2 == 0)
                marker.at<uchar>(i,j) = 1;
        }
    }
    im &= ~marker;
}

void thinning(cv::Mat& im)
{
    im /= 255;
    cv::Mat prev = cv::Mat::zeros(im.size(), CV_8UC1);
    cv::Mat diff;
    do {
        thinningIteration(im, 0);
        thinningIteration(im, 1);
        cv::absdiff(im, prev, diff);
        im.copyTo(prev);
    } 
    while (cv::countNonZero(diff) > 0);
    im *= 255;
}

void hitmiss(cv::Mat& src, cv::Mat& dst, cv::Mat& kernel) 
{
    CV_Assert(src.type() == CV_8U && src.channels() == 1);

    cv::Mat k1 = (kernel == 1) / 255;
    cv::Mat k2 = (kernel == -1) / 255;

    cv::normalize(src, src, 0, 1, cv::NORM_MINMAX);

    cv::Mat e1, e2;
    cv::erode(src, e1, k1, cv::Point(-1,-1), 1, cv::BORDER_CONSTANT, cv::Scalar(0));
    cv::erode(1 - src, e2, k2, cv::Point(-1,-1), 1, cv::BORDER_CONSTANT, cv::Scalar(0));
    dst = e1 & e2;
}

int main()
{
    cv::Mat im = cv::imread("D1Xnm.png");
    if (im.empty())
        return -1;
    cv::Mat bw;
    cv::cvtColor(im, bw, CV_BGR2GRAY);
    cv::threshold(~bw, bw, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
    thinning(bw);

    std::vector<cv::Mat> k;
    k.push_back((cv::Mat_<char>(5,5) << -1,-1, 0,-1,-1,
                                        -1,-1, 0,-1,-1,
                                         0, 0, 0, 0, 1,
                                        -1,-1, 0, 0,-1,
                                        -1,-1, 1,-1,-1 ));

    k.push_back((cv::Mat_<char>(5,5) << -1,-1, 0,-1,-1,
                                        -1,-1, 0,-1,-1,
                                         1, 0, 0, 0, 0,
                                        -1, 0, 0,-1,-1,
                                        -1,-1, 1,-1,-1 ));

    k.push_back((cv::Mat_<char>(5,5) << -1,-1, 1,-1,-1,
                                        -1,-1, 0,-1,-1,
                                         1, 0, 0, 0, 0,
                                        -1, 0, 0,-1,-1,
                                        -1,-1, 0,-1,-1 ));

    k.push_back((cv::Mat_<char>(5,5) << -1,-1, 1,-1,-1,
                                        -1,-1, 0,-1,-1,
                                         0, 0, 0, 0, 1,
                                        -1,-1, 0, 0,-1,
                                        -1,-1, 0,-1,-1 ));

    cv::Mat dst = cv::Mat::zeros(bw.size(), CV_8U);

    for (int i = 0; i < k.size(); i++)
    {
        cv::Mat tmp;
        hitmiss(bw, tmp, k[i]);
        dst |= tmp;
    }

    std::vector<std::vector<cv::Point> > cnt;
    cv::findContours(dst.clone(), cnt, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
    cv::drawContours(im, cnt, -1, CV_RGB(255,0,0), 10);

    cv::imshow("src", im);
    cv::imshow("bw", bw*255);
    cv::imshow("dst", dst*255);
    cv::waitKey();
    return 0;
}
flowfree
  • 15,468
  • 12
  • 46
  • 71
2

Take a look at OpenCV squares.c demo, or one of the threads below:

Community
  • 1
  • 1
karlphillip
  • 87,606
  • 33
  • 227
  • 395
1

You can use morphological operations on the binary image you got. In matlab you may play with bwmorph

bw = I == 0; % look at dark lines in image I
[y x] = find( bwmorph( bw, 'branchpoints' ) );

will give you x y coordinates of junction points.

Shai
  • 93,148
  • 34
  • 197
  • 325