每日更新,建议关注、收藏、点赞

目录

  • ocr流程
  • 自己生成文字训练集
  • 字符识别方法
  • 证件照识别
  • 复杂场景下的ocr
    • 图片预处理
      • 透视矫正/透视变换
      • 水平矫正
  • Tesseract
  • 实战
    • 基于精准OCR智能识别的移动扫描工具

ocr流程

版面分析 、预处理-> 行列切割 -> 字符识别 -> 后处理识别矫正

  1. 判断页面上的文本朝向,图像预处理,做角度矫正和去噪。
  2. 对文档版面进行分析,进每一行进行行分割,把每一行的文字切割下来,最后再对每一行文本进行列分割,切割出每个字符。
  3. 将该字符送入训练好的OCR识别模型进行字符识别,得到结果。
  4. 但是模型识别结果往往是不太准确的,我们需要对其进行识别结果的矫正和优化,比如我们可以设计一个语法检测器,去检测字符的组合逻辑是否合理。比如,考虑单词Because,我们设计的识别模型把它识别为8ecause,那么我们就可以用语法检测器去纠正这种拼写错误,并用B代替8并完成识别矫正。

自己生成文字训练集

已有的数据集
CASIA Online and Offline Chinese Handwriting Databases

自己生成文字训练集步骤

  1. 确定你要生成多少字体,生成一个记录着汉字与label的对应表。
  2. 确定和收集需要用到的字体文件。
  3. 生成字体图像,存储在规定的目录下。
    第三步,生成字体图像最为重要,如果仅仅是生成很正规的文字,那么用这个正规文字集去训练模型,第一图像数目有点少,第二模型泛化能力比较差,所以我们需要对字体图像做大量的图像处理工作,以增大我们的印刷体文字数据集。
  4. 适当的数据增强。
    可以做的一些图像增强工作有这些:
    文字扭曲
    背景噪声(椒盐)
    文字位置(设置文字的中心点)
    笔画粘连(膨胀来模拟)
    笔画断裂(腐蚀来模拟)
    文字倾斜(文字旋转)
    多种字体

一、生成汉字与label的对应表
这里的汉字、label映射表的生成我使用了pickel模块,借助它生成一个id:汉字的映射文件存储下来。
这里举个小例子说明怎么生成这个“汉字:id”映射表。

首先在一个txt文件里写入你想要的汉字,如果对汉字对应的ID没有要求的话,我们不妨使用该汉字的排位作为其ID,比如“一二三四五”中,五的ID就是00005。如此类推,把汉字读入内存,建立一个字典,把这个关系记录下来,再使用pickle.dump存入文件保存。

二、收集字体文件
字体文件上网收集就好了,但是值得注意的是,不是每一种字体都支持汉字,所以我们需要筛选出真正适合汉字生成的字体文件才可以。如果需要进一步扩大数据集来增强训练得到的模型的泛化能力,可以花更多的时间去收集各类汉字字体,那么模型在面对各种字体时也能从容应对,给出准确的预测。

三、文字图像生成
首先是定义好输入参数,其中包括输出目录、字体目录、测试集大小、图像尺寸、图像旋转幅度等等。
将我们第一步得到的对应表读入内存,因为这个表示ID到汉字的映射,我们在做一下转换,改成汉字到ID的映射,用于后面的字体生成。
对旋转的角度存储到列表中,旋转角度的范围是[-rotate,rotate].
字体图像如何生成?首先我们使用的工具是PIL。PIL里面有很好用的汉字生成函数,我们用这个函数再结合我们提供的字体文件,就可以生成我们想要的数字化的汉字了。我们先设定好我们生成的字体颜色为黑底白色,字体尺寸由输入参数来动态设定。

def args_parse():
    #解析输入参数
    parser = argparse.ArgumentParser(
        description=description, formatter_class=RawTextHelpFormatter)
    parser.add_argument('--out_dir', dest='out_dir',
                        default=None, required=True,
                        help='write a caffe dir')
    parser.add_argument('--font_dir', dest='font_dir',
                        default=None, required=True,
                        help='font dir to to produce images')
    parser.add_argument('--test_ratio', dest='test_ratio',
                        default=0.2, required=False,
                        help='test dataset size')
    parser.add_argument('--width', dest='width',
                        default=None, required=True,
                        help='width')
    parser.add_argument('--height', dest='height',
                        default=None, required=True,
                        help='height')
    parser.add_argument('--no_crop', dest='no_crop',
                        default=True, required=False,
                        help='', action='store_true')
    parser.add_argument('--margin', dest='margin',
                        default=0, required=False,
                        help='', )
    parser.add_argument('--rotate', dest='rotate',
                        default=0, required=False,
                        help='max rotate degree 0-45')
    parser.add_argument('--rotate_step', dest='rotate_step',
                        default=0, required=False,
                        help='rotate step for the rotate angle')
    parser.add_argument('--need_aug', dest='need_aug',
                        default=False, required=False,
                        help='need data augmentation', action='store_true')   
    args = vars(parser.parse_args()) 
    return args

#将汉字的label读入,得到(ID:汉字)的映射表label_dict
label_dict = get_label_dict()

char_list=[]  # 汉字列表
value_list=[] # label列表
for (value,chars) in label_dict.items():
    print (value,chars)
    char_list.append(chars)
    value_list.append(value)

# 合并成新的映射关系表:(汉字:ID)
lang_chars = dict(zip(char_list,value_list)) 
font_check = FontCheck(lang_chars) 

if rotate < 0:
    roate = - rotate

if rotate > 0 and rotate <= 45:
    all_rotate_angles = []
    for i in range(0, rotate+1, rotate_step):  
        all_rotate_angles.append(i)
    for i in range(-rotate, 0, rotate_step):
        all_rotate_angles.append(i)
    #print(all_rotate_angles)

# 生成字体图像
class Font2Image(object):

    def __init__(self,
                 width, height,
                 need_crop, margin):
        self.width = width
        self.height = height
        self.need_crop = need_crop
        self.margin = margin

    def do(self, font_path, char, rotate=0):
        find_image_bbox = FindImageBBox()
        # 黑色背景
        img = Image.new("RGB", (self.width, self.height), "black")
        draw = ImageDraw.Draw(img)
        font = ImageFont.truetype(font_path, int(self.width * 0.7),)
        # 白色字体
        draw.text((0, 0), char, (255, 255, 255),
                  font=font)
        if rotate != 0:
            img = img.rotate(rotate)
        data = list(img.getdata())
        sum_val = 0
        for i_data in data:
            sum_val += sum(i_data)
        if sum_val > 2:
            np_img = np.asarray(data, dtype='uint8')
            np_img = np_img[:, 0]
