投影分割算法—文字识别前的预处理(字符准确分割)

序言

基于opencv、numpy,实现了投影分割算法对文字识别前的预处理,能够准确分割出字符的具体位置,有助于提高模型准确率,使用numpy实现某些运算过程,程序耗时在毫秒级别,对于识别速度效率而言几乎无影响。

一、使用场景

搞过ocr的同学都知道,OCR通常可以分为两阶段:一、文字检测;二、文字识别。这两部分衔接起来,构成了OCR的识别任务。但是往往在这两个阶段的中间还可以加入一些处理,能够有助于提高模型的准确率,比较典型的例子可以看我之前的文章OCR-文本检测后的文字纠正,如果文字检测模型检测出来的文字倾斜角度较大的,在送入识别网络前不做任何纠正的话,识别率往往非常的感人。
在这里插入图片描述在这里插入图片描述

这部分的纠正在之前的文章中已经解决了,那么本文要解决的是当模型检测不准的时候,或者人工框选(有些需求可能需要人工去框选识别)的目标不准时,可能会出现如下情况:
在这里插入图片描述
在这里插入图片描述

可以看到文字的边缘有许多冗余的信息,这会干扰到文字识别的效果,我们需要的是精准的文字的位置,即红色框部分;

这是因为在模型训练的时候,通常情况下训练的数据是相对来说文字在图片中的占比是比较多的,并没有太多的冗余信息存在,如果按照以上的图片直接送入CRNN网络中,图片首先会等比例压缩成高为32的图片,本身文字所占比就比较小,经过一压缩,文字信息会变得更模糊了,严重的话会缩成一团字符笔画都无法看清的文字。

所以需要在识别之前做一些处理,我直接贴出代码实现,是基于上一篇文章的分割部分投影法实现,对速度进行了优化,投影法原理也比较简单,百度稍微看一下就好了,在这里就不多说,本实现应用了垂直和水平投影,代码分成了好几个模块,看起来也比较容易理解:

import numpy as np
import cv2
from collections import Counter

def stretch(img):
    '''
    图像拉伸函数
    '''
    maxi=float(img.max())
    mini=float(img.min())

    img = img*np.array(255/(maxi-mini))-np.array((255*mini)/(maxi-mini))
    img = img.astype("uint8")

    return img

def dobinaryzation(img):
    '''
    二值化处理函数
    '''
    maxi=float(img.max())
    mini=float(img.min())

    x = maxi-((maxi-mini)/2)
    ret,thresh=cv2.threshold(img,x,255,cv2.THRESH_BINARY)
    #返回二值化后的黑白图像
    return thresh

def getHProjection(image):

    # 图像高与宽
    (h, w) = image.shape
    # 长度与图像高度一致的数组
    h_ = [0] * h
    # 循环统计每一行白色像素的个数
    indx = np.where(image==255)
    indx_set = set(indx[0])
    indx_list = list(indx[0])

    count = Counter(indx_list)
    count_dict = dict(count)
    for i in indx_set:
        count = count_dict[i]
        h_[i] = count
    return h_

def getWProjection(image):
    # 图像高与宽

    (h, w) = image.shape
    # 长度与图像高度一致的数组
    w_ = [0] * w
    # 循环统计每一列白色像素的个数
    indx = np.where(image==255)

    indx_set = set(indx[1])
    indx_list = list(indx[1])

    count = Counter(indx_list)
    count_dict = dict(count)
    for i in indx_set:
        count = count_dict[i]
        w_[i] = count

    return w_

def find_license(img):
    '''
    预处理函数
    '''
    gray_img=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    # 灰度拉伸
    stretchedimg=stretch(gray_img)
    # 图像二值化

    binaryimg=dobinaryzation(stretchedimg)
    # canny边缘检测

    canny=cv2.Canny(binaryimg,binaryimg.shape[0],binaryimg.shape[1])

    # 进行闭运算
    kernel=np.ones((3,19),np.uint8)
    closingimg=cv2.morphologyEx(canny,cv2.MORPH_CLOSE,kernel)


    H = getHProjection(closingimg)     # 投影字符分割
    W = getWProjection(closingimg)

    start_h = 0
    H_Start = []
    H_End = []
    
    # 根据水平投影获取垂直分割位置
    for i in range(len(H)):
        if H[i] > 0 and start_h == 0:
            H_Start.append(i)
            start_h = 1
        if H[i] <= 0 and start_h == 1:
            H_End.append(i)
            start_h = 0
    if len(H_Start) != len(H_End):
        H_End.append(len(H))
    # 分割行,分割之后再进行列分割并保存分割位置
    sum_list = []

    for i in range(len(H_Start)):
        sum_pix = sum(H[H_Start[i]:H_End[i]])        # 对每个区域内像素相加
        sum_list.append(sum_pix)
    index_h = sum_list.index(max(sum_list))         # 选出像素分布最大的区域

    start_w = 0
    W_Start = []
    W_End = []
    
    # 根据垂直投影获取水平分割位置
    for i in range(len(W)):
        if W[i] > 0 and start_w == 0:
            W_Start.append(i)
            start_w = 1
        if W[i] <= 0 and start_w == 1:
            W_End.append(i)
            start_w = 0
    if len(W_Start) != len(W_End):
        W_End.append(len(W))
    # 分割行,分割之后再进行列分割并保存分割位置
    w_start = min(W_Start)
    w_end = max(W_End)

    cropImg = img[max(H_Start[index_h]-5,0):min(H_End[index_h]+5,img.shape[0]), max(w_start - 5, 0): min(w_end + 5, img.shape[1])]      # 对该区域进行裁剪,这里裁剪的时候保留了上下左右5个像素

    return cropImg

