OpenCV-Python实现简单数字识别OCR

我们知道,OpenCV是Python中用来处理图像识别的一个很强大的库。问题从简单的入手,在OpenCV-Python(cv2)中实现“数字识别OCR”。非产品级别的解决方案,主要为了学习。我们希望在OpenCV中学习KNearest和SVM功能。

假设我们有每个数字的100个样本(即图像)。并一起训练OpenCV示例附带的示例letter_recog.py。我们着手弄清楚下列问题:
1)什么是letter_recognition.data文件?如何从自己的数据集构建该文件?
2)results.reval()表示什么?
3)如何使用letter_recognition.data文件(KNearest或SVM)编写一个简单的数字识别工具?

解决方法如下:

想要的是在OpenCV中使用KNearest或SVM功能来实现一个简单的OCR。办法应该如下 (只是为了学习如何使用KNearest进行简单的OCR目的):
1)我的第一个问题是关于openCV示例附带的letter_recognition.data文件。我想知道那个文件里面是什么。
它包含一封信,以及该信的16个特征。
this SOF帮助我找到它。这些16个功能在文档Letter Recognition Using Holland-Style Adaptive Classifiers中进行了说明。
(虽然我不了解一些结尾的功能)
2)由于我知道,在不了解所有这些功能的情况下,很难做到这一点。我尝试了一些其他的论文,但对初学者来说,这些都是有点困难的。
So I just decided to take all the pixel values as my features.(我并不担心准确性或表现,我只是想让它工作,至少在最不准确的情况下)
我拍下了我的训练资料:
enter image description here
(我知道培训数据少了,但是由于所有的字母和字体大小相同,所以我决定尝试这样做)。
为了准备训练数据,我在OpenCV中编写了一个小代码。它做以下事情:
A)它加载图像。
B)选择数字(显然通过轮廓查找和对字母的面积和高度应用约束以避免错误检测)。
C)绘制围绕一个字母的边界矩形,并等待key press manually。这一次我们自己按数字键对应的字母在框中。
D)按下相应的数字键后,将此框重新调整为10×10,并将数组中的100个像素值(这里为样本)和相应的手动输入的数位保存在另一个数组中(这里为响应)。
E)然后将这两个数组保存在单独的txt文件中。
在数字手动分类结束时,列车数据(train.png)中的所有数字都由我们自己手动标记,图像如下图所示:
enter image description here
以下是我用于上述目的的代码(当然不是那么干净):

import sys

import numpy as np
import cv2

im = cv2.imread('pitrain.png')
im3 = im.copy()

gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray,(5,5),0)
thresh = cv2.adaptiveThreshold(blur,255,1,1,11,2)

#################      Now finding Contours         ###################

contours,hierarchy = cv2.findContours(thresh,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)

samples =  np.empty((0,100))
responses = []
keys = [i for i in range(48,58)]

for cnt in contours:
    if cv2.contourArea(cnt)>50:
        [x,y,w,h] = cv2.boundingRect(cnt)

        if  h>28:
            cv2.rectangle(im,(x,y),(x+w,y+h),(0,0,255),2)
            roi = thresh[y:y+h,x:x+w]
            roismall = cv2.resize(roi,(10,10))
            cv2.imshow('norm',im)
            key = cv2.waitKey(0)

            if key == 27:  # (escape to quit)
                sys.exit()
            elif key in keys:
                responses.append(int(chr(key)))
                sample = roismall.reshape((1,100))
                samples = np.append(samples,sample,0)

responses = np.array(responses,np.float32)
responses = responses.reshape((responses.size,1))
print "training complete"

np.savetxt('generalsamples.data',samples)
np.savetxt('generalresponses.data',responses)

现在我们进入培训和测试部分。
对于我测试的部分,我使用下面的图像,它有相同类型的字母,我用来训练。
enter image description here
对于培训,我们做如下
A)加载我们之前已经保存的txt文件
B)创建一个我们正在使用的分类器的实例(这里是KNearest)
C)然后我们使用KNearest.train函数来训练数据
为了测试目的,我们做如下:
A)我们加载用于测试的图像
B)如前所述处理图像,并使用轮廓方法提取每个数字
C)为其绘制边框,然后调整为10×10,并将其像素值存储在数组中,如前所述。
D)然后我们使用KNearest.find_nearest()函数来找到我们给出的最接近的项目。 (如果幸运,它会识别正确的数字。)
我在下面的单一代码中包括了最后两个步骤(培训和测试):

import cv2
import numpy as np

#######   training part    ############### 
samples = np.loadtxt('generalsamples.data',np.float32)
responses = np.loadtxt('generalresponses.data',np.float32)
responses = responses.reshape((responses.size,1))

model = cv2.KNearest()
model.train(samples,responses)

