简体   繁体   English

从从二进制png图像检索的像素数据中检测矩形

[英]Detect rectangles from pixel data retrieved from binary png image

I have retrieved all pixels from a png image which looks like this... 我已经从看起来像这样的png图像中检索了所有像素... 在此处输入图片说明

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).... 我开始使所有填充线变为水平,以获取每条线的开始结束像素....当我水平扫描时,我也获得了水平层中的垂直线.....我想可以手动进行全部编码,但这需要时间...。我的问题是,有没有与opencv,imageJ或其他程序有关的经验,可以告诉我这些库中的任何一个是否可以解决问题...我也愿意接受任何算法建议...(使用Java)... 。

-> The biggest problem is the thickness of the lines between 1 and 4 pixels otherwise I could easily retrieve the joint points ->最大的问题是1到4像素之间的线的粗细,否则我可以很容易地找到连接点

Here is a possible solution using image morphology. 这是使用图像形态的可能解决方案。 The code below is in C++ since I only have little experience with Java. 下面的代码是C ++,因为我对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. 坏消息是,从2.4.3版开始,OpenCV尚不支持这两种操作。 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. 我将使用我的thinning()hitmiss()函数以及您的测试图像。

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. 由于线的宽度从1到4像素不等,因此请进行细化处理以获得单宽度的线。

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. 通过一些努力,您可以将其移植到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 在Matlab中,您可以玩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. 将为您提供交点的x y坐标。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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