13

嗨,目前我正在开发一个 OCR 阅读应用程序,我已经成功地使用 AVFoundation 框架捕获了卡片图像。

下一步,我需要找出卡片的边缘,以便我可以从主要捕获的图像中裁剪卡片图像,然后我可以将其发送到 OCR 引擎进行处理。

现在的主要问题是找到卡片的边缘,我正在使用下面的代码(取自另一个开源项目),它使用 OpenCV 来实现此目的。如果卡片是纯矩形卡片或纸,它工作正常。但是当我使用圆角卡(例如驾驶执照)时,它无法检测到。另外我在 OpenCV 方面没有太多专业知识,有人可以帮我解决这个问题吗?

- (void)detectEdges
{
    cv::Mat original = [MAOpenCV cvMatFromUIImage:_adjustedImage];
    CGSize targetSize = _sourceImageView.contentSize;
    cv::resize(original, original, cvSize(targetSize.width, targetSize.height));

    cv::vector<cv::vector<cv::Point>>squares;
    cv::vector<cv::Point> largest_square;

    find_squares(original, squares);
    find_largest_square(squares, largest_square);

    if (largest_square.size() == 4)
    {

        // Manually sorting points, needs major improvement. Sorry.

        NSMutableArray *points = [NSMutableArray array];
        NSMutableDictionary *sortedPoints = [NSMutableDictionary dictionary];

        for (int i = 0; i < 4; i++)
        {
            NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:[NSValue valueWithCGPoint:CGPointMake(largest_square[i].x, largest_square[i].y)], @"point" , [NSNumber numberWithInt:(largest_square[i].x + largest_square[i].y)], @"value", nil];
            [points addObject:dict];
        }

        int min = [[points valueForKeyPath:@"@min.value"] intValue];
        int max = [[points valueForKeyPath:@"@max.value"] intValue];

        int minIndex;
        int maxIndex;

        int missingIndexOne;
        int missingIndexTwo;

        for (int i = 0; i < 4; i++)
        {
            NSDictionary *dict = [points objectAtIndex:i];

            if ([[dict objectForKey:@"value"] intValue] == min)
            {
                [sortedPoints setObject:[dict objectForKey:@"point"] forKey:@"0"];
                minIndex = i;
                continue;
            }

            if ([[dict objectForKey:@"value"] intValue] == max)
            {
                [sortedPoints setObject:[dict objectForKey:@"point"] forKey:@"2"];
                maxIndex = i;
                continue;
            }

            NSLog(@"MSSSING %i", i);

            missingIndexOne = i;
        }

        for (int i = 0; i < 4; i++)
        {
            if (missingIndexOne != i && minIndex != i && maxIndex != i)
            {
                missingIndexTwo = i;
            }
        }


        if (largest_square[missingIndexOne].x < largest_square[missingIndexTwo].x)
        {
            //2nd Point Found
            [sortedPoints setObject:[[points objectAtIndex:missingIndexOne] objectForKey:@"point"] forKey:@"3"];
            [sortedPoints setObject:[[points objectAtIndex:missingIndexTwo] objectForKey:@"point"] forKey:@"1"];
        }
        else
        {
            //4rd Point Found
            [sortedPoints setObject:[[points objectAtIndex:missingIndexOne] objectForKey:@"point"] forKey:@"1"];
            [sortedPoints setObject:[[points objectAtIndex:missingIndexTwo] objectForKey:@"point"] forKey:@"3"];
        }


        [_adjustRect topLeftCornerToCGPoint:[(NSValue *)[sortedPoints objectForKey:@"0"] CGPointValue]];
        [_adjustRect topRightCornerToCGPoint:[(NSValue *)[sortedPoints objectForKey:@"1"] CGPointValue]];
        [_adjustRect bottomRightCornerToCGPoint:[(NSValue *)[sortedPoints objectForKey:@"2"] CGPointValue]];
        [_adjustRect bottomLeftCornerToCGPoint:[(NSValue *)[sortedPoints objectForKey:@"3"] CGPointValue]];
    }

    original.release();


}
4

