Siarhei
Siarhei

Reputation: 2436

OpenCV lines/ruler detection

I'm trying to detect ruler on the image, and I'm going to follow the next process:

1) prepare image (blur,Canny, ect.)

2) detect lines

3) prepare set of parallel lines

so, I have an image: enter image description here

that app converts to this: enter image description here

next I've tried HoughLinesP method and looks I cannot apply it in my case, because I don't know the angle of lines, so it isn't found ruler vertical lines, but found horizontal (for example) and every ruler line consists of many thin lines, that will be a problem to process: enter image description here

the code:

std::vector<cv::Vec4i> lines_std;
cv::HoughLinesP( grayMat, lines_std, 1, CV_PI/90,  50, 10, 0 );

// drawing lines (with random color)
for( size_t i = 0; i < lines_std.size(); i++ )
{
    cv::line( originalMat, cv::Point(lines_std[i][0], lines_std[i][1]),
             cv::Point(lines_std[i][2], lines_std[i][3]), cv::Scalar(arc4random_uniform(155)+100,
                                                             arc4random_uniform(155)+100,
                                                             arc4random_uniform(155)+100), 1);
}

also I've tried LineSegmentDetector, and got more closer result I expected: enter image description here

code:

vector<Vec4f> lines_std;
Ptr<LineSegmentDetector> ls = createLineSegmentDetector(LSD_REFINE_NONE);
ls->detect(grayMat, lines_std);

but here I faced with some problems (and looks there is no way to customize createLineSegmentDetector) : not all lines were detected;lines detects not in center but on the sides and some times in left or right side only, but I need to get the center of bold line, because this will be used in calculations next.

So, what is the right way to find all lines (and every line only one time at the center of bold line)?

Update

tried HoughLines also:

vector lines;

cv::HoughLines(grayMat, lines, 1, CV_PI/90, 100 , 100, 0 );

for( size_t i = 0; i < lines.size(); i++ )
{
    float rho = lines[i][0], theta = lines[i][1];
    cv::Point pt1, pt2;
    double a = cos(theta), b = sin(theta);
    double x0 = a*rho, y0 = b*rho;

    pt1.x = cvRound(x0 + 1000*(-b));
    pt1.y = cvRound(y0 + 1000*(a));
    pt2.x = cvRound(x0 - 1000*(-b));
    pt2.y = cvRound(y0 - 1000*(a));

    cv::line( originalMat, pt1, pt2, cv::Scalar(0,255,0), 3, CV_AA);
}

but the result also looks strange (and calculations takes a lot of time): enter image description here

Upvotes: 12

Views: 3231

Answers (1)

Siarhei
Siarhei

Reputation: 2436

Guess I found the way I should follow for:

1) make lines thin as possible (after Canny transformation):

cv::Mat skel(grayMat.size(), CV_8UC1, cv::Scalar(0));
cv::Mat temp(grayMat.size(), CV_8UC1);
cv::Mat elementSkel = cv::getStructuringElement(cv::MORPH_CROSS, cv::Size(3, 3));

bool done;
do
{
    cv::morphologyEx(grayMat, temp, cv::MORPH_OPEN, elementSkel);
    cv::bitwise_not(temp, temp);
    cv::bitwise_and(grayMat, temp, temp);
    cv::bitwise_or(skel, temp, skel);
    cv::erode(grayMat, grayMat, elementSkel);

    double max;
    cv::minMaxLoc(grayMat, 0, &max);
    done = (max == 0);
} while (!done);

it looks like this:

enter image description here

2) detect lines with LineSigmentDetector:

vector<Vec4f> lines_std;
Ptr<LineSegmentDetector> ls = createLineSegmentDetector(LSD_REFINE_NONE);
ls->detect(skel, lines_std);

3)calculate line angle and group ids by angle:

NSMutableDictionary *testHashMap = [[NSMutableDictionary alloc]init];

for( size_t i = 0; i < lines_std.size(); i++ )
{
    cv::Point p1 = cv::Point(lines_std[i][0], lines_std[i][1]);
    cv::Point p2 = cv::Point(lines_std[i][2], lines_std[i][3]);
    int angle = abs(atan2(p1.y - p2.y, p1.x - p2.x)); // int for rounding (for test only)

    NSMutableArray *idArray=testHashMap[[NSString stringWithFormat:@"%i", angle]];
    if(idArray == nil) {
        idArray = [[NSMutableArray alloc] init];
    }

    [idArray addObject:[NSNumber numberWithInt:i]];
    [testHashMap setObject:idArray forKey:[NSString stringWithFormat:@"%i", angle] ];
}

4) found the ruler line set and draw it:

for( NSInteger i = 0; i < [rulerIds count]; i++ )
{
    int itemId =   [[rulerIds objectAtIndex:i] integerValue];
    cv::Point p1 = cv::Point(lines_std[itemId][0], lines_std[itemId][1]);
    cv::Point p2 = cv::Point(lines_std[itemId][2], lines_std[itemId][3]);
    cv::line( originalMat, p1 ,  p2, cv::Scalar(0,255,0), 1);
}

result I got:

enter image description here

Update

but if we zoom this image well still see duplicated lines to remove duplications I've made simple logic that merges lines by founding average value for each point, for instance in case of 3 lines(green) we have 3 dots on the end:

enter image description here

Upvotes: 5

Related Questions