'''
X[:,0]是numpy中数组的一种写法
表示对一个二维数组,取该二维数组第一维中的所有数据,第二维中取第0个数据
直观来说,X[:,0] 就是取所有行的第0个数据, X[:,1] 就是取所有行的第1个数据。
'''
            np_img = np_img.reshape((self.height, self.width))
            cropped_box = find_image_bbox.do(np_img)
            left, upper, right, lower = cropped_box
            np_img = np_img[upper: lower + 1, left: right + 1]
            if not self.need_crop:
                preprocess_resize_keep_ratio_fill_bg = \
                    PreprocessResizeKeepRatioFillBG(self.width, self.height,
                                                    fill_bg=False,
                                                    margin=self.margin)
                np_img = preprocess_resize_keep_ratio_fill_bg.do(
                    np_img)
            # cv2.imwrite(path_img, np_img)
            return np_img
        else:
            print("img doesn't exist.")

#写两个循环,外层循环是汉字列表,内层循环是字体列表
#对于每个汉字会得到一个image_list列表,里面存储着这个汉字的所有图像。
for (char, value) in lang_chars.items():  # 外层循环是字
    image_list = []
    print (char,value)
    #char_dir = os.path.join(images_dir, "%0.5d" % value)
    for j, verified_font_path in enumerate(verified_font_paths):    # 内层循环是字体   
        if rotate == 0:
            image = font2image.do(verified_font_path, char)
            image_list.append(image)
        else:
            for k in all_rotate_angles:	
                image = font2image.do(verified_font_path, char, rotate=k)
                image_list.append(image)

#将image_list中图像按照比例分为训练集和测试集存储。
        test_num = len(image_list) * test_ratio
        random.shuffle(image_list)  # 图像列表打乱
        count = 0
        for i in range(len(image_list)):
            img = image_list[i]
            #print(img.shape)
            if count < test_num :
                char_dir = os.path.join(test_images_dir, "%0.5d" % value)
            else:
                char_dir = os.path.join(train_images_dir, "%0.5d" % value)

            if not os.path.isdir(char_dir):
                os.makedirs(char_dir)

            path_image = os.path.join(char_dir,"%d.png" % count)
            cv2.imwrite(path_image,img)# cv2.imwrite() 用于将图像保存到指定的文件。
            count += 1

#写好代码后,我们执行如下指令,开始生成印刷体文字汉字集。其实也就是运行这个.py文件
 python gen_printed_char.py --out_dir ./dataset --font_dir ./chinese_fonts --width 30 --height 30 --margin 4 --rotate 30 --rotate_step 1


'''
--out_dir 表示生成的汉字图像的存储目录
--font_dir 表示放置汉字字体文件的路径
--width --height 表示生成图像的高度和宽度
--margin 表示字体与边缘的间隔
--rotate 表示字体旋转的范围,[-rotate,rotate]
--rotate_step 表示每次旋转的间隔

数据集生成完我们可以发现,在dataset文件夹下得到train和test两个文件夹,train和test文件夹下都有3755个子文件夹,分别存储着生成的3755个汉字对应的图像,每个子文件的名字就是该汉字对应的id。
'''

如果我们想在数据增强上再做多点东西,想必我们最终训练出来的OCR模型的性能会更加优秀。我们使用opencv来完成我们定制的汉字图像增强任务。

因为生成的图像比较小,仅仅是30*30,如果对这么小的图像加噪声或者形态学处理,得到的字体图像会很糟糕,所以我们在做数据增强时,把图片尺寸适当增加,比如设置为100×100,再进行相应的数据增强,效果会更好。

def add_noise(cls,img):
    for i in range(20): #添加点噪声
        temp_x = np.random.randint(0,img.shape[0])
        temp_y = np.random.randint(0,img.shape[1])
        img[temp_x][temp_y] = 255
    return img

def add_erode(cls,img):#腐蚀
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(3, 3))    
    img = cv2.erode(img,kernel) 
    return img

def add_dilate(cls,img):#膨胀
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(3, 3))    
    img = cv2.dilate(img,kernel) 
    return img

def do(self,img_list=[]):#随机选取数据增强的方式
    aug_list= copy.deepcopy(img_list)
    for i in range(len(img_list)):
        im = img_list[i]
        if self.noise and random.random()<0.5:
            im = self.add_noise(im)
        if self.dilate and random.random()<0.25:
            im = self.add_dilate(im)
        if self.erode and random.random()<0.25:
            im = self.add_erode(im)    
        aug_list.append(im)
    return aug_list

python gen_printed_char.py --out_dir ./dataset2 --font_dir ./chinese_fonts --width 100 --height 100 --margin 10 --rotate 30 --rotate_step 1 --need_aug

字符识别方法

  1. 开源OCR引擎Tesseract,这是谷歌维护的一个OCR引擎。Tesseract现在的版本已经支持识别很多种语言但汉字识别的精度上还是需要自己去改善。Tesseract在阿拉伯数字和英文字母上的识别还是可以的,如果你要做的应用是要识别英文或者数字,不妨考虑一下使用Tesseract;不过要做到你想要的识别率,后期微调或者优化肯定要多下功夫的。
  2. 字符模板匹配法。暴力的字符模板匹配法看起来很蠢,但是在简单应用上可能却很凑效。
    比如在对电表数字进行识别时,考虑到电表上的字体较少(可能就只有阿拉伯数字),而且字体很统一,清晰度也很高,所以识别难度不高。
    针对这种简单的识别场景,我们首先考虑的识别策略当然是最为简单和暴力的模板匹配法。我们首先定义出数字模板(0~9),然后用该模板滑动匹配电表上的字符,这种策略虽然简单但是相当有效。我们不需要左思右想去建模,训练模型,只需要识别前做好模板库就可以了。
  3. OCR的一般方法,即特征设计、特征提取、分类得出结果的计算机视觉通用的技巧。
    在深度学习之前,OCR的方法基本都是这种方法,其效果不算特别好。
    第一步是特征设计和提取,特征设计(做过模式识别相关项目的懂得都懂),我们现在识别的目标是字符,所以我们要为字符设计它独有的的特征,来为后面的特征分类做好准备。字符有结构特征,即字符的端点、交叉点、圈的个数、横线竖线条数等等,都是可以利用的字符特征。比如“品”字,它的特征就是它有3个圈,6条横线,6条竖线。除了结构特征,还有大量人工专门设计的字符特征,据说都能得到不错的效果。最后再将这些特征送入分类器(SVM)做分类,得出识别结果。
    这种方式最大的缺点就是,人们需要花费大量时间做特征的设计,这是一件相当费工夫的事情。通过人工设计的特征(例如HOG)来训练字符识别模型,此类单一的特征在字体变化,模糊或背景干扰时泛化能力迅速下降。而且过度依赖字符切分的结果,在字符扭曲、粘连、噪声干扰的情况下,切分的错误传播尤其突出。针对传统OCR解决方案的不足,学界业界纷纷拥抱基于深度学习的OCR。
  4. 现在OCR基本都用卷积神经网络来做了,而且识别率也是惊人的好,人们也不再需要花大量时间去设计字符特征了。在OCR系统中,人工神经网络主要充当特征提取器和分类器的功能,输入是字符图像,输出是识别结果。运用卷积神经网络
    当然用深度学习做OCR并不是在每个方面都很优秀,因为神经网络的训练需要大量的训练数据,那么如果我们没有办法得到大量训练数据时,这种方法很可能就不奏效了。其次,神经网络的训练需要花费大量的时间,并且需要用到的硬件资源一般都比较多,这几个都是需要考虑的问题。

