信中的字母,模式识别

时间:2013-12-18 23:36:48

标签: opencv image-processing computer-vision

我想检测这种模式

https://www.dropbox.com/s/ghuaywtfdsb249j/test.jpg

正如你所看到的,它基本上是字母C,在另一个内部,具有不同的方向。我的模式可以在彼此内部有多个C,我用2 C发布的只是一个样本。我想知道有多少C,以及每个C的方向。现在我已经设法检测到这种模式的中心,基本上我已经设法检测到最里面的C的中心。你能不能向我提供关于我可以使用的不同算法的任何想法?

3 个答案:

答案 0 :(得分:14)

我们走了!此方法的高级概述可以描述为顺序执行以下步骤:

由于我分享了源代码,因此我不想详细介绍,所以请随意以您喜欢的方式进行测试和更改。 让我们开始,冬天来了

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

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

cv::RNG rng(12345);
float PI = std::atan(1) * 4;

void isolate_object(const cv::Mat& input, cv::Mat& output)
{    
    if (input.channels() != 1)
    {
        std::cout << "isolate_object: !!! input must be grayscale" << std::endl;
        return;
    }

    // Store the set of points in the image before assembling the bounding box
    std::vector<cv::Point> points;
    cv::Mat_<uchar>::const_iterator it = input.begin<uchar>();
    cv::Mat_<uchar>::const_iterator end = input.end<uchar>();
    for (; it != end; ++it)
    {
        if (*it) points.push_back(it.pos());
    }

    // Compute minimal bounding box
    cv::RotatedRect box = cv::minAreaRect(cv::Mat(points));

    // Set Region of Interest to the area defined by the box
    cv::Rect roi;
    roi.x = box.center.x - (box.size.width / 2);
    roi.y = box.center.y - (box.size.height / 2);
    roi.width = box.size.width;
    roi.height = box.size.height;

    // Crop the original image to the defined ROI
    output = input(roi);
}

有关isolate_object()的实施的详细信息,请check this threadcv::RNG稍后将用于fill each contour with a different colorPI,嗯......您知道 PI

int main(int argc, char* argv[])
{   
    // Load input (colored, 3-channel, BGR)
    cv::Mat input = cv::imread("test.jpg");
    if (input.empty())
    {
        std::cout << "!!! Failed imread() #1" << std::endl;
        return -1;
    }

    // Convert colored image to grayscale
    cv::Mat gray;
    cv::cvtColor(input, gray, CV_BGR2GRAY);

    // Execute a threshold operation to get a binary image from the grayscale
    cv::Mat binary;
    cv::threshold(gray, binary, 128, 255, cv::THRESH_BINARY); 

二进制图像看起来与输入完全相同,因为它只有2种颜色(B&amp; W):

    // Find the contours of the C's in the thresholded image
    std::vector<std::vector<cv::Point> > contours;
    cv::findContours(binary, contours, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);

    // Fill the contours found with unique colors to isolate them later
    cv::Mat colored_contours = input.clone();  
    std::vector<cv::Scalar> fill_colors;   
    for (size_t i = 0; i < contours.size(); i++)
    {
        std::vector<cv::Point> cnt = contours[i];
        double area = cv::contourArea(cv::Mat(cnt));        
        //std::cout << "* Area: " << area << std::endl;

        // Fill each C found with a different color. 
        // If the area is larger than 100k it's probably the white background, so we ignore it.
        if (area > 10000 && area < 100000)
        {
            cv::Scalar color = cv::Scalar(rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255));            
            cv::drawContours(colored_contours, contours, i, color, 
                             CV_FILLED, 8, std::vector<cv::Vec4i>(), 0, cv::Point());
            fill_colors.push_back(color);
            //cv::imwrite("test_contours.jpg", colored_contours);
        }           
    }

