从图像中分割字符

时间:2014-10-09 05:16:24

标签: image opencv image-processing computer-vision

我在分割下面的车牌图像时遇到问题,而对下面的图像进行阈值处理时,字符被分成多于1个字符。所以我得到错误的OCR结果。我在对图像进行阈值处理后应用了形态学关闭操作,即使之后我也无法正确分割字符..

License Plate image 1

License Plate image 2

License Plate image 3

License Plate image 4

用于分割上述图像的代码如下所示

#include <iostream>
#include<cv.h>
#include<highgui.h>

using namespace std;
using namespace cv;
int main(int argc, char *argv[])
{
  IplImage *img1 = cvLoadImage(argv[1] , 0);
  IplImage *img2 = cvCloneImage(img1);

  cvNamedWindow("Orig"); 
  cvShowImage("Orig",img1);
  cvWaitKey(0);

  int wind = img1->height;
  if (wind % 2 == 0) wind += 1;

  cvAdaptiveThreshold(img1, img1, 255, CV_ADAPTIVE_THRESH_GAUSSIAN_C,
                      CV_THRESH_BINARY_INV, wind);

  IplImage* temp = cvCloneImage(img1);

  cvNamedWindow("Thre"); 
  cvShowImage("Thre",img1);
  cvWaitKey(0);

  IplConvKernel* kernal = cvCreateStructuringElementEx(3, 3, 1, 1,
                                                       CV_SHAPE_RECT,NULL);

  cvMorphologyEx(img1, img1, temp, kernal, CV_MOP_CLOSE, 1);

  cvNamedWindow("close"); 
  cvShowImage("close",img1);

  cvWaitKey(0);
}

下面给出的输出图像..

U Y and 2 are not segmented properly

U, P, Y and 2 are not segmented Properly

U 3 Y 2 and 5 are not segmented properly

任何人都可以提供一种很好的方法来分割这些图像中的字符...... ??

1 个答案:

答案 0 :(得分:14)

我想展示快速&amp;由于字符的实际分割不是问题,因此在板中隔离字母/数字的脏方法。当这些是输入图像时:

enter image description here enter image description here enter image description here enter image description here

这是我在算法结束时得到的结果:

enter image description here enter image description here enter image description here enter image description here

因此,我在本回答中讨论的内容将为您提供一些想法,并帮助您摆脱当前细分过程结束时出现的工件。请记住,这种方法只适用于这些类型的图像,如果你需要更强大的东西,你需要调整一些东西,或者想出一些全新的方法来做这些东西。

  • 鉴于亮度的剧烈变化,最好执行histogram equalization以提高对比度并使它们彼此更相似,因此所有其他技术和参数都适用于它们:

enter image description here enter image description here enter image description here enter image description here

  • 接下来,可以使用bilateral filter来平滑图像,同时保留对象的边缘,这对于二值化过程非常重要。此过滤器的处理能力稍高一些than others

enter image description here enter image description here enter image description here enter image description here

enter image description here enter image description here enter image description here enter image description here

  • 二值化的结果类似于您所取得的结果,因此我想出了一种使用findContours()删除较小和较大段的方法:

enter image description here enter image description here enter image description here enter image description here

  • 结果看起来好一点,但它破坏了盘子上重要角色的部分。然而,现在这不是一个真正的问题,因为我们并不担心识别这个角色:我们只想隔离它们所在的区域。因此,下一步是继续擦除段,更具体地说是那些未与数字的相同Y轴对齐的段。在此切割过程中幸存的轮廓是:

enter image description here enter image description here enter image description here enter image description here

  • 这样做要好得多,此时会创建一个新的std::vector<cv::Point>来存储绘制所有这些段所需的所有像素坐标。这是创建cv::RotatedRect所必需的,这使我们能够创建bounding box以及crop the image

enter image description here enter image description here enter image description here enter image description here

从现在开始,您可以使用裁剪后的图像执行自己的技术,并轻松分割印版的字符。

这是C ++代码

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

/* The code has an outter loop where every iteration processes one of the four input images */