证件照识别

  1. 将证件轮廓找到
    • 二值化+高斯滤波+膨胀+canny边缘提取
      这里膨胀的作用:使某些信息区域轮廓闭合,便于提取信息区域轮廓。
    • 轮廓查找并筛选
      有很多干扰项轮廓,如果我们不能很好的剔除这些轮廓,我们根本没法找出我们想要的信息区域。我筛选轮廓的方法很简单,就是找出一张图片中面积最大的那个轮廓作为我们的信息区域轮廓
  2. 提取证件矩形轮廓四点进行透视变换
    • 由于轮廓不一定是四边形的,所以(比如x坐标最大的那个坐标肯定是四边形右上角坐标或者右下角坐标,x坐标最小的那个坐标肯定是左上角或者下角的那个坐标,如此类推)这种思路不可行。
    • 基于直线交点的思路。我们首先使用霍夫变换找出四边形的边,然后求两两直线的交点就是四边形的顶点。
      最大的问题就是,我们怎么保证我们使用霍夫变换找到的直线刚好就是形成四边形的四条直线?所以我们就必须不断地去改变霍夫变换的参数,不断迭代,来求出一个可以形成四边形的直线情况。
      什么情况的直线我们不能接受?两两直线过于接近的、两两直线没有交点、检测出来的直线数目不是4条
      如果找到了满足条件的四条直线,我们就可以去计算他们的交点了。
      计算出四个交点后,继续筛选:两两定点的距离过近排除、四个点构成不了四边形排除
      通过以上筛选条件的,可以认为就是我们找的那四个顶点,这时我们就可以停止迭代,进行顶点排序,即确定这四个顶点哪个是左上角点,哪个又是右下点。
    • 用这四点来进行透视变换, 后文有详细的介绍。
  3. 字符识别部分

复杂场景下的ocr

OCR传统方法在应对复杂图文场景的文字识别能力不够,如何把文字在复杂场景读出来,并且读得准确,关键在于 场景文本识别(文字检测+文字识别)

图片预处理

透视矫正/透视变换

透视变换是将图片投影到一个新的视平面,也称作投影映射。它是二维(x,y)到三维(X,Y,Z),再到另一个二维空间(x’,y’)的映射。我们常说的仿射变换是透视变换的一个特例。
相对于仿射变换,它不仅仅是线性变换。它提供了更大的灵活性,可以将一个四边形区域映射到另一个四边形区域。
透视变换也是通过矩阵乘法实现的,使用的是一个3x3的矩阵,矩阵的前两行与仿射矩阵相同,这意味着仿射变换的所有变换透视变换也可以实现。而第三行则用于实现透视变换。


注意:变换矩阵T和KT得到的结果是一样的,这个可以自己推一下,相当于分子共同因子还有k,还是把k给除掉了

通过透视变换,我们可以实现将一张正对我们的图片转换成仰视、俯视、侧视看这张图片的效果,反之也可以实现转换到正对的效果。

  • 仿射变换是透视变换的一种特例。
    仿射变换是一种二维坐标到二维坐标之间的线性变换,也就是只涉及一个平面内二维图形的线性变换。
    图形的平移、旋转、错切、放缩都可以用仿射变换的变换矩阵表示。
    它保持了二维图形的两种性质:
    ① “平直性”:直线经过变换之后依然是直线。一条直线经过平移、旋转、错切、放缩都还是一条直线。
    ②“平行性”:变换后平行线依然是平行线,且直线上点的位置顺序不变。
    任意的仿射变换都能表示为一个坐标向量乘以一个矩阵的形式

#平移
x = 100
y = 200
M = np.float32([[1, 0, x], [0, 1, y]])
move1 = cv2.warpAffine(img, M, (width, height))

#旋转
retval = cv2.getRotationMatrix2D(center, angle, scale)
'''
center是旋转的中心点
angle是旋转角度,正数表示逆时针旋转,负数表示顺时针旋转
scale为变换尺寸(缩放大小)
'''

M = cv2.getRotationMatrix2D((width/2, height/2), 45, 1)
rotation = cv2.warpAffine(img, M, (width, height))

#透视变换
dst = cv2.warpPerspective(src, M, dsize, [, flags[, borderMode[, borderValue]]])
'''
dst代表透视处理后的输出图像,dsize决定输出图像的实际大小
src代表要透视的图像
M为一个3X3的变换矩阵
dsize代表输出图像的尺寸大小
flags表示差值方法,默认为INTER_LINEAR。当值为WARP_INVERSE_MAP时,意味着M为逆变换,实现从目标图像dst到src的逆变换。具体值见下表。
borderMode表示边类型。默认为BORDER_CONSTANT。当值为BORDER_TRANSPARENT时,意味着目标图像内的值不做改变,这些值对应着原始图像的异常值。
border表示边界值,默认为0
与仿射变化一样,可以使用函数cv2.getPerspectiveTransform()来生成转换矩阵。

'''
rows, cols, ch = img.shape
p1 = np.float32([[80, 266], [494, 27], [239, 543], [655, 300]])
# 左上角,右上角,左下角,右下角
p2 = np.float32(
    [[0, 0], [800, 0], [0, 600], [800, 600]])
M = cv2.getPerspectiveTransform(p1, p2)
dst = cv2.warpPerspective(img, M, (cols, rows))

#复杂的仿射变换
retval = cv2.getAffineTransform(src, dst)
'''
src表示输入图像的三个点坐标
dst表示输出图像的三个点坐标
src和dst三个点坐标分别表示平行四边形的左上角、右上角、右下角的三个点。
'''

rows, cols, ch = img.shape
p1 = np.float32([[81, 265], [240, 540], [496, 26]])
p2 = np.float32(
    [[0, 0], [0, 200], [300, 0]])
M = cv2.getAffineTransform(p1, p2)
dst = cv2.warpAffine(img, M, (cols, rows))

#透视矫正
def order_points(pts):
    # 一共4个坐标点
    rect = np.zeros((4, 2), dtype = "float32")
 
    # 按顺序找到对应坐标0123分别是 左上,右上,右下,左下
    # 计算左上,右下
    s = pts.sum(axis = 1)
    rect[0] = pts[np.argmin(s)]
    rect[2] = pts[np.argmax(s)]
 
    # 计算右上和左下
    diff = np.diff(pts, axis = 1)#设置 axis=0 时,是按列进行做差,axis=1 时是按行进行做差。
    rect[1] = pts[np.argmin(diff)]
    rect[3] = pts[np.argmax(diff)]
 
    return rect
 