############################# testing part  #########################

im = cv2.imread('pi.png')
out = np.zeros(im.shape,np.uint8)
gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
thresh = cv2.adaptiveThreshold(gray,255,1,1,11,2)

contours,hierarchy = cv2.findContours(thresh,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)

for cnt in contours:
    if cv2.contourArea(cnt)>50:
        [x,y,w,h] = cv2.boundingRect(cnt)
        if  h>28:
            cv2.rectangle(im,(x,y),(x+w,y+h),(0,255,0),2)
            roi = thresh[y:y+h,x:x+w]
            roismall = cv2.resize(roi,(10,10))
            roismall = roismall.reshape((1,100))
            roismall = np.float32(roismall)
            retval, results, neigh_resp, dists = model.find_nearest(roismall, k = 1)
            string = str(int((results[0][0])))
            cv2.putText(out,string,(x,y+h),0,1,(0,255,0))

cv2.imshow('im',im)
cv2.imshow('out',out)
cv2.waitKey(0)

下面是我得到的结果:
enter image description here
这里以100%的精度工作。我假设这是因为所有的数字都是相同的和相同的大小。
但是无论如何,这对初学者来说是一个很好的开始。

 

希望有用。

By Codewenda.com

Matlab和Octave的矩阵操作详解

在学习机器学习的时候,我们会用到很多的矩阵运算,这篇文章介绍Matlab/Octave中矩阵的基本操作方法,例如如何创建向量、矩阵,如果对矩阵进行基本的加减乘除等数值运算,如何读取矩阵的数据,如何获取矩阵的子矩阵,以及矩阵相关的内置函数的用法等。

1 创建向量和矩阵

1.1 创建向量

以下是我们如何在Octave中指定行向量:

> x = [1,3,2] 
x = 

  1 3 2

向量包含在方括号中;
每个条目由逗号或者空格分隔。x = [1 3 2]和 x = [1,2,3]两者皆可。

要创建列向量,则用分号替换逗号:

> x = [1; 3; 2] 
x = 

  1 
  3 
  2

1.2 创建矩阵

由上可知,创建一个矩阵的方法如下:

> A = [1,1,2; 3,5,8; 13,21,34] 
A = 

   1 1 2 
   3 5 8 
  13 21 34

2 矩阵运算的操作符

2.1 矩阵的运算符

矩阵支持以下的操作符:

加(+),
减(-)和
乘(*)
注意矩阵乘法行列需要满足的条件:A*B,那么A的列数需要和B的行数相同(m*k, k*n, 相乘得到m*n的矩阵)

转置运算符是单引号:’。要继续上一节中的例子,

> A'
ans = 

   1 3 13 
   1 5 21 
   2 8 34

(注意:这实际上是复共轭转置算子,但对于实数矩阵,这与转置相同。要计算复矩阵的转置,请使用点转置(.’)运算符。)

幂运算(^)可用于计算矩阵的幂,A^3即A*A*A.

2.2 矩阵与矩阵的元素操作

两个同型矩阵(行数列数都相同),可以对它们执行逐个元素的操作。

> A = [1,6,3; 2,7,4] 
A = 

  1 6 3 
  2 7 4 

> B = [2,7,2; 
  7,3,9 ] 
B = 

  2 7 2 
  7 3 9 

> A ./ B 
ans = 
0.50000 0.85714 1.50000 
  0.28571 2.33333 0.44444

注意使用点除(./)运算符来进行元素的除法。对于乘法(.*)和点幂(.^)也同样如此,对应元素分别相乘,取幂。

2.3 矩阵与单个数值的运算

下面来看一个标量与矩阵的运算,上述的.*, ./, .^同样可以用于标量和矩阵的运算:
一下X中的每一个元素都被a除。

>a = 12;
>X = [1, 2; 3, 4]
>a ./ X
ans = 
    12 6
	4  3

3 矩阵元素索引

3.1 读取矩阵的单个元素

可以通过索引来读取或操作矩阵和向量的部分。

> x = [ 
  1.2,5,7.6,3,8 ] 
x = 
1.2000 5.0000 7.6000 3.0000 8.0000

如果需要读取X的第二个元素:

> x(2)
ans = 5
> x(2, 1)
ans = 5

这里A(i,j)读取第i行第j列的元素。

3.2 读取多个元素

还可以按以下方式查看元素列表。

> x([1,3,4])
ans = 

  1.2000 7.6000 3.0000

显示向量x的第1,第3和第4个元素。

那么如何选择矩阵的行和列呢?和上面的方法类似,先定义一个矩阵

> A = [1,2,3; 4,5,6; 7,8,9] 
A = 

  1 2 3 
  4 5 6 
  7 8 9

并选择第1和第3行,第2和第3列:

> A([1,3],[2,3])
ans = 

  2 3 
  8 9