std::string files[] = { "plate1.jpg", "plate2.jpg", "plate3.jpg", "plate4.jpg" };
cv::Mat imgs[4];
for (int a = 0; a < 4; a++)
{
    /* Load input image */

    imgs[a] = cv::imread(files[a]);
    if (imgs[a].empty())
    {
        std::cout << "!!! Failed to open image: " << imgs[a] << std::endl;
        return -1;
    }

    /* Convert to grayscale */

    cv::Mat gray;
    cv::cvtColor(imgs[a], gray, cv::COLOR_BGR2GRAY);

    /* Histogram equalization improves the contrast between dark/bright areas */

    cv::Mat equalized;
    cv::equalizeHist(gray, equalized);
    cv::imwrite(std::string("eq_" + std::to_string(a) + ".jpg"), equalized);
    cv::imshow("Hist. Eq.", equalized);

    /* Bilateral filter helps to improve the segmentation process */

    cv::Mat blur;
    cv::bilateralFilter(equalized, blur, 9, 75, 75);
    cv::imwrite(std::string("filter_" + std::to_string(a) + ".jpg"), blur);
    cv::imshow("Filter", blur);

    /* Threshold to binarize the image */

    cv::Mat thres;
    cv::adaptiveThreshold(blur, thres, 255, cv::ADAPTIVE_THRESH_GAUSSIAN_C, cv::THRESH_BINARY, 15, 2); //15, 2
    cv::imwrite(std::string("thres_" + std::to_string(a) + ".jpg"), thres);
    cv::imshow("Threshold", thres);

    /* Remove small segments and the extremelly large ones as well */

    std::vector<std::vector<cv::Point> > contours;
    cv::findContours(thres, contours, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);

    double min_area = 50;
    double max_area = 2000;
    std::vector<std::vector<cv::Point> > good_contours;
    for (size_t i = 0; i < contours.size(); i++)
    {
        double area = cv::contourArea(contours[i]);
        if (area > min_area && area < max_area)
            good_contours.push_back(contours[i]);
    }

    cv::Mat segments(gray.size(), CV_8U, cv::Scalar(255));
    cv::drawContours(segments, good_contours, -1, cv::Scalar(0), cv::FILLED, 4);
    cv::imwrite(std::string("segments_" + std::to_string(a) + ".jpg"), segments);
    cv::imshow("Segments", segments);

    /* Examine the segments that survived the previous lame filtering process
     * to figure out the top and bottom heights of the largest segments.
     * This info will be used to remove segments that are not aligned with
     * the letters/numbers of the plate.
     * This technique is super flawed for other types of input images.
     */

    // Figure out the average of the top/bottom heights of the largest segments
    int min_average_y = 0, max_average_y = 0, count = 0;
    for (size_t i = 0; i < good_contours.size(); i++)
    {
        std::vector<cv::Point> c = good_contours[i];
        double area = cv::contourArea(c);
        if (area > 200)
        {
            int min_y = segments.rows, max_y = 0;
            for (size_t j = 0; j < c.size(); j++)
            {
                if (c[j].y < min_y)
                    min_y = c[j].y;

                if (c[j].y > max_y)
                    max_y = c[j].y;
            }
            min_average_y += min_y;
            max_average_y += max_y;
            count++;
        }
    }
    min_average_y /= count;
    max_average_y /= count;
    //std::cout << "Average min: " << min_average_y << " max: " << max_average_y << std::endl;

    // Create a new vector of contours with just the ones that fall within the min/max Y
    std::vector<std::vector<cv::Point> > final_contours;
    for (size_t i = 0; i < good_contours.size(); i++)
    {
        std::vector<cv::Point> c = good_contours[i];
        int min_y = segments.rows, max_y = 0;
        for (size_t j = 0; j < c.size(); j++)
        {
            if (c[j].y < min_y)
                min_y = c[j].y;

            if (c[j].y > max_y)
                max_y = c[j].y;
        }

        // 5 is to add a little tolerance from the average Y coordinate
        if (min_y >= (min_average_y-5) && (max_y <= max_average_y+5))
            final_contours.push_back(c);
    }

    cv::Mat final(gray.size(), CV_8U, cv::Scalar(255));
    cv::drawContours(final, final_contours, -1, cv::Scalar(0), cv::FILLED, 4);
    cv::imwrite(std::string("final_" + std::to_string(a) + ".jpg"), final);
    cv::imshow("Final", final);


    // Create a single vector with all the points that make the segments
    std::vector<cv::Point> points;
    for (size_t x = 0; x < final_contours.size(); x++)
    {
        std::vector<cv::Point> c = final_contours[x];
        for (size_t y = 0; y < c.size(); y++)
            points.push_back(c[y]);
    }

    // Compute a single bounding box for the points
    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;

    // Draw the box at on equalized image
    cv::Point2f vertices[4];
    box.points(vertices);
    for(int i = 0; i < 4; ++i)
        cv::line(imgs[a], vertices[i], vertices[(i + 1) % 4], cv::Scalar(255, 0, 0), 1, CV_AA);
    cv::imwrite(std::string("box_" + std::to_string(a) + ".jpg"), imgs[a]);
    cv::imshow("Box", imgs[a]);

    // Crop the equalized image with the area defined by the ROI
    cv::Mat crop = equalized(roi);
    cv::imwrite(std::string("crop_" + std::to_string(a) + ".jpg"), crop);
    cv::imshow("crop", crop);

    /* The cropped image should contain only the plate's letters and numbers.
     * From here on you can use your own techniques to segment the characters properly.
     */

    cv::waitKey(0);
}

有关使用OpenCV进行车牌识别的更完整和强大的方法,请查看Mastering OpenCV with Practical Computer Vision Projects第5章Source code is available on Github!