def four_point_transform(image, pts):
    # 获取输入坐标点
    rect = order_points(pts)
    (tl, tr, br, bl) = rect
 
    # 计算输入的w和h值
    widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
    widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
    maxWidth = max(int(widthA), int(widthB))
 
    heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
    heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
    maxHeight = max(int(heightA), int(heightB))
 
    # 变换后对应坐标位置
    dst = np.array([#四个顶点
        [0, 0], 
        [maxWidth - 1, 0],
        [maxWidth - 1, maxHeight - 1],
        [0, maxHeight - 1]], dtype = "float32")
 
    # 计算变换矩阵
    M = cv2.getPerspectiveTransform(rect, dst)
    warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))
 
    # 返回变换后结果
    return warped
 
# 透视矫正
def perspective_transformation(img):
    # 读取图像,做灰度化、高斯模糊、膨胀、Canny边缘检测
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    blurred = cv2.GaussianBlur(gray, (5, 5), 0)#高斯模糊、高斯滤波
    dilate = cv2.dilate(blurred, cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)))
    # edged = cv2.Canny(dilate, 75, 200)
    edged = cv2.Canny(dilate, 30, 120, 3)
 
    cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    '''
contours, hierarchy = cv2.findContours( image, mode, method)

返回值为:
contours:返回的轮廓。
该返回值返回的是一组轮廓信息,每个轮廓都是由若干个点所构成的。例如,contours[i] 是第 i 个轮廓(下标从 0 开始),contours[i][j]是第 i 个轮廓内的第 j 个点。返回值 contours 的 type 属性是 list 类型,list 的每个元素都是图像的一个轮廓,用 Numpy中的 ndarray 结构表示。

hierarchy:图像的拓扑信息(轮廓层次)。
图像内的轮廓可能位于不同的位置。比如,一个轮廓在另一个轮廓的内部。在这种情况下,
我们将外部的轮廓称为父轮廓,内部的轮廓称为子轮廓。按照上述关系分类,一幅图像中所有轮廓之间就建立了父子关系。
根据轮廓之间的关系,就能够确定一个轮廓与其他轮廓是如何连接的。比如,确定一个轮廓是某个轮廓的子轮廓,或者是某个轮廓的父轮廓。上述关系被称为层次(组织结构),返回值 hierarchy 就包含上述层次关系。
每个轮廓 contours[i]对应 4 个元素来说明当前轮廓的层次关系。其形式为:
[Next,Previous,First_Child,Parent]
Next:后一个轮廓的索引编号。
Previous:前一个轮廓的索引编号。
First_Child:第 1 个子轮廓的索引编号。
Parent:父轮廓的索引编号。
如果上述各个参数所对应的关系为空时,也就是没有对应的关系时,则将该参数所对应的值设为“-1”。

式中的参数为:
image:原始图像。8 位单通道图像,所有非零值被处理为 1,所有零值保持不变。即 会被自动处理为二值图像。在实际操作时,可以根据需要,预先使用阈值处理等函数将待查找轮廓的图像处理为二值图像。

mode:轮廓检索模式。
cv2.RETR_EXTERNAL:只检测外轮廓。
cv2.RETR_LIST:对检测到的轮廓不建立等级关系。
cv2.RETR_CCOMP:检索所有轮廓并将它们组织成两级层次结构。上面的一层为外边界,下面的一层为内孔的边界。如果内孔内还有一个连通物体,那么这个物体的边界仍
然位于顶层。
cv2.RETR_TREE:建立一个等级树结构的轮廓。

method:轮廓的近似方法。
参数 method 决定了如何表达轮廓,可以为如下值:
cv2.CHAIN_APPROX_NONE:存储所有的轮廓点,相邻两个点的像素位置差不超过 1,即 max(abs(x1-x2),abs(y2-y1))=1。
cv2.CHAIN_APPROX_SIMPLE:压缩水平方向、垂直方向、对角线方向的元素,只保留该方向的终点坐标。例如,在极端的情况下,一个矩形只需要用 4 个点来保存轮廓信息。

注意使用函数 cv2.findContours()查找图像轮廓之前,要预先对图像进行阈值分
割或者边缘检测处理,得到满意的二值图像后再将其作为参数使用。在 OpenCV 中,都是从黑色背景中查找白色对象。因此,对象必须是白色的,背景必须是黑色的。
    '''
    cnts = cnts[0] if imutils.is_cv2() else cnts[1]  # 判断是OpenCV2还是OpenCV3
    docCnt = None
 
    # 确保至少找到一个轮廓
    if len(cnts) > 0:
        # 按轮廓大小降序排列
        cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
        for c in cnts:
            # 近似轮廓
            peri = cv2.arcLength(c, True)#arcLength 函数用于计算封闭轮廓的周长或曲线的长度。
            approx = cv2.approxPolyDP(c, 0.02 * peri, True)#以指定的精度近似生成多边形曲线。
            # 如果我们的近似轮廓有四个点,则确定找到了纸
            if len(approx) == 4:
                docCnt = approx
                break
 
    # 对原始图像应用四点透视变换,以获得纸张的俯视图
    paper = four_point_transform(img, docCnt.reshape(4, 2))
    return paper


水平矫正

普通的水平矫正图像都会带有自己的边缘,根据边缘可以提取出一个mask,然后进行旋转即可。
文本图像的背景是白色的,所以我们没有办法像人民币发票那类有明显边界的矩形物体那样,提取出轮廓并旋转矫正。这里我们用基于直线探测的水平矫正。
- 文本水平矫正
用霍夫线变换探测出图像中的所有直线
计算出每条直线的倾斜角,求他们的平均值
根据倾斜角旋转矫正

霍夫线变换,对图像中每个点对应曲线间的交点进行追踪,如果交于一点的曲线的数量超过了阈值,就认为这个交点所对应的 ( r , θ )在原图像中为一条直线。
这里对直线的表示不是用传统的斜率和截距表示,因为垂直线的斜率不存在(或无限大),所以用Hesse normal form(Hesse法线式) 。即用原点到直线上的最近点 ( r , θ ) 表示,这个点是可以将图像的每一条直线与一对参数相关联。这个参数平面有时被称为霍夫空间,用于二维直线的集合。

经过Hough变换,将图像空间中的一个点映射到Hough空间

对图像中所有的点进行上述操作,如果两个不同点进行上述操作后得到的曲线在平面 θ − r 相交, 这就意味着它们通过同一条直线。
霍夫线变换,对图像中每个点对应曲线间的交点进行追踪,如果交于一点的曲线的数量超过了阈值,就认为这个交点所对应的 ( r , θ )在原图像中为一条直线。

