python+OpenCV笔记(三十八):识别手写数字——基于人工神经网络ANN

目录

一、简单理解人工神经网络

理解神经元和感知器

理解神经网络的层

二、基于人工神经网络识别手写数字

流程

一、(训练模块)——digits_ann.py

二、(测试模块)——digits_ann.py

三、(主模块)——detect_and_classify_digits.py

三、全部代码

1.digits_ann.py

2.detect_and_classify_digits.py

四、保存与加载分类器


一、简单理解人工神经网络

        (理解人工神经网络可跳过此节)

        人工神经网络是一个统计模型。统计模型是一对元素,即空间S(一组观测数据)和概率P,其中P是近似于S的一个分布(或者说是一个函数,会产生一系列和S非常相似的观测结果)。
        因此,人工神经网络是将复杂的现实问题进行简化,并推导出函数的一种模型,使推导函数以数学形式(近似地)表示我们期望从那个现实问题中得到的统计观测结果。

        神经网络的一个定义特征是神经元必须能够近似非线性函数。

理解神经元和感知器

        通常,为了解决分类问题,会将人工神经网络设计成多层感知器,其中每个神经元作为一种称为感知器的二值分类器。

        简单地说,感知器是一个函数,它接受多个输入并产生单个值。每个输入都有一个相关的权重,表示该输入在激活函数中的重要性。激活函数应具有非线性响应,例如常见的sigmoid函数(有时也称为s曲线),阈值函数(称为判别函数)被应用到激活函数的输出,将其转换成0或1的二值分类,如图:

python+OpenCV笔记(三十八):识别手写数字——基于人工神经网络ANN_第1张图片


输入权重代表什么,如何确定这些权重?
神经元之间是相互连接的,因为一个神经元的输出可以成为许多其他神经元的输入。
输入权重定义了两个神经元之间的连接强度。这些权重是自适应的,这意味着权重会根据学习算法随时间而变化。
由于神经元的互联性,网络有多层。现在,我们来看这些层通常是如何组织的。

理解神经网络的层

         神经网络中至少有3个不同的层:输入层、隐藏层和输出层。

        虽然可以有多个隐藏层,但是,往往一个隐藏层就足以解决许多现实生活中的问题。有时将拥有多个隐藏层的神经网络称为深度神经网络(DNN)。如图:

python+OpenCV笔记(三十八):识别手写数字——基于人工神经网络ANN_第2张图片

 输入层:

        输入层的节点数量就是网络的输入数量。例如:如果选择根据重量、长度和牙齿数量对动物进行分类,那么输入集包含3个属性,因此网络需要包含3个输入节点。

输出层:

        对于分类器来说,输出层节点的数量定义为网络能够区分的类的数量。例如:如果我们知道要分类的动物有:狗、秃鹰、海豚和龙,那么可以使用有4个节点的输出层。

隐藏层:

        对于隐藏层大小的选择,并没有统一的经验法则,必须在实验的基础上进行选择。对于想要应用人工神经网络的每个实际问题,都需要对人工神经网络进行训练、测试、再训练,直到找到可以获得可接受准确率的隐藏节点。

什么是过拟合?

        为得到一致假设而使假设变得过度复杂称为过拟合。
        比如:某种学习算法产生了一个过拟合的分类器,这个分类器能够百分之百的正确分类样本数据,即再拿样本中的文档来给它,它绝对不会分错,但也就为了能够对样本完全正确的分类,使得它的构造如此精细复杂,规则如此严格,以至于任何与样本数据稍有不同的文档它全都认为不属于这个类别。
        在这里指的是,与训练数据实际提供的信息相比,隐含层中包含的伪信息过多时,就会发生过拟合,导致分类不是很有意义。

二、基于人工神经网络识别手写数字

流程

