简体   繁体   中英

Detect rectangles from pixel data retrieved from binary png image

I have retrieved all pixels from a png image which looks like this... 在此处输入图片说明

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

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 ie 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);

在此处输入图片说明

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

thinning(bw);

在此处输入图片说明

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

在此处输入图片说明在此处输入图片说明

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;
}

在此处输入图片说明

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);

在此处输入图片说明


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;
}

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM