用Python编写自己的OCR程序

27

我还是个新手,但是我想写一个字符识别程序。这个程序还没有准备好。我做了很多修改,因此注释可能不完全匹配。我将使用8连通性进行连通组件标记。

from PIL import Image
import numpy as np

im = Image.open("D:\\Python26\\PYTHON-PROGRAMME\\bild_schrift.jpg")

w,h = im.size
w = int(w)
h = int(h)

#2D-Array for area
area = []
for x in range(w):
    area.append([])
    for y in range(h):
        area[x].append(2) #number 0 is white, number 1 is black

#2D-Array for letter
letter = []
for x in range(50):
    letter.append([])
    for y in range(50):
        letter[x].append(0)

#2D-Array for label
label = []
for x in range(50):
    label.append([])
    for y in range(50):
        label[x].append(0)

#image to number conversion
pix = im.load()
threshold = 200
for x in range(w):
    for y in range(h):
        aaa = pix[x, y]
        bbb = aaa[0] + aaa[1] + aaa[2] #total value
        if bbb<=threshold:
            area[x][y] = 1
        if bbb>threshold:
            area[x][y] = 0
np.set_printoptions(threshold='nan', linewidth=10)

#matrix transponation
ccc = np.array(area) 
area = ccc.T #better solution?

#find all black pixel and set temporary label numbers
i=1
for x in range(40): # width (later)
    for y in range(40): # heigth (later)
        if area[x][y]==1:
            letter[x][y]=1
            label[x][y]=i
            i += 1

#connected components labeling
for x in range(40): # width (later)
    for y in range(40): # heigth (later)
        if area[x][y]==1:
            label[x][y]=i
            #if pixel has neighbour:
            if area[x][y+1]==1:
                #pixel and neighbour get the lowest label             
                pass # tomorrows work
            if area[x+1][y]==1:
                #pixel and neighbour get the lowest label             
                pass # tomorrows work            
            #should i also compare pixel and left neighbour?

#find width of the letter
#find height of the letter
#find the middle of the letter
#middle = [width/2][height/2] #?
#divide letter into 30 parts --> 5 x 6 array

#model letter
#letter A-Z, a-z, 0-9 (maybe more)

#compare each of the 30 parts of the letter with all model letters
#make a weighting

#print(letter)

im.save("D:\\Python26\\PYTHON-PROGRAMME\\bild2.jpg")
print('done')

1
如果你的代码是黑白的,那么一切都很好。但是,如果有些字母/单词是灰色的呢?你需要像Gimp的“根据阈值选择颜色区域”操作一样的东西。我个人会先计算暗度分布-图像的平均暗度+标准差。然后我会从一个“白色”点开始,继续选择白色,直到我确定了非白色的岛屿-这些是潜在的字母。顺便说一句,你不需要随机性-广度优先搜索可以帮助你定位所有黑色像素...关键是要找到这些岛屿。 - Hamish Grubijan
1
我的天真做法是:a)找到一个岛屿,b)将其包围,c)记住它在测试中的原始位置,d)从图像中删除它(将剩余区域涂成白色),并将其附加到要处理的小图像列表中...这是一种开始的方式。我个人会阅读现有方法,因为线性代数和统计学等可能为您提供非常强大的工具。 - Hamish Grubijan
我认为这个任务很困难,但它非常实验性,当我编写这个程序时,我可以学到很多东西。目前我只处理黑白图片。我已经使用了一个阈值。-- 我的新想法:1)只观察水平线(也许每8行),然后检查是否有黑色像素。2)从一个字母像素到下一个周围像素并检查它们。3)如果有黑色像素,请再次检查新邻居。(这种方式不是随机的) - kame
1
对啊... 你刚刚描述的是广度优先搜索。查一下吧。我推荐使用 BFS,因为你可以在扫描 N 个像素后停止而不会让图像看起来像一堆意大利面条(虽然这并不是很重要) - 因为这对于一封信来说太大了。 - Hamish Grubijan
1
理论上,深度优先搜索(DFS)和广度优先搜索(BFS)应该计算相同的内容。但在这种情况下,我更喜欢BFS,因为它还可以为您计算级别 - 可以帮助您“剥洋葱”。 - Hamish Grubijan
显示剩余3条评论
4个回答

34

OCR确实不是一项容易的任务。这就是为什么文本CAPTCHA仍然有效的原因 :)

仅讨论字母提取而非模式识别,你正在使用的技术称为连通组件标记。由于你要求更高效的方法来完成此任务,建议尝试实现本文中描述的两遍扫描算法。另一个描述可以在文章Blob extraction中找到。

编辑:这里是我建议的算法实现:

import sys
from PIL import Image, ImageDraw

class Region():
    def __init__(self, x, y):
        self._pixels = [(x, y)]
        self._min_x = x
        self._max_x = x
        self._min_y = y
        self._max_y = y

    def add(self, x, y):
        self._pixels.append((x, y))
        self._min_x = min(self._min_x, x)
        self._max_x = max(self._max_x, x)
        self._min_y = min(self._min_y, y)
        self._max_y = max(self._max_y, y)

    def box(self):
        return [(self._min_x, self._min_y), (self._max_x, self._max_y)]