一、(训练模块)——digits_ann.py

                                ——请先创建该脚本文件

  1. 获取数据
    可在MNIST handwritten digit database, Yann LeCun, Corinna Cortes and Chris Burges上公开获取MNIST数据库。数据库具有 60,000 个示例的训练集和 10,000 个示例的测试集。数字已经过尺寸标准化,并在固定尺寸的图像中居中。且所有的训练和测试图像都是灰度格式,尺寸为28*28像素,下载页面如图:
    python+OpenCV笔记(三十八):识别手写数字——基于人工神经网络ANN_第3张图片
    (注意:接下来的脚本中,下载的该文件会无法使用,所以我们使用与其相同的其他文件,下载地址GitHub(文件名:mnist.pkl.gz):https://github.com/PacktPublishing/Learning-OpenCV-4-Computer-Vision-with-Python-Third-Edition/tree/master/chapter10/digits_datahttps://github.com/PacktPublishing/Learning-OpenCV-4-Computer-Vision-with-Python-Third-Edition/tree/master/chapter10/digits_data
  2. 选择训练参数
    每个MNIST样本都是一个包含784像素(即28×28像素)的图像。因此,我们的人工神经网络输入层将有784个节点。输出层将有10个节点,因为有10类数字(0到9)。
    我们可以自由选择其他参数的值,如隐含层中的节点数、要使用的训练样本数、训练阶段数等。通常,实验可以帮助我们找到提供可接受训练时间和准确率的参数值,而不会于训练数据上将模型过拟合。基于本书作者所做的一些实验,我们将使用60个隐藏节点、50000个训练样本、10个阶段。这些参数足以进行初步测试,将训练时间控制在几分钟之内(取决于计算机的处理能力)。
  3. 从Python库中导入gzip和pickle模块,以及 OpenCV 和 NumPy
    import gzip
    import pickle
    
    import cv2
    import numpy as np

    我们将使用gzip和pickle模块从下载的文件中加载并解压MNIST数据。
    该文件以嵌套元组的方式包含MNIST数据,格式如下:
    ((training_images, training_ids),
    (test_images, test_ids))

    这些元组的元素依次采用以下格式:
    (1)training-images是一个NumPy数组,由60000幅图像组成,其中每幅图像都是由784个像素值组成的向量(从28×28像素的原始形状展平而来)。像素值是0.0(黑色)到1.0(白色)范围内(含边界值)的浮点数。
    (2)training_ids是一个NumPy数组,包含60000个数字ID,其中每个ID是范围为0到9(包括边界值)的一个数字。training_ids[i]对应于training_images[i]。
    (3)test_images是由10 000幅图像组成的NumPy数组,其中每幅图像都是由784个像素值组成的向量(从原始形状28×28像素展平而来)。像素值是0.0(黑色)到1.0(白色)范围内(含边界值)的浮点数。
    (4)test_ids是一个NumPy数组,包含10000个数字ID,其中每个ID都是范围为0到9(包括边界值)的一个数字。test_ids[i]对应于test_images[i]。

  4. 编写以下辅助函数,加载并解压已下载文件的内容

    def load_data():
        mnist = gzip.open('mnist.pkl.gz', 'rb')
        training_data, test_data = pickle.load(mnist)
        mnist.close()
        return (training_data, test_data)

    注意,在前面的代码中,training_data是一个元组,相当于(training_images,training_ids),而且test_data也是一个元组,相当于(test_images,test_ids)

  5. 我们必须重新格式化原始数据,以匹配OpenCV所期望的格式。具体来说,当提供样本输出来训练人工神经网络时,它必须是一个有10个元素(对于10类数字)的向量,而不是单个数字ID。为了方便起见,我们还将应用Python内置的zip函数来重新组织数据,这样就可以以元组的形式遍历输入和输出向量的匹配对。我们编写以下辅助函数,重新格式化数据:

    def wrap_data():
        tr_d, te_d = load_data()
        training_inputs = tr_d[0]
        training_results = [vectorized_result(y) for y in tr_d[1]]
        training_data = zip(training_inputs, training_results)
        test_data = zip(te_d[0], te_d[1])
        return (training_data, test_data)

    其中,vectorized_result()函数将ID转换为一个分类向量,如下:

    def vectorized_result(j):
        e = np.zeros((10,), np.float32)
        e[j] = 1.0
        return e

    再回忆一下,从文件读入的 training_data[0] 是图像向量,是NumPy数组,而 training_data[1] 是与前者一一对应的数字ID,未修改格式之前,training_data[1] 的数据如图:

    python+OpenCV笔记(三十八):识别手写数字——基于人工神经网络ANN_第4张图片

    我们很容易看出,这些数字表示:第一张图像的数字表示是5,第二张表示是0......
    经过vectorized_result()函数之后,转变为了向量,如图:

    python+OpenCV笔记(三十八):识别手写数字——基于人工神经网络ANN_第5张图片

    也很容易理解,第一个向量的第5位为1,代表数字5...... 

     总之,10个元素的数组对应于人工神经网络的输出层,在训练人工神经网络时,可以将其作为正确输出的一个样本。

  6. 到目前为止,我们已经编写了加载和重新格式化MNIST数据的函数。现在,我们来编写一个函数,用于创建未训练的人工神经网络

    def create_ann(hidden_nodes=60):
        ann = cv2.ml.ANN_MLP_create()
        ann.setLayerSizes(np.array([784, hidden_nodes, 10]))
        ann.setActivationFunction(cv2.ml.ANN_MLP_SIGMOID_SYM, 0.6, 1.0)
        ann.setTrainMethod(cv2.ml.ANN_MLP_BACKPROP, 0.1, 0.1)
        ann.setTermCriteria(
            (cv2.TERM_CRITERIA_MAX_ITER | cv2.TERM_CRITERIA_EPS, 100, 1.0))
        return ann

    解析:
    (1)hidden_nodes代表我们使用60个隐藏结点,步骤2提到过。
    (2)ann = cv2.ml.ANN_MLP_create():创建一个未训练的人工神经网络
    (3)ann.setLayerSizes(np.array([784, hidden_nodes, 10])):配置人工神经网络的层数和节点数,例如,[784, 60, 10] 代表指定784个输入节点,10个输出节点以及包含60个节点的一个隐藏层。如果将其改成 [784, 60, 50, 10] ,将指定两个隐藏层,分别有60个节点和50个节点。
    (4)ann.setActivationFunction:激活函数
    (5)ann.setTrainMethod:训练方法
    (6)ann.setTermCriteria:训练终止标准

  7. 现在,我们需要一个训练函数,允许调用者指定MNIST训练样本数量和阶段数。很多训练功能应该与之前的人工神经网络样本相似,因此,我们先看一下完整的实现:

    def train(ann, samples=50000, epochs=10):
        tr, test = wrap_data()
    
        # 将迭代器转换为列表,以便我们可以在多个阶段中迭代多次
        tr = list(tr)
    
        for epoch in range(epochs):
            print("已完成 %d/%d 个阶段" % (epoch, epochs))
            counter = 0
            for img in tr:
                if (counter > samples):
                    break
                if (counter % 1000 == 0):
                    print("阶段 %d: 对第 %d/%d 个样本进行训练" % \
                          (epoch, counter, samples))
                counter += 1
                sample, response = img
                data = cv2.ml.TrainData_create(
                    np.array([sample], dtype=np.float32),
                    cv2.ml.ROW_SAMPLE,
                    np.array([response], dtype=np.float32))
                if ann.isTrained():
                    ann.train(data,
                              cv2.ml.ANN_MLP_UPDATE_WEIGHTS | cv2.ml.ANN_MLP_NO_INPUT_SCALE | cv2.ml.ANN_MLP_NO_OUTPUT_SCALE)
                else:
                    ann.train(data, cv2.ml.ANN_MLP_NO_INPUT_SCALE | cv2.ml.ANN_MLP_NO_OUTPUT_SCALE)
        print("全部完成!")
    
        return ann, test

    解析:
    (1)函数参数在步骤2中提到过,代表我们使用50000个训练样本,10个阶段。
    (2)
    对于我们处理的每1000个训练样本,打印一条关于训练进展的消息。
    (3)最后,返回训练好的人工神经网络和MNIST测试数据。本来我们可以直接返回人工神经网络,但是如果想要查看人工神经网络的准确率,返回测试数据是很有用的。

  8. 当然,训练人工神经网络的目标是进行预测,因此我们会提供以下pedict函数,以便封装人工神经网络自己的predict方法:

    def predict(ann, sample):
        if sample.shape != (784,):
            if sample.shape != (28, 28):
                sample = cv2.resize(sample, (28, 28),
                                    interpolation=cv2.INTER_LINEAR)
            sample = sample.reshape(784, )
        return ann.predict(np.array([sample], dtype=np.float32))

    首先,该函数确保样本图像是28×28并通过调整大小(如果不是28×28的话)来执行最小数量的数据清理。
    然后,该函数将图像数据展平成向量,再交给人工神经网络进行分类。

  9. 最后,我们还要再实现一个_test_函数,对一组给定的测试数据(如MNIST测试数据)进行分类,测量经训练的人工神经网络的准确率:

    def test(ann, test_data):
        num_tests = 0
        num_correct = 0
        for img in test_data:
            num_tests += 1
            sample, correct_digit_class = img
            digit_class = predict(ann, sample)[0]
            if digit_class == correct_digit_class:
                num_correct += 1
        print('准确率: %.2f%%' % (100.0 * num_correct / num_tests))

二、(测试模块)——digits_ann.py

  •  在上面的函数之后添加代码:
    ann, test_data = train(create_ann())
    _test_(ann, test_data)
    运行脚本,他应该会打印如下的训练信息:
    .
    .
    .

    python+OpenCV笔记(三十八):识别手写数字——基于人工神经网络ANN_第6张图片
    .
    .
    .

    python+OpenCV笔记(三十八):识别手写数字——基于人工神经网络ANN_第7张图片

可以看到,在MINST数据集的10000个测试样本时,人工神经网络的准确率达到了95.39%,那我们来看看人工神经网络的泛化性能如何:即它能对完全不同来源且与MINST无关的数据进行准确的分类吗?我们的主应用程序将为分类器迎接这类挑战。

三、(主模块)——detect_and_classify_digits.py

                            ——请先创建该脚本文件

  1. 首先,导入OpenCV、NumPy以及digits_ann模块
    import cv2
    import numpy as np
    
    import digits_ann
  2. 编写两个辅助函数,分析并调整数字和其他轮廓的矩形框。正如我们在前面章节中看到的,一个常见的问题是重叠检测。下面的函数名为inside,将帮助我们确定边框是否完全包含在另一个边框中:
    def inside(r1, r2):
        x1, y1, w1, h1 = r1
        x2, y2, w2, h2 = r2
        return (x1 > x2) and (y1 > y2) and (x1+w1 < x2+w2) and \
                (y1+h1 < y2+h2)

    在inside函数的帮助下,我们可以很容易地选择每个数字最外层的边框。这一点很重要,因为我们不希望检测器将数字8同时还能检测出两个0。

  3. 为了进一步确保矩形框满足分类器的需要,我们将使用名为wrap_digit的辅助函数,将紧密拟合的矩形框转换为包围数字的填充正方形。请记住,MNIST数据包含28×28像素的数字图像,所以在尝试使用MNIST训练的人工神经网络进行分类之前,必须将任何感兴趣的区域重新缩放到这个大小。通过使用填充的边框而不是紧密拟合的矩形边框,我们可以确保瘦数字(如1)和胖数字(如0)的拉伸不会不同。
    def wrap_digit(rect, img_w, img_h):
    
        x, y, w, h = rect
    
        x_center = x + w//2
        y_center = y + h//2
        if (h > w):
            w = h
            x = x_center - (w//2)
        else:
            h = w
            y = y_center - (h//2)
    
        padding = 5
        x -= padding
        y -= padding
        w += 2 * padding
        h += 2 * padding
    
        if x < 0:
            x = 0
        elif x > img_w:
            x = img_w
    
        if y < 0:
            y = 0
        elif y > img_h:
            y = img_h
    
        if x+w > img_w:
            w = img_w - x
    
        if y+h > img_h:
            h = img_h - y
    
        return x, y, w, h

    解析:
    (1)首先,修改矩形的较短边(不管是宽还是高),使其等于较长的边,修改矩形的x, y位置,使中心保持不变。
    (2)在四周添加5个像素,此时,修改的矩形可能会延伸到图像的外部。
    (3)
    为了避免越界的问题,我们裁剪矩形使矩形完全位于图像内。在这些边缘情况下,我们可能会得到非正方形矩形,但是这是可以接受的。我们更喜欢使用感兴趣的非正方形区域,而不是仅因为检测到的数字在图像的边缘处就必须完全丢弃它
    (4)返回修改后的矩形坐标

  4. 现在,我们进入主程序部分。首先,创建人工神经网络,并在MNIST数据上对其进行训练:

    ann, test_data = digits_ann.train(
        digits_ann.create_ann(60), 50000, 10)

    (请注意,我们使用的是digits_ann模块中的create_ann和train函数)

  5. 现在,加载一幅测试图像(一页白纸上包含许多手写数字的图像):
    img_path = "654321.jpg"
    img = cv2.imread(img_path, cv2.IMREAD_COLOR)

    (图像可以自己手写,注意最好是白底黑字)

  6. 把图像转换成灰度图像并对其进行模糊,以便去除噪声,并使墨水的颜色均匀一些:
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    cv2.GaussianBlur(gray, (7, 7), 0, gray)

    应用阈值以及形态学运算,确保数字从背景中脱颖而出,而且轮廓相对不规则,这可能会干扰预测

    ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV)
    erode_kernel = np.ones((2, 2), np.uint8)
    thresh = cv2.erode(thresh, erode_kernel, thresh, iterations=2)

    为了检测图片中的每个数字,首先需要找到轮廓:

    contours, hier = cv2.findContours(thresh, cv2.RETR_TREE,
                                      cv2.CHAIN_APPROX_SIMPLE)

    然后,遍历轮廓并找到它们的矩形框。丢弃所有太大或太小而不能视为数字的矩形,同时丢弃所有完全包含在其他矩形中的矩形。把其余的矩形添加到好的矩形列表中,(我们认为)这些矩形包含单个的数字。

    rectangles = []
    
    img_h, img_w = img.shape[:2]
    img_area = img_w * img_h
    for c in contours:
    
        a = cv2.contourArea(c)
        if a >= 0.98 * img_area or a <= 0.0001 * img_area:
            continue
    
        r = cv2.boundingRect(c)
        is_inside = False
        for q in rectangles:
            if inside(r, q):
                is_inside = True
                break
        if not is_inside:
            rectangles.append(r)

    既然有了好的矩形列表,那么就可以遍历它们,使用wrap_digit函数对该列表进行清理,并对其内的图像数据进行分类:

    for r in rectangles:
        x, y, w, h = wrap_digit(r, img_w, img_h)
        roi = thresh[y:y+h, x:x+w]
        digit_class = int(digits_ann.predict(ann, roi)[0])
        cv2.rectangle(img, (x,y), (x+w, y+h), (255, 0, 0), 2)
        cv2.putText(img, "%d" % digit_class, (x, y-5),
                    cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 2)
  7. 在处理完所有感兴趣的区域后,显示这些图像,直到用户按下任意键结束程序
    cv2.imshow("thresh", thresh)
    cv2.imshow("detected and classified digits", img)
    cv2.waitKey()

三、全部代码

digits_ann.py 和 detect_and_classify_digits.py两个文件

1.digits_ann.py

import gzip
import pickle

import cv2
import numpy as np

"""OpenCV ANN Handwritten digit recognition example

Wraps OpenCV's own ANN by automating the loading of data and supplying default paramters,
such as 20 hidden layers, 10000 samples and 1 training epoch.

The load data code is adapted from http://neuralnetworksanddeeplearning.com/chap1.html
by Michael Nielsen
"""


def load_data():
    mnist = gzip.open('mnist.pkl.gz', 'rb')
    training_data, test_data = pickle.load(mnist)
    mnist.close()
    return (training_data, test_data)


def wrap_data():
    tr_d, te_d = load_data()
    training_inputs = tr_d[0]
    training_results = [vectorized_result(y) for y in tr_d[1]]
    training_data = zip(training_inputs, training_results)
    test_data = zip(te_d[0], te_d[1])
    return (training_data, test_data)


def vectorized_result(j):
    e = np.zeros((10,), np.float32)
    e[j] = 1.0
    return e


def create_ann(hidden_nodes=60):
    ann = cv2.ml.ANN_MLP_create()
    ann.setLayerSizes(np.array([784, hidden_nodes, 10]))
    ann.setActivationFunction(cv2.ml.ANN_MLP_SIGMOID_SYM, 0.6, 1.0)
    ann.setTrainMethod(cv2.ml.ANN_MLP_BACKPROP, 0.1, 0.1)
    ann.setTermCriteria(
        (cv2.TERM_CRITERIA_MAX_ITER | cv2.TERM_CRITERIA_EPS, 100, 1.0))
    return ann


def train(ann, samples=50000, epochs=10):
    tr, test = wrap_data()

    # 将迭代器转换为列表,以便我们可以在多个阶段中迭代多次
    tr = list(tr)

    for epoch in range(epochs):
        print("已完成 %d/%d 个阶段" % (epoch, epochs))
        counter = 0
        for img in tr:
            if (counter > samples):
                break
            if (counter % 1000 == 0):
                print("阶段 %d: 对第 %d/%d 个样本进行训练" % \
                      (epoch, counter, samples))
            counter += 1
            sample, response = img
            data = cv2.ml.TrainData_create(
                np.array([sample], dtype=np.float32),
                cv2.ml.ROW_SAMPLE,
                np.array([response], dtype=np.float32))
            if ann.isTrained():
                ann.train(data,
                          cv2.ml.ANN_MLP_UPDATE_WEIGHTS | cv2.ml.ANN_MLP_NO_INPUT_SCALE | cv2.ml.ANN_MLP_NO_OUTPUT_SCALE)
            else:
                ann.train(data, cv2.ml.ANN_MLP_NO_INPUT_SCALE | cv2.ml.ANN_MLP_NO_OUTPUT_SCALE)
    print("全部完成!")

    return ann, test


def _test_(ann, test_data):
    num_tests = 0
    num_correct = 0
    for img in test_data:
        num_tests += 1
        sample, correct_digit_class = img
        digit_class = predict(ann, sample)[0]
        if digit_class == correct_digit_class:
            num_correct += 1
    print('准确率: %.2f%%' % (100.0 * num_correct / num_tests))


def predict(ann, sample):
    if sample.shape != (784,):
        if sample.shape != (28, 28):
            sample = cv2.resize(sample, (28, 28),
                                interpolation=cv2.INTER_LINEAR)
        sample = sample.reshape(784, )
    return ann.predict(np.array([sample], dtype=np.float32))


# ann, test_data = train(create_ann())
# _test_(ann, test_data)

2.detect_and_classify_digits.py

import cv2
import numpy as np

import digits_ann


OPENCV_MAJOR_VERSION = int(cv2.__version__.split('.')[0])


def inside(r1, r2):
    x1, y1, w1, h1 = r1
    x2, y2, w2, h2 = r2
    return (x1 > x2) and (y1 > y2) and (x1+w1 < x2+w2) and \
            (y1+h1 < y2+h2)


def wrap_digit(rect, img_w, img_h):

    x, y, w, h = rect

    x_center = x + w//2
    y_center = y + h//2
    if (h > w):
        w = h
        x = x_center - (w//2)
    else:
        h = w
        y = y_center - (h//2)

    padding = 5
    x -= padding
    y -= padding
    w += 2 * padding
    h += 2 * padding

    if x < 0:
        x = 0
    elif x > img_w:
        x = img_w

    if y < 0:
        y = 0
    elif y > img_h:
        y = img_h

    if x+w > img_w:
        w = img_w - x

    if y+h > img_h:
        h = img_h - y

    return x, y, w, h


ann, test_data = digits_ann.train(
    digits_ann.create_ann(60), 50000, 10)

img_path = "654321.jpg"
img = cv2.imread(img_path, cv2.IMREAD_COLOR)

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.GaussianBlur(gray, (7, 7), 0, gray)

ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV)
erode_kernel = np.ones((2, 2), np.uint8)
thresh = cv2.erode(thresh, erode_kernel, thresh, iterations=2)

if OPENCV_MAJOR_VERSION >= 4:
    # OpenCV 4 or a later version is being used.
    contours, hier = cv2.findContours(thresh, cv2.RETR_TREE,
                                      cv2.CHAIN_APPROX_SIMPLE)
else:
    # OpenCV 3 or an earlier version is being used.
    # cv2.findContours has an extra return value.
    # The extra return value is the thresholded image, which is
    # unchanged, so we can ignore it.
    _, contours, hier = cv2.findContours(thresh, cv2.RETR_TREE,
                                         cv2.CHAIN_APPROX_SIMPLE)

rectangles = []

img_h, img_w = img.shape[:2]
img_area = img_w * img_h
for c in contours:

    a = cv2.contourArea(c)
    if a >= 0.98 * img_area or a <= 0.0001 * img_area:
        continue

    r = cv2.boundingRect(c)
    is_inside = False
    for q in rectangles:
        if inside(r, q):
            is_inside = True
            break
    if not is_inside:
        rectangles.append(r)

for r in rectangles:
    x, y, w, h = wrap_digit(r, img_w, img_h)
    roi = thresh[y:y+h, x:x+w]
    digit_class = int(digits_ann.predict(ann, roi)[0])
    cv2.rectangle(img, (x,y), (x+w, y+h), (255, 0, 0), 2)
    cv2.putText(img, "%d" % digit_class, (x, y-5),
                cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 2)

# cv2.imwrite("detected_and_classified_digits_thresh.png", thresh)
# cv2.imwrite("detected_and_classified_digits.png", img)
cv2.imshow("thresh", thresh)
cv2.imshow("detected and classified digits", img)
cv2.waitKey()

运行第二个脚本文件:

python+OpenCV笔记(三十八):识别手写数字——基于人工神经网络ANN_第8张图片

python+OpenCV笔记(三十八):识别手写数字——基于人工神经网络ANN_第9张图片

 示例图片下载地址(GitHub):GitHub - PacktPublishing/Learning-OpenCV-4-Computer-Vision-with-Python-Third-Edition: Learning OpenCV 4 Computer Vision with Python 3 – Third Edition, published by Packt

四、保存与加载分类器

我们可以应用一些潜在的改进措施来训练人工神经网络,例如:

  • 可以用不同数据集大小、隐藏节点的数量以及阶段数进行实验,直到找到准确率的峰值水平
  • 可以修改digits_ann.create_ann 函数,使其支持多个隐藏层
  • 还可以尝试不同的激活函数
  • 也可以尝试不同的训练方法。我们使用了cv2.m1.ANN_MLP_BACKPROP。
    其他的包括cV2.M1.ANN_MLP_RPROP 和cv2.m1.ANN_MLP_ANNEAL

总之,请记住,只要我们对分类器的准确率感到满意,我们就可以保存并重新加载这个分类器,这样就可以在应用程序中使用分类器,而不必每次都训练人工神经网络。

使用下面的代码将经过训练的人工神经网络保存到XML文件中:


ann = cv2.ml.ANN_MLP_create()
data = cv2.ml.TrainData_create(
        training_samples, layout, training_responses)
ann.train(data)
ann.save('my_ann.xml')


加载:


ann = cv2,m1.ANN_MLP_create()
ann.load('my_ann.xml') 


 

【参考】:OpenCV 4计算机视觉 Python语言实现(原书第三版) 作者:Joseph Howse

你可能感兴趣的:(python+OpenCv,python,opencv,人工智能,文字识别,神经网络)