# coding=utf-8
import cv2
import numpy as np
 
input_img_file = "../test/test.png"
 
# 度数转换
def DegreeTrans(theta):
    res = theta / np.pi * 180
    return res
 
# 逆时针旋转图像degree角度(原尺寸)
def rotateImage(src, degree):
    # 旋转中心为图像中心
    h, w = src.shape[:2]
    # 计算二维旋转的仿射变换矩阵
    RotateMatrix = cv2.getRotationMatrix2D((w/2.0, h/2.0), degree, 1)
    '''
    cv.getRotationMatrix2D(center, angle, scale) → M
center	表示旋转中心坐标,二元元组 (x0, y0)。
angle	表示旋转角度,单位为角度,逆时针为正数,顺时针为负数。
scale	表示缩放因子。
    '''
    print(RotateMatrix)
    # 仿射变换,背景色填充为白色
    rotate = cv2.warpAffine(src, RotateMatrix, (w, h), borderValue=(255, 255, 255))
    return rotate
 
# 通过霍夫变换计算角度
def CalcDegree(srcImage):
    midImage = cv2.cvtColor(srcImage, cv2.COLOR_BGR2GRAY)
    dstImage = cv2.Canny(midImage, 50, 200, 3)
    lineimage = srcImage.copy()
 
    # 通过霍夫变换检测直线
    # 第4个参数就是阈值,阈值越大,检测精度越高
    lines = cv2.HoughLines(dstImage, 1, np.pi/180, 200)
    # 由于图像不同,阈值不好设定,因为阈值设定过高导致无法检测直线,阈值过低直线太多,速度很慢
    '''
cv.HoughLines(img,rho,theta,threshold)
-img:检测的图像,要求是二值化的图像,所以在调用霍夫变换之前首先要进行二值化,或者进行Canny边缘检测
-rho、theta:\rho和\theta的精确度
-threshold:阈值,只有累加器中的值高于该阈值是才被认为是直线
    '''
    sum = 0
    # 依次画出每条线段
    for i in range(len(lines)):
        for rho, theta in lines[i]:
            # print("theta:", theta, " rho:", rho)
            a = np.cos(theta)
            b = np.sin(theta)
            x0 = a * rho
            y0 = b * rho
            #(x1,y1),(x2,y2)构成了原点和(x0,y0)连线的垂直线段 即检测到的一条直线
            x1 = int(round(x0 + 1000 * (-b)))
            y1 = int(round(y0 + 1000 * a))
            x2 = int(round(x0 - 1000 * (-b)))
            y2 = int(round(y0 - 1000 * a))
            # 只选角度最小的作为旋转角度
            sum += theta
            cv2.line(lineimage, (x1, y1), (x2, y2), (0, 0, 255), 1, cv2.LINE_AA)
            cv2.imshow("Imagelines", lineimage)
 
    # 对所有角度求平均,这样做旋转效果会更好
    average = sum / len(lines)
    angle = DegreeTrans(average) - 90
    return angle
 
if __name__ == '__main__':
    image = cv2.imread(input_img_file)
    cv2.imshow("Image", image)
    # 倾斜角度矫正
    degree = CalcDegree(image)
    print("调整角度:", degree)
    rotate = rotateImage(image, degree)
    cv2.imshow("rotate", rotate)
    # cv2.imwrite("../test/recified.png", rotate, [int(cv2.IMWRITE_PNG_COMPRESSION), 0])
    cv2.waitKey(0)
    cv2.destroyAllWindows()

在预处理工作做好之后,就可以开始切割字符了。最普通的切割算法可以总结为以下几个步骤:
对图片进行水平投影(水平投影就是对一张图片的每一行元素进行统计,往水平方向统计,根据这个统计结果画出统计结果图,进而确定每一行的起始点和结束点),找到每一行的上界限和下界限,进行行切割
对切割出来的每一行,进行垂直投影(统计每一列的元素个数),找到每一个字符的左右边界,进行单个字符的切割

常出现英语的切割效果很好,但中文效果一般。分析其原因,这其实跟中文的字体复杂度有关的,中文的字符的笔画和形态都比英文的多,更重要的是英文字母都是绝大部分都是联通体,切割起来很简单,但是汉字多存在左右结构和上下结构,很容易造成过度切割,即把一个左右偏旁的汉字切成了两份,比如上面的“则”字。
针对行字符分割,左右偏旁的字难以分割的情况,我觉得可以做以下处理:
先用通用的分割方法切割字符,得到一堆候选的切割字符集合。
统计字符集合的大多数字符的尺寸,作为标准尺寸。
根据标准尺寸选出标准的字符,切割保存。并对切割保存好的字符原位置涂成白色
对剩下下来的图片进行腐蚀,让字体粘连。
用算法再次分割,得到完整字体集合。
因为以上的思路可能只适应于纯汉字文本

一些字体较小,字体间隔较窄的情况。这类情况确实分割效果大打折扣,因为每个字体粘连过于接近,字体的波谷很难确定下来,进而造成切割字符失败。

现在解决汉字切割失败(过切割,一个字被拆成两个)的较好方法是,在OCR识别中再把它修正。比如“刺”字被分为两部分了,那么我们就直接将这两个“字”送去识别,结果当然是得到一个置信度很低的一个反馈,那么我们就将这两个部分往他们身边最近的、而且没被成功识别的部分进行合并,再将这个合并后的字送进OCR识别。

Tesseract

训练前提是我们要安装好tesseract-ocr以及附加其训练工具的版本,
win10版本可以在https://github/UB-Mannheim/tesseract/wiki/Windows-build 命令行安装或者https://github/UB-Mannheim/tesseract/wiki选择EXE类型直接安装接下来可以进入下一步,即安装jTessBoxEditorFX,注意jTessBoxEditor有两个版本,带FX的版本才支持中文字符编辑,我们下载带FX版本的。
官网:http://vietocr.sourceforge/training.html
根据系统来进行不同的安装步骤,WIN10需要的如下:
Java版本需要Java Runtime Environment 8或更高版本(安装说明)。在Windows上,还需要Microsoft Visual C ++ 2015-2019可再发行组件包。
安装之后进入https://sourceforge/projects/vietocr/files/jTessBoxEditor/下载

数据集已经准备好了,【插入数据集的描述】
现在的任务是shell脚本或python循环每次取出数据集中的一个图片(20一组)组成tif文件,全部生成完之后
再循环调用 训练命令

