C++:opencv将文字图片按行分割,可以用来提高OCR项目的识别率

先上效果图

代码

#include<opencv2/opencv.hpp>
#include<iostream>

//using namespace cv;
using namespace std;

 //计算旋转矩形左上角的x
 float count_x(cv::RotatedRect& p1) {
     if (p1.angle >=-10&&p1.angle<=10)return p1.center.x - p1.size.width / 2 * std::cos(p1.angle * CV_PI / 180) + p1.size.height / 2 * std::sin(p1.angle * CV_PI / 180);
     return p1.center.x - p1.size.width / 2 * std::cos(p1.angle * CV_PI / 180) - p1.size.height / 2 * std::sin(p1.angle * CV_PI / 180);
 }

 计算旋转矩形左上角的y
 float count_y(cv::RotatedRect& p1) {
     if (p1.angle >= -10 && p1.angle <= 10)return p1.center.y - p1.size.width / 2 * std::sin(p1.angle * CV_PI / 180) - p1.size.height / 2 * std::cos(p1.angle * CV_PI / 180);
     return p1.center.y - p1.size.width / 2 * std::sin(p1.angle * CV_PI / 180) + p1.size.height / 2 * std::cos(p1.angle * CV_PI / 180);
 }


//形成文字轮廓
cv::Mat preprocess(cv::Mat gray)
{
    //1.Sobel算子,x方向求梯度
    Sobel(gray, gray, CV_8U, 1, 0, 3);
    //imshow("3", gray);
    
    //2.二值化
    threshold(gray, gray, 0, 255, cv::THRESH_OTSU + cv::THRESH_BINARY);
    //imshow("4", gray);
    
    //3.膨胀和腐蚀操作核设定
    cv::Mat element1 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(24, 9));
    //控制高度设置可以控制上下行的膨胀程度,例如3比4的区分能力更强,但也会造成漏检
    cv::Mat element2 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(24, 4));

    //4.膨胀一次,让轮廓突出
    dilate(gray, gray, element2);
    //imshow("0", gray);
    
    //5.腐蚀一次,去掉细节,表格线等。这里去掉的是竖直的线
    erode(gray, gray, element1);
    //imshow("1", gray);
   
    //6.再次膨胀,让轮廓明显一些
    dilate(gray, gray, element2);
    //imshow("2", gray);

    return gray;
}


//存储文字轮廓坐标
vector<cv::RotatedRect> findTextRegion(cv::Mat img)
{
    vector<cv::RotatedRect> rects_coord;
    //1.查找轮廓
    vector<vector<cv::Point>> contours;//存储图像中的轮廓。
    vector<cv::Vec4i> hierarchy;//存储轮廓的层级信息。
    findContours(img, contours, hierarchy, cv::RETR_CCOMP, cv::CHAIN_APPROX_SIMPLE, cv::Point(0, 0));



    //2.筛选那些面积小的
    for (int i = 0; i < contours.size(); i++)
    {
        //计算当前轮廓的面积
        double area = cv::contourArea(contours[i]);

        //轮廓近似,作用较小,approxPolyDP函数有待研究
        double epsilon = 0.001 * arcLength(contours[i], true);
        cv::Mat approx;
        approxPolyDP(contours[i], approx, epsilon, true);

        //找到最小矩形,该矩形可能有方向
        cv::RotatedRect rect_min = minAreaRect(contours[i]);

        //计算高和宽
        int m_width = rect_min.boundingRect().width;
        int m_height = rect_min.boundingRect().height;

        
        //筛选那些太细的矩形,留下扁的
        if (m_height > m_width*1.2)
            continue;
        if (m_width <= 30 && m_height > m_width * 0.82)//判断例如{} 去掉竖线
            continue;
        if (m_height < 10)  //去掉未截完的文字
            continue;
        rects_coord.push_back(rect_min);

    }
    return rects_coord;
}

//文字矩形从下到上从左到右排序
bool cmp(cv::RotatedRect& p1, cv::RotatedRect& p2) {
    float y1= count_y(p1);
    float y2= count_y(p2);
    float x1= count_x(p1);
    float x2= count_x(p2);

    if (fabs(y1 - y2) < 5)return x1 < x2;
    else return y1 > y2;
}