4 回答 4

15

这个简单的实现基于squares.cpp中演示的一些技术,可在 OpenCV 示例目录中找到。以下帖子也讨论了类似的应用程序:

@John,下面的代码已经用您提供的示例图像和我创建的另一个图像进行了测试:

处理管道以 开头,是 OpenCV 的squares.cpp演示findSquares()实现的相同功能的简化。此函数将输入图像转换为灰度并应用模糊以改善边缘检测(Canny):

边缘检测很好,但需要进行形态学操作(膨胀)来连接附近的线:

之后,我们尝试找到轮廓(边缘)并从中组装出正方形。如果我们尝试在输入图像上绘制所有检测到的正方形,这将是结果:

它看起来不错,但它并不是我们正在寻找的,因为检测到的方块太多。然而,最大的方格实际上是卡片,所以从这里开始就很简单了,我们只需找出哪个方格最大。这正是这样findLargestSquare()做的。

一旦我们知道了最大的正方形,我们只需在正方形的角上绘制红点以进行调试:

如您所见,检测并不完美,但对于大多数用途来说似乎已经足够了。这不是一个强大的解决方案,我只想分享一种解决问题的方法。我敢肯定,还有其他方法可以解决您可能更感兴趣的问题。祝你好运!

#include <iostream>
#include <cmath>
#include <vector>

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

/* angle: finds a cosine of angle between vectors, from pt0->pt1 and from pt0->pt2
 */
double angle(cv::Point pt1, cv::Point pt2, cv::Point pt0)
{
    double dx1 = pt1.x - pt0.x;
    double dy1 = pt1.y - pt0.y;
    double dx2 = pt2.x - pt0.x;
    double dy2 = pt2.y - pt0.y;
    return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10);
}

/* findSquares: returns sequence of squares detected on the image
 */
void findSquares(const cv::Mat& src, std::vector<std::vector<cv::Point> >& squares)
{
    cv::Mat src_gray;
    cv::cvtColor(src, src_gray, cv::COLOR_BGR2GRAY);

    // Blur helps to decrease the amount of detected edges
    cv::Mat filtered;
    cv::blur(src_gray, filtered, cv::Size(3, 3));
    cv::imwrite("out_blur.jpg", filtered);

    // Detect edges
    cv::Mat edges;
    int thresh = 128;
    cv::Canny(filtered, edges, thresh, thresh*2, 3);
    cv::imwrite("out_edges.jpg", edges);

    // Dilate helps to connect nearby line segments
    cv::Mat dilated_edges;
    cv::dilate(edges, dilated_edges, cv::Mat(), cv::Point(-1, -1), 2, 1, 1); // default 3x3 kernel
    cv::imwrite("out_dilated.jpg", dilated_edges);

    // Find contours and store them in a list
    std::vector<std::vector<cv::Point> > contours;
    cv::findContours(dilated_edges, contours, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);

    // Test contours and assemble squares out of them
    std::vector<cv::Point> approx;
    for (size_t i = 0; i < contours.size(); i++)
    {
        // approximate contour with accuracy proportional to the contour perimeter
        cv::approxPolyDP(cv::Mat(contours[i]), approx, cv::arcLength(cv::Mat(contours[i]), true)*0.02, true);

        // Note: absolute value of an area is used because
        // area may be positive or negative - in accordance with the
        // contour orientation
        if (approx.size() == 4 && std::fabs(contourArea(cv::Mat(approx))) > 1000 &&
            cv::isContourConvex(cv::Mat(approx)))
        {
            double maxCosine = 0;
            for (int j = 2; j < 5; j++)
            {
                double cosine = std::fabs(angle(approx[j%4], approx[j-2], approx[j-1]));
                maxCosine = MAX(maxCosine, cosine);
            }

            if (maxCosine < 0.3)
                squares.push_back(approx);
        }
    }
}