Tesseract 4.00版本更新包含一个基于神经网络的识别引擎即Tesseract 4的LSTM OCR引擎,同以前的版本相比在文档图像上具有更高的准确性,而且在复杂语言中实际可能比基本的Tesseract更快。
神经网络需要更多的显著的训练数据,培训有多种选择,但是除了命令行之外,训练步骤实际上几乎是相同的。
1、微调。从现有语言开始,对特定其他数据进行培训。这可能适用于与现有培训数据相近但又有细微差别的问题,例如特别不寻常的字体。甚至可以使用少量的培训数据。
2、切断网络的顶层(或任意数量的层),并使用新数据重新训练新的顶层。如果微调不起作用,则很可能是次佳选择。如果您从外观最相似的脚本开始,切掉顶层仍然可以用于训练一种全新的语言或脚本。
3、从头开始训练。除非您有非常有代表性的培训和足够大的培训来解决问题,否则这是一项艰巨的任务。如果不是这样,您可能最终会得到一个过拟合的网络,该网络在训练数据上确实非常好,但在实际数据上却没有。

Tesseract 4.00包括配置为文本行识别器的新神经网络子系统。起源于基于Python的LSTM实现,但针对C ++中的Tesseract完全重新进行设计。Tesseract中的神经网络系统早于TensorFlow,但与之兼容,是一种名为变量图规范语言(VGSL)的网络描述语言,该语言也可用于TensorFlow。VGSL的想法是可以构建神经网络并对其进行训练,而无需学习很多知识。无需学习Python,TensorFlow,甚至无需编写任何C ++代码。仅需要充分理解VGSL规范语言,即可构建语法正确的网络描述。

Tesseract 4.00版本的神经网络子系统作为行识别器集成于Tesseract中。它可以识别大型文档中的文本,也可与外部文本检测器一起结合使用,从单个文本行的图像中识别文本。
神经网络引擎是4.0版本的默认值。要从单个文本行的图像中识别文本,请使用SetPageSegMode(PSM_RAW_LINE)或在命令行中使用-psm 13

4.0集成神经网络引擎后可启用与Tesseract 3.04一起使用的多语言模式,将来的版本中将对此继续改进。目前可实现支持中文,日文和韩文的垂直文本并且自动检测到。
从3.03开始,需要下列库来构建培训工具。
sudo apt-get install libicu-dev libpango1.0-dev libcairo2-dev
已提供的现有模型数据已在约400000条文本行上进行了训练,跨越约4500种字体。对于其他脚本,可用的字体不是很多,但是仍然在相似数量的文本行上进行了训练。我们需要做的就是继续训练使其准确度更高来替换原配置文件。

训练的大致步骤:
安装jTessBoxEditor -> 获取样本文件 -> Merge样本文件 –> 生成BOX文件 -> 定义字符配置文件 -> 字符矫正 -> 执行批处理文件 -> 将生成的traineddata放入tessdata中

首先为了说明方便,先训练数字0~9为例,从网上找了5张图片图片来做一个简单的训练。
这里需要注意的是所有样本图像文件格式必须为tif\tiff格式,否则在Merge样本文件的过程中会出现 Couldn’t Seek 的错误。

实战

基于精准OCR智能识别的移动扫描工具

1、证件扫描仪(裁剪成图)
2、证件信息快速提取(便于信息填写)
3、图片转文字(一键扫描取字)
结合前端实现在线图片上传,图片识别,结果展示。

OCR(Optical Character Recognition,光学字符识别)一直是模式识别领域中重要的研究方向,所谓模式识别的问题就是用计算的方法根据样本的特征将样本划分到一定的类别中去。

OCR工具对比

经过预处理后,tesseract识别率达到100%,tesseract对清晰度不高的图片识别出现很多拒识,百度OCR-API准确率还在95%以上
结论:tesseract较多人使用,有比较多资料可查,目前由google提供支持,暂定它了,优化方向:通过训练微调、想办法提速。

Tesseract,一款由HP实验室开发由Google维护的开源OCR(Optical Character Recognition , 光学字符识别)库,目前由谷歌赞助,它可以通过训练识别出任何字体,我们可以不断的训练的库,使图像转换文本的能力不断增强;

tesseract 模型ocr基本原理:
基于深度神经网络的模型,应用循环神经网络结构,
一般的深度神经网络中xi->yi,一一对应的关系,将文字按照像素点直接展开成很长的矢量,得到字符对应的每个输出。
LSTM即循环神经网络rnn,图像竖着裁剪成特征序列,不断输出得到反馈,共同作用不断得到新的输出,获取特征变化信息及相互关系都可以学习出来,故精确的多。

基于传统OCR框架处理身份证文字识别:
身份证识别技术流程与上述框架稍微有所差异。对该问题,需要已知先验信息有证件固定的长宽、大小一致的字体、文本相对于证件位置固定、.存在固定文字。因此,处理该问题的思路为:先定位目标物体(证件),矫正后提取文字进行识别,最后进行语义纠错,如下图:
目标物体定位并矫正。基于现有的先验信息,定位最后的方法为采用模板关键点特征匹配的方法,并利用模板上特征点及目标图像特征点坐标之间的关系进行透视变换,以定位目标物体,如下图所示。接着,基于四角的坐标,进行旋转、仿射、尺寸的变换,并提取出目标物体的俯视图。
因文字位置相对固定,接着便分割出文字区域,二值化后,行列分割出单个字符。这里的技术难点在于二值化,二值化效果的好坏直接影响字符分割,并最终影响识别结果。受光照和拍摄质量的影响,全局二值化难以设置统一的阈值,而自适应二值化算法易受到阴影及模糊边界的干扰。所以在这边尝试过许多方法,测试下来未发现在任何情形下效果都满足要求的方法。

提高识别度的技巧在于图像预处理,常见的有反转(处理倒置图像)、重新缩放、二值化(但是在页面背景的暗度不均匀的情况下可能效果不好)、噪声消除(噪声是图像中亮度或颜色的随机变化,会使图像的文本更难以阅读)、在细体字符和墨水泄露进行扩张和侵蚀处理、旋转去歪斜、扫描边框去除等。

传统OCR识别框架及基于深度学习的OCR识别框架
传统文本检测:首先文本定位,接着进行倾斜文本矫正,之后分割出单字后,并对单字识别,最后基于统计模型(如隐马尔科夫链,HMM)进行语义纠错。可按处理方式划分为三个阶段:预处理阶段、识别阶段和后处理阶段。其中关键在于预处理阶段,预处理阶段的质量直接决定了最终的识别效果。
通过链接组件检测文本,利用opencv函数找到链接的文本组。大部分链接的组件是字符,也会存在二值化留下的噪声,通过设置阈值大小过滤相关文本,执行合成算法合成字符,搜索最邻近的字符组合合成单词,这种算法要求找到每个相关字母最接近的字符,从若干字母中找到最佳选择展示。文字形成文字行,通过判断文字是否高度一致判断文本是否属于同一行。
优点:性能稳定,简单复杂场景均能应用。
缺点:计算精度有限难突破,遇到大量数据效率低。行列分割提取出单字,这一步利用文字在行列间存在间隙的特征,通过二值化并在投影后找出行列分割点,当在文字与背景的区分度较好时,效果很好,而拍摄的图片中光照、摄像质量的影响,并且文字背景难以区分时,常造成错误分割的情况。