3.3 冒号运算符读取元素

冒号运算符(: )可用于从矩阵中选择所有行或列。因此,要选择第二行中的所有元素,请键入

> A(2,: ) 
ans = 

  4 5 6

也可以使用:来选择矩阵所有的元素:

> A(:, : ) 
ans = 
 
  1 2 3 
  4 5 6 
  7 8 9

3.4 冒号运算符的补充

这里在进一步来看一下冒号运算符来获取矩阵的元素,A(:,: )使用两个冒号,可以使用四个参数:

A(row_start:row_end, column_start:column_end)

以上,row_start不能超过row_end, column_start不能超过column_end,否则返回空矩阵–行数或列数为0。
如果冒号运算符两边没有参数,那么就是选择整个行或者列的范围,这也就是为什么A(:,: )返回A的所有行和列。

那么我们看A(1:2, 3)表示什么呢?这里列的范围参数省略了一个,
A(1:2, 3) == A(1:2, 3:3)

A(3, 1:2) == A(3:3, 1:2)

那么A(1:2:3)又表示什么意思呢?我们先看下一小节。

这里和上面有所不同了,我们可以回顾一下1:2:3,实际上表示的是一个行向量[1 3],它的形式是start:step:end
所以这里A(1:2:3)表示A([1, 3])

4 Range的用法

4.1 定义一个范围range

我们还可以从矩阵中选择一行或多列。我们指定一个范围

start:step:end
可以在Octave提示符下实际输入范围来查看结果。例如,

> 1:3:10 
ans = 

   1 4 7 10

第一个数字是start第二start + step个,第二个是第三个start + (2 * step)。最后一个数字小于或等于stop。

通常,您只需要步长为1.在这种情况下,您可以省略步骤参数并键入

> 1:10 
ans = 

   1 2 3 4 5 6 7 8 9 10

4.2 用range来选取矩阵的元素

范围命令的结果只是一个整数的向量。我们现在可以使用它来索引到一个向量或矩阵。选择一个2*2的子向量。

> A(1:2,1:2)
ans = 

  1 2 
  4 5

最后,有一个关键字称为end可以在索引到矩阵或向量时使用。它是指行或列中的最后一个元素。例如,要查看矩阵中的最后一列,可以使用

> A(:,end)
ans = 

  3 
  6 
  9

所以这里再回答上小节的那个问题。
那么A(1:2:3)又表示什么意思呢?
这里和上面有所不同了,我们可以回顾一下1:2:3,实际上表示的是一个行向量[1 3],它的形式是start:step:end
所以这里A(1:2:3)表示A[1, 3]

矩阵操作的相关函数

5.1 创建矩阵的函数

tril(A)返回A的下三角形部分。
triu(A)返回A的上三角形部分。
eye(n) 返回n阶单位矩阵。也可以使用eye(m, n)返回m*n的单位矩阵,不过它不再是方阵了。
ones(m, n) 返回m行n列的矩阵,每个元素都为1。同样,ones(n)返回元素都为1的n阶方阵。
zeros(m, n) 返回m行n列的矩阵,每个元素都为0。同样,zeros(n)返回全0的n阶方阵。
rand(m, n) 返回m行n列的矩阵,所有元素为一个[0,1)范围的随机数。同样地,rand(n)返回元素为随机数的n阶方阵。
randn(m, n) 返回m行n列的矩阵,其每一个元素为正态分布的随机元素。
randperm(n) 返回包含数字的随机排列的行向量,元素为1到n的一个全排列。
diag(x)diag(A),对于矢量x,这返回一个矩形矩阵,对角线上的元素x和其他地方的0。对于矩阵A,返回一个包含对角元素A的列向量。例如,

5.2 示例代码

> A = [1,2,3; 4,5,6; 7,8,9] 
A = 

  1 2 3 
  4 5 6 
  7 8 9 

> x = diag(A)
ans = 

  1 
  5 
  9 

> diag(x)
ans = 

  1 0 0 
  0 5 0 
  0 0 9

linspace(a, b, n)返回具有n个值的行向量,使得第一个元素等于a,最后一个元素等于b,并且连续元素之间的差是恒定的。最后一个参数n是可选的,默认值为100。

> linspace(2,4,2 )
ans = 

  2 4 

> linspace(2,4,4 )
ans = 
2.0000 2.6667 3.3333 4.0000 

> linspace(2,4,6)
ans = 

  2.0000 2.4000 2.8000 3.2000 3.6000 4.0000

logspace(a, b,n)返回具有n个值的向量,使得第一个元素等于10^a,最后一个元素相等10^b,并且连续元素之间的比率是恒定的。最后一个参数,n是可选的,默认值为50。

> logspace(2,4,2)
ans =

100 10000

By Code问答