/* findLargestSquare: find the largest square within a set of squares
 */
void findLargestSquare(const std::vector<std::vector<cv::Point> >& squares,
                       std::vector<cv::Point>& biggest_square)
{
    if (!squares.size())
    {
        std::cout << "findLargestSquare !!! No squares detect, nothing to do." << std::endl;
        return;
    }

    int max_width = 0;
    int max_height = 0;
    int max_square_idx = 0;
    for (size_t i = 0; i < squares.size(); i++)
    {
        // Convert a set of 4 unordered Points into a meaningful cv::Rect structure.
        cv::Rect rectangle = cv::boundingRect(cv::Mat(squares[i]));

        //std::cout << "find_largest_square: #" << i << " rectangle x:" << rectangle.x << " y:" << rectangle.y << " " << rectangle.width << "x" << rectangle.height << endl;

        // Store the index position of the biggest square found
        if ((rectangle.width >= max_width) && (rectangle.height >= max_height))
        {
            max_width = rectangle.width;
            max_height = rectangle.height;
            max_square_idx = i;
        }
    }

    biggest_square = squares[max_square_idx];
}

int main()
{
    cv::Mat src = cv::imread("cc.png");
    if (src.empty())
    {
        std::cout << "!!! Failed to open image" << std::endl;
        return -1;
    }

    std::vector<std::vector<cv::Point> > squares;
    findSquares(src, squares);

    // Draw all detected squares
    cv::Mat src_squares = src.clone();
    for (size_t i = 0; i < squares.size(); i++)
    {
        const cv::Point* p = &squares[i][0];
        int n = (int)squares[i].size();
        cv::polylines(src_squares, &p, &n, 1, true, cv::Scalar(0, 255, 0), 2, CV_AA);
    }
    cv::imwrite("out_squares.jpg", src_squares);
    cv::imshow("Squares", src_squares);

    std::vector<cv::Point> largest_square;
    findLargestSquare(squares, largest_square);

    // Draw circles at the corners
    for (size_t i = 0; i < largest_square.size(); i++ )
        cv::circle(src, largest_square[i], 4, cv::Scalar(0, 0, 255), cv::FILLED);
    cv::imwrite("out_corners.jpg", src);

    cv::imshow("Corners", src);
    cv::waitKey(0);

    return 0;
}
于 2014-10-07T18:35:18.873 回答
2

而不是“纯”矩形斑点,尝试使用接近矩形的斑点。

1-高斯模糊

2-灰度和canny边缘检测

3-提取图像中的所有斑点(轮廓)并过滤掉小的斑点。为此,您将使用 findcontours 和 contourarea 函数。

4-使用,过滤掉非矩形的矩。首先,您需要检查矩形对象的时刻。你可以自己做,也可以google。然后列出这些时刻并找到对象之间的相似性,这样创建您的过滤器。

例如:经过测试,假设您发现矩形对象的中心矩 m30 相似 -> 过滤掉 m30 不准确的对象。

于 2013-06-27T18:30:06.383 回答
2

我知道这篇文章可能为时已晚,但我发布它以防它可能对其他人有所帮助。

iOS Core Image 框架已经有一个很好的工具来检测矩形(从 iOS 5 开始)、人脸、二维码甚至是静止图像中包含文本的区域等特征。如果你查看CIDetector 类,你会找到你需要的。我也将它用于 OCR 应用程序,与您可以使用 OpenCV 执行的操作相比,它超级简单且非常可靠(我不擅长使用 OpenCV,但 CIDetector 只需 3-5 行代码即可提供更好的结果)。

于 2016-05-12T10:42:37.623 回答
0

我不知道这是否是一个选项,但您可以让用户定义它的边缘,而不是尝试以编程方式进行。

于 2013-06-27T15:55:23.963 回答