主流深度学习OCR文字识别方法:
1、Tesseract(LSTM) +Densenet
2、CTPN+CRNN
Keras pytorch两个版本的中文识别模型
3、opencv mser算法框出图片文字区域
运用tensorflow实现自然场景文字检测
Keras/pytorch实现crnn+ctc实现不定长中文OCR识别
特点:对于明暗不一、弯曲的、被遮挡的、有一定透明度的字体进行识别效果好

  • convolve 卷积:将卷积核按在输入矩阵上对应相乘再相加得到新的特征矩阵

  • padding填充:常用两种
    1)valid padding有效填充:即不进行任何处理 仅使用原图像 不允许卷积核超出原始图像边界
    2)same padding:允许卷积核超出原始图像边界并使卷积后结果大小与原来一致

  • pooling池化:降低特征维度 防止过拟合 保证原来的特性
    即采样 减少了信息给下一层 原输入高度宽度都缩小一半,通道不变
    最大池化即取里面最大值 都是2*2

  • feature 特征值

  • fully connected layers FC全连接层
    在整个卷积神经网络中起到分类器的作用,全连接层将学到的“分布式特征表示”映射到样本标记空间。
    全连接层可用卷积操作实现,根据特征组合进行分类,大大减少特征位置对分类的影响,即不管这只猫在哪 左上或右下都可以得出找到的结果
    全连接过滤器filter作用即不管这猫在哪找到就可
    实际上把feature map整合成一个值,这个值大就有猫,小可能没猫,与猫在哪里关系不大
    红色的神经元代表特征找到,被激活,其他的要么特征不明显要么没照到,最后把找到的特征组合在一起发现是猫

  • 神经网络
    可看作能够拟合任意函数的黑盒子,训练数据足够时可由x->y
    有输入层、隐层、输出层

  • 激活函数
    激活函数,不是去激活什么,而是指如何把“激活的神经元的特征”通过函数把特征保留并映射出来,即负责将神经元的输入映射到输出端。激活函数是用来加入非线性因素的,因为线性模型的表达力不够。
    非线性划分:

  • RELU激活函数:
    从图像上可得到 大于0的值才有效,故仅会激活部分神经元

-CNN卷积神经网络
最常见的卷积层+池化层可以在隐藏层中出现多次 卷积+卷积、卷积+卷积+池化组合等等

-RNN循环神经网络
输入层 X是其值 ->输入层到隐藏层的权重矩阵U->隐藏层S W是权重矩阵,其值是上一次隐藏层的值 ->隐藏层到输出层的权重矩阵V ->输出层 O是其值

  • DenseNet
    建立前面层与后面层的密集连接
    常规神经网络有L个连接,DenseNet有L(L+1)/2个连接
    输入是前面层的特征+输入
    优点
    1.减轻梯度消失
    2.加强特征传递
    3.更有效利用特征
    4.减少参数数量实现特征重用

  • LSTM 常用于NLP
    长短期记忆 long short-term memory
    是一种特殊的RNN,解决长序列训练过程中的梯度消失和梯度爆炸问题(gradient vanishing problem)(gradient exploding problem)

    • Sigmoid函数有一个缺点:当x较大或较小时,导数接近0;并且Sigmoid函数导数的最大值是0.25
      导数很容易逐渐变为0,使得权重和偏差参数无法被更新,导致神经网络无法被优化,训练永远不会收敛到良好的解决方案
    • 当我们将w初始化为一个较大的值时,例如>10的值,那么从输出层到输入层每一层都会有一个s‘(zn)*wn的增倍,当s‘(zn)为0.25时s‘(zn)*wn>2.5,同梯度消失类似,当神经网络很深时,梯度呈指数级增长,最后到输入时,梯度将会非常大,我们会得到一个非常大的权重更新,这就是梯度爆炸的问题,在循环神经网络中最为常见.

      Naive里的 h t h^t ht就是LSTM中的 c t c^t ct
      c t c^t ct变化慢,是 c t − 1 c^{t-1} ct1加上某个值得到的
      h t h^t ht变化快
      对前面的结果 x t x^t xt进行选择性忘记,选择性记忆
      有三个门,输入门、输出门、忘记门
  • VGG
    使用3×3卷积核代替7×7卷积核,使用2个3×3卷积核代替5×5卷积核,提升网络深度,卷积核通道数降维升维

  • 全连接是把所提取目标各部分的特征整理起来,最后通过激活函数分类
    是怎么样把3x3x5的输出,转换成1x4096的形式?用3×3×5的filter去卷积激活函数的输出,得到的结果是全连接神经元的输出,输出是一个值

  • CTPN
    原始CTPN只检测横向排列的文字。
    CTPN结构与Faster R-CNN基本类似但加入了LSTM层。
    作用:会把图像中的文字用绿框标注出来,即文本检测

  • DenseNet+CTC:文字识别

  • CTC (connectionist Temporal Classification)解决输入序列和输出序列难以一一对应问题。CTC是一种损失函数,衡量输入序列数据经过神经网络之后和真实的输出相差多少。

    • 应用场景 :OCR中使用RNN时,RNN的每个输出都要对应字符图像的每个位置,手工标记工作量大。
    • 优点:事先无需知道一共有几个字符需要识别。

开始项目实战吧!

unzip
unrar e xxx.rar  //当前目录
unrar X xxx.rar extract_path //解压到扩展目录
rm xx //删除文件
rm -r xxx //删除文件夹

数据集简介:
约364万张图片,按照99:1划分成训练集和验证集
利用中文语料库,包括新闻+文言文,通过字体、大小、灰度、模糊、透视、拉伸等变化随机生成
包括汉字、英文字母、数字和标点共5990个字符
每个样本固定10个字符,字符随机截取自语料库中的句子
图片分辨率统一为280×32

python虚拟环境命令

conda create -n my_ocr
conda activate my_ocr
conda deactivate
conda install (-n env_name) package  

homebrew:是 mac的包管理器,仅需执行相应的命令,就能下载安装需要的软件包,可以省掉自己去下载、解压、拖拽(安装)等繁琐的步骤。

m1芯片MacBook Pro 配置tesseract-ocr环境

/bin/bash -c "$(curl -fsSL https://cdn.jsdelivr/gh/ineo6/homebrew-install/install.sh)"
#安装成功后配置环境变量
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"
#测试是否成功并查看版本
brew -v
#看到以下说明homebrew和git都安装好了
Homebrew 3.1.3
Homebrew/homebrew-core (git revision 4ce9aa0b87; last commit 2021-04-24)
#再看下git版本
git --version
#Mac直接安装tesseract的话无法附带安装training tools
#如果已经安装了没有training tools的tesseract,请先卸载
brew uninstall tesseract
#安装相关依赖
brew install cmake
brew install wget
# Packages which are always needed.
brew install automake autoconf libtool
brew install pkgconfig
brew install icu4c
brew install leptonica
# Packages required for training tools.
brew install pango
# Optional packages for extra features.
brew install libarchive
# Optional package for builds using g++.
brew install gcc