def find_regions(im):
    width, height  = im.size
    regions = {}
    pixel_region = [[0 for y in range(height)] for x in range(width)]
    equivalences = {}
    n_regions = 0
    #first pass. find regions.
    for x in xrange(width):
        for y in xrange(height):
            #look for a black pixel
            if im.getpixel((x, y)) == (0, 0, 0, 255): #BLACK
                # get the region number from north or west
                # or create new region
                region_n = pixel_region[x-1][y] if x > 0 else 0
                region_w = pixel_region[x][y-1] if y > 0 else 0

                max_region = max(region_n, region_w)

                if max_region > 0:
                    #a neighbour already has a region
                    #new region is the smallest > 0
                    new_region = min(filter(lambda i: i > 0, (region_n, region_w)))
                    #update equivalences
                    if max_region > new_region:
                        if max_region in equivalences:
                            equivalences[max_region].add(new_region)
                        else:
                            equivalences[max_region] = set((new_region, ))
                else:
                    n_regions += 1
                    new_region = n_regions

                pixel_region[x][y] = new_region

    #Scan image again, assigning all equivalent regions the same region value.
    for x in xrange(width):
        for y in xrange(height):
                r = pixel_region[x][y]
                if r > 0:
                    while r in equivalences:
                        r = min(equivalences[r])

                    if not r in regions:
                        regions[r] = Region(x, y)
                    else:
                        regions[r].add(x, y)

    return list(regions.itervalues())

def main():
    im = Image.open(r"c:\users\personal\py\ocr\test.png")
    regions = find_regions(im)
    draw = ImageDraw.Draw(im)
    for r in regions:
        draw.rectangle(r.box(), outline=(255, 0, 0))
    del draw 
    #im.show()
    output = file("output.png", "wb")
    im.save(output)
    output.close()

if __name__ == "__main__":
    main()

虽然不是100%完美,但由于您只是为了学习目的而这样做,它可能是一个很好的起点。现在,您可以使用每个字符的边界框,如其他人在此处建议的那样,使用神经网络。


你好jbochi。在你写信给我之前,我已经有了连通组件标记的想法。我会在我的新版本中使用它。 - kame
1
但为什么要考虑北和西像素(在考虑4连通性时),而不是南和西像素?我从左上角开始,从左到右移动。 - kame
这篇文章介绍了一种有趣的“雨算法”,可以将彼此靠近的字母分开。 http://www.criticalsecurity.net/index.php/topic/29921-silly-captcha-tricks-are-for-kids/ - nate c
@jbochi:请就这个问题提出您的建议。 - Emil
+1,谢谢你教我新东西,这帮助我入门了 :) - Benjamin Gruenbaum
显示剩余4条评论

7
OCR非常困难。即使是计算机生成的字符,如果您不提前知道字体和字号,它也很具有挑战性。即使您完全匹配字符,我也不会称其为“初学者”编程项目;它非常微妙。
如果您想识别扫描或手写字符,那就更难了——您需要使用高级数学、算法和机器学习。关于这个主题已经有相当多的书籍和成千上万篇文章,因此您不需要重新发明轮子。
我钦佩您的努力,但我认为您还没有遇到任何实际困难。到目前为止,您只是随机探索像素并将其从一个数组复制到另一个数组。您还没有进行任何比较,而且我不确定您“随机行走”的目的是什么。
  • 为什么是随机的?编写正确的随机化算法非常困难。我建议您先从确定性算法开始。
  • 为什么要从一个数组复制到另一个数组?为什么不直接进行比较?
当您进行比较时,您将不得不处理图像与“原型”不完全相同的事实,而且不清楚您将如何处理这一点。
基于您目前编写的代码,我有一个建议:尝试编写一个在图像中“迷宫”寻路的程序。输入是图像,以及起始像素和目标像素。输出是从起点到终点的迷宫路径。这比OCR要容易得多——解决迷宫问题是计算机擅长的事情——但仍然很有趣和具有挑战性。

你好dmazzoni。在新版本中,我不再使用随机性。现在我将使用DFS或BFS。/从一个数组复制到另一个数组?因为我想将字母与模型字母进行比较。/我没有说我想如何进行比较,但我有一个计划;)迷宫的事情也很有趣,但我会尽管有警告,用OCR来完成它。 :) - kame

5
大多数OCR算法现在都基于神经网络算法。霍普菲尔德网络是一个不错的起点。基于可在C中找到的Hopfield模型,我使用Python构建了一个非常基本的图像识别算法,类似于您描述的内容。我已经发布了完整的源代码在这里。这只是一个玩具项目,不适合进行真正的OCR,但可以帮助您朝着正确的方向开始。

霍普菲尔德模型用作自联想记忆以存储和恢复一组位图图像。通过计算相应的权重矩阵来存储图像。从任意配置开始,记忆将定格在与起始配置相近的汉明距离最近的存储图像上。因此,给定存储图像的不完整或损坏版本时,网络能够回忆起相应的原始图像。

可以在这里找到一个用于玩具的Java小程序,它是使用数字0-9的示例输入来训练网络的。在右边的框中画图,点击测试并查看从网络中得出的结果。
不要被数学符号吓倒,一旦你看到源代码,算法就很简单。

我对链接的混乱的Python代码感到更加害怕,而不是数学符号。如果您打算将其作为答案的一部分,请允许我建议您对其进行清理。 - Zoran Pavlovic

4

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接