//裁剪图片
cv::Mat* detect(cv::Mat img,int &count)
{
    cv::Mat gray;
    img.copyTo(gray);
    count = 0;

    //2.形态学变换的预处理,得到可以查找矩形的轮廓
    cv::Mat dilation = preprocess(gray);
    //imshow("bbb", gray);

    //3.查找和筛选文字区域
    vector<cv::RotatedRect> rects;
    vector<cv::Point2f> textrect;//存储每行文字的左上角坐标
    vector<pair<float, float>>lenth;//存储宽高信息。
    rects = findTextRegion(dilation);

    sort(rects.begin(), rects.end(), cmp);
     


    //第一个矩形的信息
    float text_width, text_height;
    text_width = rects[0].size.width;
    text_height= rects[0].size.height;

    if (text_width > 10 || text_height > 10) {
        if (text_width < text_height)swap(text_width, text_height);//使文字的宽永远大于高
    }
    cv::Point2f text_point(count_x(rects[0]), count_y(rects[0]));
    pair<float, float>p(text_width, text_height);
    textrect.push_back(text_point);
    lenth.push_back(p);
    count = 0;//计算有多少行



    //同一行合并
    for (int i = 0; i < rects.size(); i++)
    {
        if (i != rects.size() - 1) {
            text_width = rects[i].size.width;
            text_height = rects[i].size.height;
            float text_width1 = rects[i+1].size.width;
            float text_height1 = rects[i+1].size.height;
            if (text_width > 10 || text_height > 10) {
                if (text_width < text_height)swap(text_width, text_height);//使文字的宽永远大于高
            }
            if (text_width1 > 10 || text_height1 > 10) {
                if (text_width1 < text_height1)swap(text_width1, text_height1);//使文字的宽永远大于高
            }

            float y1 = count_y(rects[i]);
            float y2 = count_y(rects[i+1]);
            float x1 = count_x(rects[i]);
            float x2 = count_x(rects[i+1]);
            if (fabs(y2 - y1) < 5) {
                lenth[count].first = fabs(x2 - textrect[count].x)+ text_width1;
                lenth[count].second = max(text_height, text_height);

            }
            else {
                count++;
                text_point.x = x2, text_point.y = y2;
                textrect.push_back(text_point);
                p.first = text_width1; p.second = text_height1;
                lenth.push_back(p);
            }
        }
    }


    //把合并后的每一行单独作为图片输出
    cv::Mat* text_part = new cv::Mat[count+1];
    for (int i = count; i >=0; i--) {
        text_part[i] = cv::Mat(lenth[i].second+15, lenth[i].first+15, CV_8UC3);
        cv::Rect rect(textrect[i].x-5, textrect[i].y-5, lenth[i].first, lenth[i].second+9);//注意左上角的纵坐标比左下角要小,
        text_part[i] = img(rect).clone();
        cv::imshow("切割图片 " + std::to_string(i), text_part[i]);
    }
      
    return text_part;
}


int main(int argc, char* argv[])
{
    int count = 0;
    cv::Mat image_dispose = cv::imread("aaaccc.png");
    if (image_dispose.empty()) {
        std::cout << "无法加载图像文件" << std::endl;
        return 0;
    }

    cvtColor(image_dispose, image_dispose, cv::COLOR_BGR2GRAY); 
    // 创建一个map用于存储每个像素值的频次
    std::map<uchar, int> colorFreq;

    // 遍历图像的每个像素
    for (int i = 0; i < image_dispose.rows; i++) {
        for (int j = 0; j < image_dispose.cols; j++) {
            // 获取像素值
            uchar pixel = image_dispose.at<uchar>(i, j);
            
            // 像素值频次加一
            colorFreq[pixel]++;
        }
    }

    // 找到频次最高的像素值和频次
    int mostFrequentColor = 0;
    int maxFrequency = 0;

    std::map<uchar, int>::iterator it;
    for (it = colorFreq.begin(); it != colorFreq.end(); ++it) {
         std::pair<int, int> pair = *it;
         int frequency = pair.second;
        if (frequency > maxFrequency) {
            maxFrequency = frequency;
            mostFrequentColor = pair.first;
        }
    }
    //调试,得到像素值最多的颜色

        copyMakeBorder(image_dispose, image_dispose, 15, 15, 15, 15, cv::BORDER_CONSTANT, mostFrequentColor <= 120 ? cv::Scalar(255 - mostFrequentColor) : cv::Scalar(mostFrequentColor));//添加白色像素为10的边框

    cv::Mat* img;
    img=detect(image_dispose,count);

    imshow("原图", image_dispose);
    cv::waitKey(0);
    delete[]img;
    return 0;
}

并且我自己写了一个OCR软件,如果需要可以看这篇文章C++:分享一款自己写的可以对图片文字识别的单机软件 - 知乎 (zhihu.com)

本站下载链接:https://download.csdn.net/download/KeNanKN/87848965

参考资料:(8条消息) OpenCV2.4.13 文本分割(水平垂直,直方图投影)_songyalong123的博客-CSDN博客

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值