If you need to have libarchive first in your PATH, run:
  echo 'export PATH="/opt/homebrew/opt/libarchive/bin:$PATH"' >> ~/.zshrc

For compilers to find libarchive you may need to set:
  export LDFLAGS="-L/opt/homebrew/opt/libarchive/lib"
  export CPPFLAGS="-I/opt/homebrew/opt/libarchive/include"

For pkg-config to find libarchive you may need to set:
  export PKG_CONFIG_PATH="/opt/homebrew/opt/libarchive/lib/pkgconfig"


  • 第一种方式是https://blog.csdn/henrywang8888/article/details/114911346
  • 第二种方式是安装pytesseract 暂时选择第二种去实现
    实则跑起来会发现,这俩都是要安的,不然会报错
#如果不需要训练的可以直接按下面安装
brew install tesseract
#This formula contains only the "eng", "osd", and "snum" 
#不过osd/snum是什么语言 没有查到
#language data files.If you need any other supported languages, run `brew install tesseract-lang`.
#想识别中文 还要安装-lang版本 命令如上

#安装python 会默认从官网安装最新合适版本
brew install python3
#prompt
Python has been installed as
  /opt/homebrew/bin/python3

Unversioned symlinks `python`, `python-config`, `pip` etc. pointing to
`python3`, `python3-config`, `pip3` etc., respectively, have been installed into
  /opt/homebrew/opt/python@3.9/libexec/bin

You can install Python packages with
  pip3 install <package>
They will install into the site-package directory
  /opt/homebrew/lib/python3.9/site-packages

tkinter is no longer included with this formula, but it is available separately:
  brew install python-tk@3.9
#tkinter这个gui模块得单独安装了 后面用到再说
See: https://docs.brew.sh/Homebrew-and-Python
#continue
pip install pytesseract
#安装好3.9发现没有pip 因为根本不匹配,故安装anaconda的精简版miniforge
brew install miniforge
#报错
Error: Failure while executing; `git clone https://github.com/Homebrew/homebrew-cask /opt/homebrew/Library/Taps/homebrew/homebrew-cask --origin=origin` exited with 128.
#查错
brew doctor
#原来是源地址不对
git -C "/opt/homebrew" remote set-url origin https://github.com/Homebrew/brew
git -C "/opt/homebrew/Library/Taps/homebrew/homebrew-core" remote set-url origin https://github.com/Homebrew/homebrew-core
#不管用则进以下链接
https://codechina.csdn.net/mirrors/conda-forge/miniforge?utm_source=csdn_github_accelerator
#下载silicon对应的版本下载
#下载完成后打开对应终端,输入
bash Miniforge3-MacOSX-arm64.sh
#一路yes即可 记住一定要重启终端!!!不然会显示你没安装
conda --version
conda create --name my_ocr
#下次激活时就用conda activate my_ocr
conda install -y python==3.8.6
conda install -y pandas matplotlib scikit-learn jupyterlab
conda install pytesseract
#Python图像库PIL(Python Image Library)以及numpy也要安装
#报错的可以试下
conda update -n base conda
conda install jupyter notebook
jupyter notebook
#m1芯片 暂时不可用pip/conda install opencv-python来安装
可以用conda forge来安装(也就是之前安装的那个miniforge)
https://anaconda.org/conda-forge/py-opencv
conda install -c conda-forge py-opencv

pwd:print working directory
/Users/yourname/Downloads/my_ocr

m1芯片下想要安装tesseract并且带训练工具
brew install --with-training-tools tesseract已经不可用了,替代使用docker。
Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从 Apache2.0 协议开源。Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。
https://docs.docker/docker-for-mac/apple-silicon/
安装请看
https://blog.csdn/weixin_42219336/article/details/107718269
启动
docker run -dp 3000:3000 tesseractshadow/tesseract4re
然而报错了
WARNING: The requested image’s platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested
ed6f864cdfb9b06996b7d78dfbe5733648efaa405e15803d1e06ac781915d7a0
不匹配只能另作它法。

#安装训练工具和环境
docker pull jitesoft/tesseract-ocr #已废弃
#进入https://github/tesseract-ocr/tesseract/releases
#选择支持m1芯片的5.0.0-alpha-20201224 我选择的是zip方便解压 也可以选另一个tar.gz的
#根据官方文档按步骤来https://tesseract-ocr.github.io/tessdoc/Compiling-–-GitInstallation.html
cd tesseract
./autogen.sh
./configure 
#m1用这句替代上句 ./configure CXX="g++ --target=arm-apple-darwin64"
make
sudo make install
sudo ldconfig
make training
sudo make training-install

brew tap homebrew/cask-versions
brew cask install java8
brew tap AdoptOpenJDK/openjdk
brew install homebrew/cask-versions/adoptopenjdk8
#然而一切戛然而止在我用port search icu可以找到而不能安装,./configure却要求我必须安装>51.1版本的这一刻
#最终选择用Window 10来安装训练工具并进行训练 
#用macos big sur即m1芯片 arm64架构做图片预处理/测试及计算错误率/前端部分。
#扩展:连接服务器命令 首先进入终端
ssh your_username@ip_path
type in your_password
enter in...

#插入小插曲:apple silicon m1芯片安装tensorflow
#首先还是之前的conda环境配好 即miniforge/conda-forge
#网上的真是一个也不能用
 docker pull tensorflow/tensorflow:latest  # Download latest stable image
 docker run -it -p 8888:8888 tensorflow/tensorflow:latest-jupyter  # Start Jupyter server 

数据集的准备


Synthetic Chinese String Dataset
用tesseract可以识别的图片中字体,主要有以下一些特点:
使用一个标准字体
可以使用复印或者拍照,但是必须字体要清晰,没有痕迹
图片里没有歪歪斜斜的字体
另外没有超出图片中的字体,也没有残缺的字体
https://github/xiaohongxiao/OCR_DataSet

#功能基本完成,开始做前端界面
#采用pyqt6
brew install pyqt6
#弹出提示
'''
==> dbus
To have launchd start dbus now and restart at login:
  brew services start dbus
'''
brew services start dbus
#dbus是进程间通信工具
brew list qt #查看安装包路径
#发现访达中找不到该配置文件夹 使用以下命令
defaults write com.apple.finder AppleShowAllFiles -boolean true ; killall Finder
'''
mac下是没有pyqt-tools的,需要是因为在mac下Qt Designer需要去Qt里面下载。用国内源https://www.pianshen/article/82671945359/
下载安装后,即可拥有qt-creator及designer,我们只需要使用designer即可
打开designer后发现节目是个个窗体独立的,在偏好设置-》用户界面模式-》锚链的窗口。解决 
'''