字母里面的字母,模式识别 [英] Letter inside letter, pattern recognition

查看:374
本文介绍了字母里面的字母,模式识别的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想检测此模式



;

  • 将其转换为灰度;

  • 阈值可生成二进制图像;

  • 使用二进制图片查找轮廓;

  • 用不同的颜色填充轮廓的每个区域(因此我们可以提取每个字母分开);

  • 为找到的每个字母创建一个遮罩, / a>;

  • 将图片缩小到尽可能小的尺寸;

  • 找出图片的中心;

  • 找出字母边框的宽度,以确定边框的确切中心;

  • 沿着边界扫描(以圆形方式)以获得不连续性;

  • 找出不连续的大致角度,从而识别字母的旋转量。



  • 我不想进入太多的细节,因为我共享源代码,所以随时随地测试和改变它在任何你喜欢的方式。
    让我们开始,冬天来了

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

    //在组合边界框之前存储图像中的点集合
    std :: vector< cv :: Point>点数
    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());
    }

    //计算最小边界框
    cv :: RotatedRect box = cv :: minAreaRect(cv :: Mat(points));

    //将感兴趣区域设置为框定义的区域
    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;

    //将原始图像裁剪到定义的ROI
    output = input(roi);
    }

    有关 isolate_object 查看此主题 cv :: RNG 后来用于用不同的颜色填充每个轮廓 PI ,你...知道 PI

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

    //将彩色图像转换为灰度
    cv :: Mat gray;
    cv :: cvtColor(input,gray,CV_BGR2GRAY);

    //执行阈值操作以从灰度获取二进制图像
    cv :: Mat binary;
    cv :: threshold(gray,binary,128,255,cv :: THRESH_BINARY);

    二进制图像看起来像输入,因为它只有2种颜色(B& W):



      //在阈值化图像中查找C的轮廓
    std :: vector< std: :vector< cv :: Point> >轮廓;
    cv :: findContours(binary,contours,cv :: RETR_LIST,cv :: CHAIN_APPROX_SIMPLE);

    //填充用唯一颜色找到的轮廓,以便以后分离它们
    cv :: Mat colored_contours = input.clone();
    std :: vector< cv :: Scalar> fill_colors;
    for(size_t i = 0; i {
    std :: vector< cv :: Point& cnt = contoururs [i];
    double area = cv :: contourArea(cv :: Mat(cnt));
    // std :: cout<< * Area:<<面积<< std :: endl;

    //用不同的颜色填充每个C。
    //如果区域大于100k,它可能是白色背景,所以我们忽略它。
    if(area> 10000&& area< 100000)
    {
    cv :: Scalar color = cv :: Scalar(rng.uniform(0,255),rng。均匀(0,255),均匀(0,255));
    cv :: drawContours(colored_contours,contour,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





      //为每个C创建一个掩码,使它们彼此隔离
    for(int i = 0; i< ; fill_colors.size(); i ++)
    {
    // inRange()single_color_mask存储单个C字母
    cv :: Mat single_color_mask = cv :: Mat :: zeros 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 (循环的每次迭代都有一个)存储的图片:



      //将图像裁剪到对象区域
    cv :: Mat裁剪
    isolate_object(single_color_mask,cropped);
    //cv::imwrite(\"test_cropped.jpg,cropped);
    cv :: Mat orig_cropped = cropped.clone();

    这些是通过裁剪较小的C看起来很胖,因为图像被该页面重新缩放为具有与较大的C相同的大小,不必担心):



      //找出图像的中心
    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 一个用于教育目的的灰色圆圈:



      //找出边框的确切中心位置
    std :: vector< cv :: Point> border_points;
    for(int y = 0; y< cropped.cols; y ++)
    {
    if(cropped.at 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< !!!糟糕!没有检测到边框。 << std :: endl;
    return 0;
    }

    //确定边界的确切中心位置
    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);

    上述过程从顶部/中间扫描单个垂直线图像来找到圆的边界,以便能够计算它的宽度。再次,为了教育目的,我在边境的中间画了一个小的灰色圆圈。这是裁剪的外观:



      //扫描圆的不连续边界
    int radius = obj_center.y - border_center.y;
    if(radius< 0)
    radius * = -1;
    std :: vector< cv :: Point>不连续点
    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< !!!糟糕!没有检测到不连续,这是一个完美的圆圈,dang! << std :: endl;
    return 0;
    }

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



      //找出不连续的大致角度:
    //第一个角度发现将足以满足这个演示。
    int approx_angle = discontinuity_angles [0];
    std :: cout<< #<< i<< 字母C大约旋转:< approx_angle<< degrees<< std :: endl;

    //找出不连续的中心点
    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字母C大约旋转:49度
    #1字母C大约旋转0度

    我希望它有帮助。


    I would like to detect this pattern

    As you can see it's basically the letter C, inside another, with different orientations. My pattern can have multiple C's inside one another, the one I'm posting with 2 C's is just a sample. I would like to detect how many C's there are, and the orientation of each one. For now I've managed to detect the center of such pattern, basically I've managed to detect the center of the innermost C. Could you please provide me with any ideas about different algorithms I could use?

    解决方案

    And here we go! A high level overview of this approach can be described as the sequential execution of the following steps:

    I don't want to get into too much detail since I'm sharing the source code, so feel free to test and change it in any way you like. Let's start, Winter Is Coming:

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

    For more details on the implementation of isolate_object() please check this thread. cv::RNG is used later on to fill each contour with a different color, and PI, well... you know 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); 
    

    The binary image looks exactly like the input because it only had 2 colors (B&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);
            }           
        }
    

    What colored_contours looks like:

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

    Since this for loop is executed twice, one for each color that was used to fill the contours, I want you to see all images that were generated by this stage. So the following images are the ones that were stored by single_color_mask (one for each iteration of the loop):

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

    These are the ones that were stored by cropped (by the way, the smaller C looks fat because the image is rescaled by this page to have the same size of the larger C, don't worry):

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

    To make it clearer to understand for what obj_center is for, I painted a little gray circle for educational purposes on that location:

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

    The procedure above scans a single vertical line from top/middle of the image to find the borders of the circle to be able to calculate it's width. Again, for education purposes I painted a small gray circle in the middle of the border. This is what cropped looks like:

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

    Great, so the piece of code above scans along the middle of the circle's border looking for discontinuity. I'm sharing a sample image to illustrate what I mean. Every gray dot on the image represents a pixel that is tested. When the pixel is black it means we found a discontinuity:

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

    Very well... This last piece of code is responsible for figuring out the approximate angle of the discontinuity as well as indicate the central point of discontinuity. The following images are stored by orig_cropped. Once again I added a gray dot to show the exact positions detected as the center of the gaps:

    When executed, this application prints the following information to the screen:

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

    I hope it helps.

    这篇关于字母里面的字母,模式识别的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