colored_contours 的内容如下:

    // Create a mask for each C found to isolate them from each other
    for (int i = 0; i < fill_colors.size(); i++)
    {
        // After inRange() single_color_mask stores a single C letter
        cv::Mat single_color_mask = cv::Mat::zeros(input.size(), CV_8UC1);
        cv::inRange(colored_contours, fill_colors[i], fill_colors[i], single_color_mask);
        //cv::imwrite("test_mask.jpg", single_color_mask);

由于此for循环执行两次,每个颜色用于填充轮廓,我希望您查看此阶段生成的所有图像。所以下面的图像是由 single_color_mask 存储的图像(每次迭代循环一次):

        // Crop image to the area of the object
        cv::Mat cropped;
        isolate_object(single_color_mask, cropped);        
        //cv::imwrite("test_cropped.jpg", cropped);
        cv::Mat orig_cropped = cropped.clone();

这些是由裁剪存储的(顺便说一句,较小的C看起来很胖,因为此页面重新调整图像以使其具有与较大C相同的尺寸,不要&#39 ;担心):

        // Figure out the center of the image
        cv::Point obj_center(cropped.cols/2, cropped.rows/2);
        //cv::circle(cropped, obj_center, 3, cv::Scalar(128, 128, 128));
        //cv::imwrite("test_cropped_center.jpg", cropped);

为了更清楚地了解 obj_center 的用途,我在该位置绘制了一个用于教育目的的小灰圈:

        // Figure out the exact center location of the border
        std::vector<cv::Point> border_points;
        for (int y = 0; y < cropped.cols; y++) 
        {
            if (cropped.at<uchar>(obj_center.x, y) != 0)
                border_points.push_back(cv::Point(obj_center.x, y));

            if (border_points.size() > 0 && cropped.at<uchar>(obj_center.x, y) == 0)
                break;
        }

        if (border_points.size() == 0)
        {
            std::cout << "!!! Oops! No border detected." << std::endl;
            return 0;
        }

        // Figure out the exact center location of the border
        cv::Point border_center = border_points[border_points.size() / 2];
        //cv::circle(cropped, border_center, 3, cv::Scalar(128, 128, 128));
        //cv::imwrite("test_border_center.jpg", cropped);

上面的过程从图像的顶部/中间扫描单个垂直线,以找到圆的边界,以便能够计算它的宽度。同样,出于教育目的,我在边界中间画了一个小灰圈。这是裁剪的样子:

        // Scan the border of the circle for discontinuities 
        int radius = obj_center.y - border_center.y;
        if (radius < 0) 
            radius *= -1;  
        std::vector<cv::Point> discontinuity_points;   
        std::vector<int> discontinuity_angles;
        for (int angle = 0; angle <= 360; angle++)
        {
            int x = obj_center.x + (radius * cos((angle+90) * (PI / 180.f))); 
            int y = obj_center.y + (radius * sin((angle+90) * (PI / 180.f)));                

            if (cropped.at<uchar>(x, y) < 128)
            {
                discontinuity_points.push_back(cv::Point(y, x));
                discontinuity_angles.push_back(angle); 
                //cv::circle(cropped, cv::Point(y, x), 1, cv::Scalar(128, 128, 128));                           
            }
        }

        //std::cout << "Discontinuity size: " << discontinuity_points.size() << std::endl;
        if (discontinuity_points.size() == 0 && discontinuity_angles.size() == 0)
        {
            std::cout << "!!! Oops! No discontinuity detected. It's a perfect circle, dang!" << std::endl;
            return 0;
        }

很好,所以上面的代码片段沿着圆圈的边缘扫描,寻找不连续性。我分享了一张示例图片来说明我的意思。图像上的每个灰点代表一个经过测试的像素。当像素是黑色时,意味着我们发现了不连续性:

enter image description here

        // Figure out the approximate angle of the discontinuity: 
        // the first angle found will suffice for this demo.
        int approx_angle = discontinuity_angles[0];        
        std::cout << "#" << i << " letter C is rotated approximately at: " << approx_angle << " degrees" << std::endl;    

        // Figure out the central point of the discontinuity
        cv::Point discontinuity_center;
        for (int a = 0; a < discontinuity_points.size(); a++)
            discontinuity_center += discontinuity_points[a];
        discontinuity_center.x /= discontinuity_points.size(); 
        discontinuity_center.y /= discontinuity_points.size(); 
        cv::circle(orig_cropped, discontinuity_center, 2, cv::Scalar(128, 128, 128));

        cv::imshow("Original crop", orig_cropped);
        cv::waitKey(0);
    }

    return 0;
}

很好......最后一段代码负责确定不连续的近似角度以及指示不连续的中心点。以下图像由 orig_cropped 存储。我再次添加了一个灰点,以显示检测到的确切位置作为间隙的中心:

执行时,此应用程序将以下信息显示在屏幕上:

#0 letter C is rotated approximately at: 49 degrees
#1 letter C is rotated approximately at: 0 degrees 

我希望它有所帮助。

答案 1 :(得分:0)

首先,您可以使用Hough转换。这个算法不是很快,但它非常强大。特别是如果你有这样清晰的图像。

一般方法是:

1) preprocessing - suppress noise, convert to grayscale / binary
2) run edge detector
3) run Hough transform - IIRC it's `cv::HoughCircles` in OpenCV
4) do some postprocessing - remove surplus circles, decide which ones correspond to shape of letter C, and so on

我的方法将为每个字母C提供2个hough圆圈。一个在内部边界上,一个在外部字母C上。如果每个字母只需要一个圆圈,则可以使用骨架化算法。更多信息http://homepages.inf.ed.ac.uk/rbf/HIPR2/skeleton.htm

答案 2 :(得分:0)

鉴于我们已经嵌套了C结构并且您知道Cs的中心并且想要评估方向 - 只需要观察沿着同心Cs的 radius 的像素分布。所有方向。

这可以通过从中心执行简单的morphological dilation操作来完成。当我们到达最里面的C的正确半径时,我们将达到最内层C所覆盖的最大像素数。盘和C之间的差异将给出我们整体中间隙的位置,并且可以执行{ {3}}获得C中间隙的质心。中心与此点之间的角度是C的方向。迭代此步骤直到覆盖所有Cs。

这也可以使用Cs中心点的ultimate erosion快速完成。