if __name__ == "__main__":
    import time
    img_path = r"/home/cai/1.png"
    img = cv2.imread(img_path)
    cv2.imshow("img0",img)
    time1 = time.time()
    img = find_license(img)
    time2 = time.time()
    print(time2-time1)
    cv2.imshow("img",img)
    cv2.waitKey()

运行前后图像的对比和速度(最上方的数字):
在这里插入图片描述
在这里插入图片描述
再附上C++实现:

//
// Created by cai on 2021/7/12.
//

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

using namespace cv;
using namespace std;

Mat vertical_projection(Mat dst, Mat images) //基于Y轴垂直投影分割算法(输入的是二值图像)
{
    int src_width = dst.cols;        //获得二值图像的列数(宽)(列的像素点个数)
    int src_height = dst.rows;       //获得二值图像的行数(高)(行的像素点个数)
    int* projectValArry = new int[src_height]();//创建用于储存每行黑色像素个数的数组
    int* projectValWidth = new int[src_width]();//创建用于储存每行黑色像素个数的数组

    for (int i = 0; i < src_height; i++){
        for (int j = 0; j < src_width; j++){
            if (dst.at<uchar>(i, j) == 255)      //at:取出二值图像的i行j列的像素点,如果他是黑色,则存取
            {
                projectValArry[i]++;   //每一行的黑色像素个数加一
                projectValWidth[j]++;
            }
        }
    }

    //定义Mat vector ,存储图片数组
    vector<int> H_strat;
    vector<int> H_end;

    bool white_block = 0;

    for (int i =0;i<src_height;i++){
        if (projectValArry[i]){
            white_block = 1;
            H_strat.push_back(i);
        }
        if (projectValArry[i] && white_block ==1){
            H_end.push_back(i);
            white_block = 0;
        }
    }
    int h_start = *min_element(H_strat.begin(),H_strat.end());
    int h_end = *max_element(H_end.begin(),H_end.end());



    //定义Mat vector ,存储图片数组
    vector<int> W_strat;
    vector<int> W_end;

    for (int i =0;i<src_width;i++){
        if (projectValWidth[i]){
            white_block = 1;
            W_strat.push_back(i);
        }
        if (projectValWidth[i] && white_block ==1){
            W_end.push_back(i);
            white_block = 0;
        }
    }
    int w_start = *min_element(W_strat.begin(),W_strat.end());
    int w_end = *max_element(W_end.begin(),W_end.end());

    Mat split_temp;
    split_temp = images(Rect(w_start-5, h_start-5, w_end-w_start+10, h_end - h_start+10)).clone();
    return split_temp;
}

int main(){
    //载入图像
    Mat img = imread("/home/cai/1.png");
    if (img.empty()){
        cout << "Error: Could not load image" << endl;
        return 0;
    }
    Mat gray;
    cvtColor(img, gray, COLOR_BGR2GRAY);

    double minv = 0.0, maxv = 0.0,thres = 0.0;
    double* minp = &minv;
    double* maxp = &maxv;

    minMaxIdx(gray,minp,maxp);

    //先转为灰度图
    Mat dst;
    thres = maxv - ((maxv-minv)/2);
    threshold(gray,dst,thres,255,THRESH_BINARY);

    Canny(dst,dst,dst.rows,dst.cols);

    Mat element;
    element = getStructuringElement(MORPH_RECT, Size(19, 3));
    morphologyEx(dst, dst, MORPH_CLOSE, element);

    Mat img_crop = vertical_projection(dst,img);
    imshow("img_crop",img_crop);
    waitKey(0);
    return 0;
}
  • 3
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值