精华内容
下载资源
问答
  • 本程序有三个cpp文件,main.cpp为测试文件;prepare.cpp为预处理文件;train_SVMSVM的训练文件。详情可参考博客:https://blog.csdn.net/didi_ya/article/details/114362904
  • 七、使用神经网络识别手写数字 前期准备 下载数据集: git clone https://github.com/mnielsen/neural-networks-and-deep-learning.git 线性代数运算:使用Python函数库Numpy 共60000幅图片,50000幅图片用于训练,...

    复杂的算法 ≤ 简单的学习算法 + 好的训练数据

    神经网络通过一系列的“多层结构”将一个复杂的问题神经网络通过一系列的“多层结构”将一个复杂的问题逐渐简化为在像素级别就可以轻易回答的简单问题

    一、概述

    • 人工神经网络的两大类别
    • 感知器
    • sigmoid神经元
    • 神经网络标准学习算法
    • 随机梯度下降
    • 本章要解决的问题
    • 为什么这样做是有效的

    二、感知器

    • 许多现代的神经网络工作中,主要的神经网络模型是sigmoid神经元
    • 通过对输入加权求和,判断结果是否大于或者小于某个阈值(threshold),给定结果是0或1。下图就是一个感知器如何工作的全部内容!

    8c3c30aec5b607804192af1506fe47dc.png

    这个加法器例子演示了感知器网络能够模拟具有许多与非门的电路。并且由于与非门对于计算是通用的,因此感知器也是对计算通用的。 它能够让我们设计学习算法,这种算法能够自动调整人工神经网络的权重和偏移。

    三、Sigmoid神经元

    • 为了看到学习如何进行,假设我们在网络中改变一点权重(或者偏移)。我们希望很小的权重改变能够引起网络输出的细微改变。后面我们将看到,这个特性将使学习成为可能。以下示意图就是我们想要的(显然这个网络对于手写识别太简单!):

    4503437e984ef2c9dd0d7e889a1c68bf.png
    • 问题是在我们的感知器网络中并没有这样发生。实际上,任何一个感知器的权重或者偏移细小的改变有时都能使得感知器输出彻底翻转,从0到1。
    • 也许对于这个问题有一些聪明的方式解决,但是目前如何让感知器网络学习还不够清楚。
    • 我们能通过引入一种新的人工神经元(sigmoid神经元)来克服这个问题。 它和感知器类似,但是细微调整它的权重和偏移只会很细小地影响到输出结果。这就是让sigmoid神经元网络学习的关键原因。
    • sigmoid函数输出不是0或者1,而是

    bf9dde1551854b25ffb5c58505095b3e.png

    9717523dc9700ad31b1c19bdb1af9e25.png

    是sigmoid函数(逻辑函数)

    c6bef54ee5ffbc1277a49867bd0ccc49.png

    四、神经网络体系结构

    38163cf915b8d146a4dbabbcb3f179b1.png
    • 输入神经元:输入层的神经元
    • 输出神经元:输出层的神经元
    • 隐含层:既不是输入也不是输出
    • 前向反馈神经网络:某一层的输出当作下一层的输入的神经网络(没有循环)。下面将使用它解决手写识别问题。
    • 递归神经网络:具有循环反馈的网络。递归神经网络没有前馈神经网络具有影响力,因为递归网络的学习算法威力不大(至少到目前)。 它更接近人脑的工作方式。

    输入层和输出层的设计

    十分简单:如果图像是一幅64 by 64的灰度图,那么我们将有4,096=64×64个输入神经元,每一个是介于0和1的强度值。输出层将只包含一个神经元,输出值小于0.5表示“输入图像不是9”,大于0.5表示“输入图像是9”。

    隐含层的设计

    比较困难:现在研究者已经为隐含层开发出许多启发式设计。

    五、分类手写数字的简单神经网络

    采用三层神经网络。

    80c221410417b4556868fea6569db3ee.png

    图像信息:28 by 28 灰度图像

    输入层:784 = 28 × 28,白色值0.0,黑色值1.0

    隐含层:神经元数量 = 15(可以实验调整)

    输出层:0 ~ 9 十个数字,共 10 个神经元

    隐含层的神经元做了什么呢?

    (答案不唯一)假设隐含层中第一个神经元目标是检测到是否存在像下面一样的图像:

    3f870731f7f806fe089b2e7ee26a0940.png

    它能够对输入图像与上面图像中像素重叠部分进行重加权,其他像素轻加权来实现。相似的方式,对隐含层的第二、三和四个神经元同样能检测到如下所示的图像:

    01bc004c47df21a9a53e85656cab511e.png

    你可能已经猜到,这四幅图像一起构成了我们之前看到的0的图像:

    832068ff34ef82171e9069c93eac96fe.png

    因此如果这四个神经元一起被触发,那么我们能够推断出手写数字是0。

    六、梯度下降学习算法

    公式推导:梯度下降法详解(公式推导)

    输入x:

    c5eb359dc8e8f112ad5e0001a38a2d51.png

    维的向量。也就是784个输入神经元。

    对应关系:

    52be0eac4bced580ded48581c6581097.png

    输出y:

    9a146dbcd688fbb06bf09e03bd68c3ee.png

    。T表示转置。它表明输入的是6(理想状态下)。

    面临的问题:如何找到合适的权重和偏移的算法?

    解决方式:量化这个问题,定义一个代价函数,如下,

    d91758b51a6fbf2008dc986ec979f2e5.png

    这里,w表示网络中的所有权重,b是所有偏移,n训练输入的总数,a是网络输入为x时的输出向量,总和是对所有输入x进行的累加。当然输出a取决于x,w和b,但是为了符号简化,我没有指明这种依赖关系。

    我们称C为二次型代价函数;它有时候也叫做均方误差或MSE。

    因此问题转化为:最小化

    53d2140b027f58b7767a7140cabda874.png

    我们将使用梯度下降算法来实现。 具体思路是用梯度下降来寻找权重(weights)

    c08894f29626a4e88da78e25f318c9be.png

    和 偏移(bias)

    5f4bd7cbf185441d9067d4f7fca993cc.png

    由于梯度下降算法计算量过大,通常的是随机梯度下降法。其更新公式为:

    df24a2e5a49c46de2d78c72eb5f0f703.png

    从所有训练输入中挑选出m个输入组成一个batch,其中,j=1~m,当完成对m个数据的训练后,我们称完成了一(epoch)训练。

    例如,在MNIST样例中,整个训练集的样本数 n=60000 个,而比如说我们选定的“小组”样本数为m=10个。这意味着我们在估算梯度的时候会快6000倍!当然,估算结果不一定精确——因为有统计涨落的误差——不过,我们不需要它精确:因为我们关心的仅仅是这个过程是不是整体向着C减小的方向在走,而不是要严格计算出梯度值。

    梯度下降的一种极端方式是使用大小为1的“小组”。也就是,给定一个训练输入xx,我们按照规则

    3cab65c236c733945da05d72d798340e.png

    688ac6f3a1af6f121dbbde381df24635.png

    来更新权重和偏差。然后选择另外一个训练输入,再来更新权重和偏差。反复这样,这个过程被称为在线在线增量学习。在线学习中,神经网络每次从一个训练输入中进行学习(就像人来开始那样)。

    如何想象三维的空间?

    302a111694417368d6dc35efcb1fa944.png

    七、使用神经网络识别手写数字

    前期准备

    下载数据集:

    git clone https://github.com/mnielsen/neural-networks-and-deep-learning.git

    线性代数运算:使用Python函数库Numpy

    共60000幅图片,50000幅图片用于训练,10000幅图片用于验证。

    核心类:Network类

    作用:定义网络的结构模型

    使用说明:

    假设要处理的图片是像素为 28×28=784 的灰度图像,最终的判定结果是 0~9 十个数字。

    那么这个神经网络就可以设计为输入层(第一层) 784 个神经元,隐含层(第二层)30 个神经元(可更改),输出层(第三层) 10 个神经元。可以看出规律,每个神经元对应一个像素点。

    因此,可以初始化实例 net = Network([784, 30, 10])

    class Network(object):
    
        def __init__(self, sizes):
            self.num_layers = len(sizes)
            self.sizes = sizes
            self.biases = [np.random.randn(y, 1) for y in sizes[1:]]
            self.weights = [np.random.randn(y, x) 
                            for x, y in zip(sizes[:-1], sizes[1:])]

    核心函数:SGD函数

    作用:训练和测试数据集

    使用说明:

    首先先看一下几个参数是什么样子的,然后才能知道它是怎么工作的。

    print training_data[0]

    c6d1f05bf2abbd2e451c754302b172a7.png

    省略……若干行

    460ce7549c4fdad59cbca6ccbf59292a.png

    省略……若干行

    5c18732f36f09ac6c20b4a218fe917ef.png

    翻译这个输出,让学过数学的人都能看懂,那就是下图:

    7a18012887528b0d2a742f1d37e28696.png

    代码演示如何生成训练数据:

    input = np.array([[[1], [2], [3]],
                      [[4], [5], [6]],
                      [[7], [8], [9]]
                     ], dtype=np.float32)
                 
    output = np.array([[[1],[0],[0]],
                       [[0],[1],[0]],
                       [[0],[0],[1]]
                      ], dtype=np.int)
                 
    training_data = zip(input, output)

    training_data 是 (x, y) 类型元组组成的列表,其中 x 表示训练数据的输入,y 为对应的输出。进一步剖析,可以知道,x 为对应像素点的灰度值,y 为对应的输出,本例中这个图片为手写数字 5 (因为第二个 array 的第 6 个数字是 1,注意从 0 开始)。

    P.S.【什么是元组可以看下方参考代码 test for tuple list function】

    epochs,mini_batch_size,eta

    epochs 是迭代次数,mini_batch_size 是每个“小组”的图片个数,eta 是学习率。假设我们每次从全部的 60000 张图片中随机的选择 50 张图片用作训练,那么 mini_batch_size=50 。当我们【对这 50 张图片做训练,更新完权重后(不完全明白,等待后续补充)】。我们称完成了一代(epoch)的训练,epochs+1。【eta 是学习率不完全明白,等待后续补充)】

    def SGD(self, training_data, epochs, mini_batch_size, eta,
            test_data=None):
        """Train the neural network using mini-batch stochastic
        gradient descent.  The "training_data" is a list of tuples
        "(x, y)" representing the training inputs and the desired
        outputs.  http://tensorfly.cn/home The other non-optional parameters are
        self-explanatory.  If "test_data" is provided then the
        network will be evaluated against the test data after each
        epoch, and partial progress printed out.  This is useful for
        tracking progress, but slows things down substantially."""
    
        if test_data: n_test = len(test_data)
        n = len(training_data)
        for j in xrange(epochs):
            random.shuffle(training_data)
            mini_batches = [
                training_data[k:k+mini_batch_size]
                for k in xrange(0, n, mini_batch_size)]
            for mini_batch in mini_batches:
                self.update_mini_batch(mini_batch, eta)
            if test_data:
                print "Epoch {0}: {1} / {2}".format(
                    j, self.evaluate(test_data), n_test)
            else:
                print "Epoch {0} complete".format(j)

    八、通往深度学习

    有两个或更多“隐藏层”(hidden lyaers)的网络,被称为深度神经网络。

    利用神经网络识别其他图片也是类似的思想。例如识别一个图片是否含有人脸,则可以建立下面的一个神经网络:

    f3d3987fb36d120a466fb094229bd220.png

    这些子网络似乎也应该可以进一步细化。假设我们来考虑这个问题:“是不是有个眼睛在图片的左上方?” 这个问题可以被进一步细化为如下“子问题”:“是不是有眉毛?”;“是不是有睫毛?”;“是不是有瞳孔?”;如此等等。当然,这些问题其实应该也包含相关的位置信息,例如:“眉毛是在左上方,并且在瞳孔上方吗?” 。不过,为了简化表述,我们认为对于“是不是有个眼睛在图片的左上方?”这个问题,我们可以进一步细化为:

    b96c80a765fbc806c20e8bf936ed827a.png

    这些问题当然可以通过多层结构(multiple layers)被一步一步更加细化。直到最终,我们要通过子网络来解决的问题如此简单,以至于可以在像素级别上很容易地回答它们。比如说,这些问题可能是:某种很简单的形状是否出现于图片某个特定的位置上。这些问题可以由直接与图片像素相连接的神经元来回答。

    因此,这种启发性的算法可以推广到很多方面,这也就是人工智能应用场景了。

    九、参考文献

    [1] TensorFly神经网络教程 - 第一章

    [2] 什么是张量?在物理学中,张量可以看成是一种参考系的变换。在数学家眼中, 张量已经被抽象成了线性变换。

    附、Python语法测试

    import random
    import numpy as np
    
    
    # ++++++++++ test for `xrange` function ++++++++++
    print "n ++++ test for `xrange` function ++++"
    test_data = (11, 52, 63, 54, 25, 46) # a tuple CAN NOT EDIT
    print '    test_data:', test_data
    n = len(test_data) # n = 6
    for k in xrange(1, n, 2):
      print '  xrange(',k,'):', k
    mini_batches = [test_data[k: k+2] 
                    for k in xrange(1, n, 2)] # xrange returns the INDEX
    print 'mini_batches: ', mini_batches
    
    
    # ++++++++++ test for `random.shuffle` function ++++++++++
    print "nnn ++++ test for `random.shuffle` function ++++"
    test_data2 = [34, 45, 67, 89, 23, 76]
    print 'before shuffle:', test_data2
    random.shuffle(test_data2)
    print ' after shuffle:', test_data2
    
    
    # ++++++++++ test for `np_array.shape` attribute ++++++++++
    print "nnn ++++ test for `np_array.shape` attribute ++++"
    test_data3 = np.array( [(3, 4, 5, 6),(5, 6, 7, 8)] ) # .shape is 2 rows, 4 cols
    print np.zeros(test_data3.shape) 
    
    
    # ++++++++++ test for `tuple list` function ++++++++++
    print "nnn ++++ test for `tuple list` function ++++"
    for x, y in ((3, 4), (5, 6)):  # ((3, 4), (5, 6)) is a tuple list
      print '  x:', x, '  y:', y
      
    # ++++++++++ test for `np.dot(x_matrix, y_matrix)` function ++++++++++
    print "nnn ++++ test for `np.dot(x_matrix, y_matrix)` function ++++"
    x_matrix = [3, 4, 5]
    y_matrix = [[1], [2], [3]]
    print np.dot(x_matrix, y_matrix) # 1*3+2*4+3*5=26
    展开全文
  • 这篇博客是之前那篇在win7上用OpenCV的SVM分类器做[MNIST手写数字识别](https://blog.csdn.net/wblgers1234/article/details/73477860)的后续。用MNIST数据集做SVM训练和测试的细节可以移步那篇博客进行了解。

    这篇博客是之前那篇在win7上用OpenCV的SVM分类器做MNIST手写数字识别的后续。用MNIST数据集做SVM训练和测试的细节可以移步那篇博客进行了解。

    0.开发环境

    这篇文章的思路是将Windows上训练好的SVM分类模型移植到Android上,并可以实时通过手机触摸屏进行数字手写体测试,这样对算法的理解更直观,也让算法有了实用性。后期如果有时间和条件,我可以逐渐将这个识别功能具体化,做一个可以识别任意文字的App。

    以下是我的开发环境配置:

    • Android Studio
    • Android SDK 7.1.1 (API25)
    • OpenCV4Android 2.4.10

    1.设计思路

    考虑到手机的处理器性能,所以这次的实现将不会在手机端进行SVM分类器的训练。换句话说,我们首先需要现在PC上用OpenCV训练出一个可用的SVM分类模型,然后在Android上将这个分类模型进行加载,最后再用它进行手写体的分类测试。

    2.Layout

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        tools:context="com.example.bolong_wen.handwritedigitrecognize.MainActivity">
    
        <TextView
            android:id="@+id/intro"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="25sp"
            android:text="It's a recognition demo on hand written digits, enjoy!" />
        <com.example.bolong_wen.handwritedigitrecognize.HandWriteView
            android:id="@+id/handWriteView"
            android:layout_below="@id/intro"
            android:layout_width="match_parent"
            android:background="@drawable/draw_background"
            android:layout_height="400dp" />
        <Button
            android:id="@+id/btnRecognize"
            android:layout_below="@id/handWriteView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft = "true"
            android:text="Recognize" />
        <Button
            android:id="@+id/btnClear"
            android:layout_below="@id/handWriteView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight = "true"
            android:text="Clear" />
        <TextView
            android:id="@+id/resultShow"
            android:layout_below="@id/btnRecognize"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft = "true"
            android:layout_alignParentBottom="true"
            android:textSize="25sp"
            android:layout_marginBottom = "50dp"
            android:text= "The recognition result is: " />
    </RelativeLayout>
    

    在界面设计上,除了两个交互性的按钮Button和一些显示性的静态文本外,需要特别注意的是通过触摸屏进行手写的部分。
    这部分显示是继承于Android的View,我们将其命名为HandWriteView。当手指在屏幕上滑动时,会触发onTouchEvent函数,我们在这个函数中进行坐标提取,并把每次滑动的轨迹用很小的线段拼接起来,这样就达到了手写体显示的效果。在进行识别时,将当前View上面的内容通过BitMap取出,然后送入SVM分类器进行识别。

    3.核心代码

    3.1 加载SVM分类器

    为了方便每次更新训练好的SVM模型,我将它放入Android的res目录下,在Android Studio环境中要注意添加新的res目录时,请选择“raw”这个类别,如下图所示:

    ![AS添加新的resmulu](https://img-blog.csdn.net/20180508172735779?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dibGdlcnMxMjM0/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 首先声明一个SVM分类器和SVM模型的承载器:
    CvSVM mClassifier;
    File mSvmModel;

    然后我们通过Android的资源目录将保存好的分类器模型进行载入,我存放的模型名字为mnist.xml

    mClassifier = new CvSVM();
    
    //
    try {
        // load cascade file from application resources
        InputStream is = getResources().openRawResource(R.raw.mnist);
        File mnist_modelDir = getDir("mnist_model", Context.MODE_PRIVATE);
        mSvmModel = new File(mnist_modelDir, "mnist.xml");
        FileOutputStream os = new FileOutputStream(mSvmModel);
    
        byte[] buffer = new byte[4096];
        int bytesRead;
        while ((bytesRead = is.read(buffer)) != -1) {
            os.write(buffer, 0, bytesRead);
        }
        is.close();
        os.close();
    
        mClassifier.load(mSvmModel.getAbsolutePath());
    
        mnist_modelDir.delete();
    
    } catch (IOException e) {
        e.printStackTrace();
        Log.e(TAG, "Failed to load cascade. Exception thrown: " + e);
    }

    在完成这一步并且没有报错的情况下,mClassifier已经将整个SVM模型加载完成,可以进行接下来的预测。

    3.2 HandWriteView绘制手写体

    先给出这部分的代码:

    package com.example.bolong_wen.handwritedigitrecognize;
    import android.content.Context;
    import android.graphics.Bitmap;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.util.AttributeSet;
    import android.view.MotionEvent;
    import android.view.View;
    /**
     * Created by bolong_wen on 2018/3/29.
     */
    
    public class HandWriteView extends View{
    
        public Bitmap returnBitmap(){
            return mBitmap;
        }
        private Paint mPaint;
        private float degrees=0;
        private int mLastX, mLastY, mCurrX, mCurrY;
        private Bitmap mBitmap;
    
        public HandWriteView(Context context) {
            super(context);
            init();
        }
    
        public HandWriteView(Context context, AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        public HandWriteView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init();
        }
        private void init(){
            mPaint = new Paint();
            mPaint.setColor(Color.WHITE);
            mPaint.setStrokeWidth(70);
            mPaint.setAntiAlias(true);
            mPaint.setDither(true);
            mPaint.setStrokeCap(Paint.Cap.ROUND);
            mPaint.setStrokeJoin(Paint.Join.ROUND);
        }
        @Override
        public void draw(Canvas canvas) {
            super.draw(canvas);
    
            int width = getWidth();
            int height = getHeight();
            if (mBitmap == null) {
                mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            }
            canvas.drawBitmap(mBitmap, 0, 0, mPaint);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
    
            mLastX = mCurrX;
            mLastY = mCurrY;
            mCurrX = (int) event.getX();
            mCurrY = (int) event.getY();
    
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    mLastX = mCurrX;
                    mLastY = mCurrY;
                    break;
                default:
                    break;
            }
            updateDrawHandWrite();
            return true;
        }
    
        private void updateDrawHandWrite(){
            int width = getWidth();
            int height = getHeight();
    
            if (mBitmap == null) {
                mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            }
    
            Canvas tmpCanvas = new Canvas(mBitmap);
            tmpCanvas.drawLine(mLastX, mLastY, mCurrX, mCurrY, mPaint);
            invalidate();
        }
    
        public void clearDraw(){
            mBitmap = null;
            invalidate();
        }
    }
    

    在这个View类的初始化里面,我们设置好画笔的颜色,宽度,同时需要注意的是要设置笔触风格和连接处的形状为“圆形”,以及设置反锯齿,这样会使得画出来的手写体数字更光滑,细节处更连贯,有利于后期的识别。这段代码如下所示:

    mPaint.setAntiAlias(true);
    mPaint.setDither(true);
    mPaint.setStrokeCap(Paint.Cap.ROUND);
    mPaint.setStrokeJoin(Paint.Join.ROUND);

    当用户通过手指触摸在屏幕上移动时会触发onTouchEvent函数,在该函数里我们获取当前的接触点坐标:

    mLastX = mCurrX;
    mLastY = mCurrY;
    mCurrX = (int) event.getX();
    mCurrY = (int) event.getY();

    同时在原始接触点Last和当前接触点Curr之间绘制出直线:

    int width = getWidth();
    int height = getHeight();
    
    if (mBitmap == null) {
        mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    }
    
    Canvas tmpCanvas = new Canvas(mBitmap);
    tmpCanvas.drawLine(mLastX, mLastY, mCurrX, mCurrY, mPaint);
    invalidate();

    3.3 识别数字

    在Android程序的主activity中MainActivity,当按下识别按钮时,会从HandWriteView返回得到一个Bitmap,它是当前绘制得到的一个截图(snapshot),然后将这个Bitmap转换为OpenCV的Mat格式,同时进行灰度化处理。

    Bitmap tmpBitmap = mHandWriteView.returnBitmap();
    if(null == tmpBitmap)
        return;
    Mat tmpMat = new Mat(tmpBitmap.getHeight(),tmpBitmap.getWidth(),CvType.CV_8UC3);
    Mat saveMat = new Mat(tmpBitmap.getHeight(),tmpBitmap.getWidth(),CvType.CV_8UC1);
    
    Utils.bitmapToMat(tmpBitmap,tmpMat);
    
    Imgproc.cvtColor(tmpMat, saveMat, Imgproc.COLOR_RGBA2GRAY);

    在前一篇博客中我们的SVM分类器模型是基于MNIST数据集进行训练得到的,数据集中的每幅图片的大小是28×28。因此在进行实际测试时,我们也需要将上一步手写得到的图片进行resize处理,归一化到[0,1],并且转换为一维向量。

    int imgVectorLen = 28 * 28;
    Mat dstMat = new Mat(28,28,CvType.CV_8UC1);
    Mat tempFloat = new Mat(28,28,CvType.CV_32FC1);
    
    Imgproc.resize(saveMat,dstMat,new Size(28,28));
    dstMat.convertTo(tempFloat, CvType.CV_32FC1);
    
    Mat predict_mat = tempFloat.reshape(0,1).clone();
    Core.normalize(predict_mat,predict_mat,0.0,1.0,Core.NORM_MINMAX);

    其中特别需要注意的是归一化,MNIST中每幅图片的数据都是在[0,1]之间,要保持一致才能得到正确的结果。
    最后一步,我们调用加载好的SVM模型进行预测,得到识别出的数字:

    int response = (int)mClassifier.predict(predict_mat);

    4.demo效果

    直接给出在手机上运行的识别效果:

    这里写图片描述

    经过多次测试,发现在8/9两个数字上的识别率比较低。还需要在后续的开发中进行改进,有一个思路:将误识别的8/9手写体图片保存下来,加入训练集,重新训练模型,这样应该会得到一个更好的分类效果。

    项目地址:HandwriteDigitRecognize
    ^-^ 欢迎交流讨论!

    展开全文
  • SVM实现手写数字识别

    千次阅读 2018-03-30 18:48:01
    SVM简介 知乎上的一个回答我认为是史上...开发环境 Windows10 + VS2013 + Qt580 + OpenCV300主要代码 利用opencv-SVM算法和Mnist数据集封装成一个单例模式的数字识别检测器。DigitsDetector.h#ifndef DIGITSDETEC...

    SVM简介

        知乎上的一个回答我认为是史上最NB最形象的SVM含义解释,想看介绍戳这里(里面的第一个回答),再看看百科就能知道个大概了。

    开发环境

        Windows10 + VS2013 + Qt580 + OpenCV300

    主要代码

        利用opencv-SVM算法和Mnist数据集封装成一个单例模式的数字识别检测器。

    DigitsDetector.h
    #ifndef DIGITSDETECTOR
    #define DIGITSDETECTOR
    
    #include <opencv2/opencv.hpp>
    #include <opencv2/highgui/highgui.hpp>  
    #include <opencv2/imgproc/imgproc.hpp>  
    #include <opencv2/core/core.hpp> 
    #include <opencv2/features2d/features2d.hpp>
    #include <opencv2/flann/flann.hpp>
    #include <opencv.hpp>
    #include <opencv2/ml/ml.hpp>
    
    #include <iostream>
    #include <string>
    #include <fstream>
    
    using namespace std;
    using namespace cv;
    using namespace cv::ml;
    
    class DigitsDetector
    {
    public:
    	static DigitsDetector *I();
    
    public:
    	Mat ReadMnistImage(const string pathName);
    	Mat ReadMnistLabel(const string pathName);
    	void CreateSVM();
    	bool Train(Mat data, Mat label);
    	float Predict(Mat testdata);
    
    protected:
    	int CvtToLittleEndian(int i);
    
    private:
    	DigitsDetector();
    	~DigitsDetector();
    	static DigitsDetector *inst;
    
    	Ptr<SVM> svm;
    };
    
    #endif // !DIGITSDETECTOR
    
    
    DigitsDetector.cpp
    #include "DigitsDetector.h"
    
    DigitsDetector *DigitsDetector::inst = new DigitsDetector();
    
    DigitsDetector::DigitsDetector(){ 
    
    }
    
    DigitsDetector::~DigitsDetector(){
    
    	if (inst != NULL){
    
    		delete inst;
    		inst = NULL;
    	}
    }
    
    DigitsDetector *DigitsDetector::I(){
    
    	if (inst == NULL) inst = new DigitsDetector();
    	return inst;
    }
    
    Mat DigitsDetector::ReadMnistImage(const string pathName){
    
    	int magicNumber = 0;
    	int imageNumber = 0;
    	int rows = 0;
    	int cols = 0;
    	Mat dataMat;
    	ifstream file(pathName, ios::binary);
    	if (file.is_open() == true){
    
    		file.read((char*)&magicNumber, sizeof(magicNumber));
    		file.read((char*)&imageNumber, sizeof(imageNumber));
    		file.read((char*)&rows, sizeof(rows));
    		file.read((char*)&cols, sizeof(cols));
    		magicNumber = CvtToLittleEndian(magicNumber);
    		imageNumber = CvtToLittleEndian(imageNumber);
    		rows = CvtToLittleEndian(rows);
    		cols = CvtToLittleEndian(cols);
    
    		// 每张数字图像为一个一维向量,构成imageNumber * (rows * cols)的矩阵
    		dataMat = Mat::zeros(imageNumber, rows * cols, CV_32FC1);
    		for (int i = 0; i < imageNumber / 1; i++){
    
    			for (int j = 0; j < rows * cols; j++){
    
    				unsigned char temp = 0;
    				file.read((char*)&temp, sizeof(temp));
    				float value = float((temp + 0.0) / 255.0);
    				dataMat.at<float>(i, j) = value;
    			}
    		}
    	}
    	file.close();
    	return dataMat;
    }
    
    Mat DigitsDetector::ReadMnistLabel(const string pathName){
    
    	int magicNumber;
    	int labelNumber;
    	Mat labelMat;
    	ifstream file(pathName, ios::binary);
    	if (file.is_open() == true){
    
    		file.read((char*)&magicNumber, sizeof(magicNumber));
    		file.read((char*)&labelNumber, sizeof(labelNumber));
    		magicNumber = CvtToLittleEndian(magicNumber);
    		labelNumber = CvtToLittleEndian(labelNumber);
    
    		labelMat = Mat::zeros(labelNumber, 1, CV_32SC1);
    		for (int i = 0; i < labelNumber / 1; i++){
    
    			unsigned char temp = 0;
    			file.read((char*)&temp, sizeof(temp));
    			labelMat.at<unsigned int>(i, 0) = (unsigned int)temp;
    		}
    	}
    	file.close();
    	return labelMat;
    }
    
    int DigitsDetector::CvtToLittleEndian(int i){
    
    	unsigned char c1, c2, c3, c4;
    	c1 =i & 255;
    	c2 = (i >> 8) & 255;
    	c3 = (i >> 16) & 255;
    	c4 = (i >> 24) & 255;
    	return ((int)c1 << 24) + ((int)c2 << 16) + ((int)c3 << 8) + c4;
    }
    
    void DigitsDetector::CreateSVM(){
    
    	svm = SVM::create();
    	svm->setType(SVM::C_SVC);
    	svm->setKernel(SVM::RBF);
    	svm->setGamma(0.01);
    	svm->setC(10.0);
    	svm->setTermCriteria(TermCriteria(CV_TERMCRIT_EPS, 1000, FLT_EPSILON));
    }
    
    bool DigitsDetector::Train(Mat data, Mat label){
    
    	return svm->train(data, ROW_SAMPLE, label);
    }
    float DigitsDetector::Predict(Mat testdata){
    
    	return svm->predict(testdata);
    }
    

    实验效果

    训练Mnist训练集过程

        训练过程时间有些长,需要耐心等待。我的测试环境为:windows10+i7-6700+8GRAM,debug版本的训练时间大约10分钟,release版本大约5分钟,训练过程如下图:


    测试Mnist测试集的过程和结果

        测试10000张Mnist的测试集,也需要一小段时间,测试结果的准确率为98.33%,如下图。


    测试现场手写数字

        在画板上利用鼠标手写测试数字,点击“开始识别当前数字”识别,实验效果如下图:


    总结

        训练过程比较耗时,但是识别率比较高(相比于KNN好不少,KNN实现可以参考上一篇文章《KNN实现手写数字识别》)。和KNN的区别可以应用知乎上的一句神总结:svm, 就像是在河北和北京之间有一条边界线,如果一个人居住在北京一侧就预测为北京人,在河北一侧,就预测为河北人。但是住在河北的北京人和住在北京的河北人就会被误判。knn,就是物以类聚,人以群分。如果你的朋友里大部分是北京人,就预测你也是北京人。

    附件

        源代码工程戳这里(注:release里面的可执行程序可以直接运行)。

    更多参考

        SVM百度百科:https://baike.baidu.com/item/svm/4385807?fr=aladdin

        SVM笔试题:https://blog.csdn.net/szlcw1/article/details/52259668

        SVM最牛逼的解释:https://www.zhihu.com/question/21094489

        SVM车牌识别:https://blog.csdn.net/u010429424/article/details/75335212

        支持向量机通俗导论(理解SVM的三层境界):https://blog.csdn.net/v_july_v/article/details/7624837

        opencv3-svm参数详解:http://livezingy.com/svm-in-opencv3-1/ 


    展开全文
  • 该系统已经在基准MNIST手写数字基准数据库上进行了测试,使用独立测试集策略已达到99.36%的分类精度。 还使用10折交叉验证策略对分类系统进行了交叉验证分析,并且获得了10折分类精度为99.26%。 所提出的系统的...
  • 最新的论文资源 研究了利用支持向量机分类器进行手写数字识别的...在此库上, 利用基于SVM 多类分类器进行了实验, 并与其他分类器的识别方法进 行了比较。实验结果表明, 算法的正确识别率达到96. 005%, 识别效果最好。
  • svm 手写数字识别 MNIST数据集

    千次阅读 2018-05-08 20:49:48
    利用SVM(支持向量机)和MNIST库在OpenCV环境下实现手写数字0~9的识别https://blog.csdn.net/wenhao_ir/article/details/52160225

    利用SVM(支持向量机)和MNIST库在OpenCV环境下实现手写数字0~9的识别

    六万张训练生成SVM的XML文件用了564秒,随后用训练的结果进行训练数据的测试,一万张测试图片正确的识别个数为9833,错误率为1.67%。


    SVM学习笔记(二)----手写数字识别

    参数选择为C=100.0, kernel=’rbf’, gamma=0.03时,预测的准确度就已经高达98.5%了。

    展开全文
  • # 从sklearn.datasets里导入手写数字加载器。 from sklearn.datasets import load_digits # 从通过数据加载器获得手写数字的数码图像数据并储存在digits变量中。 digits = load_digits() # 检视数据规模和特征...
  • 前言前两天利用kNN实现了手写数字识别...这两天把Mnist-image的手写数字数据down了下来,利用SVM进行识别一下。Mnist-image的手写数字数据有7万的图像数据(6万训练数据+1万测试数据),每个图像数据为 20px * 20px。
  • 基于Hog特征+SVM分类器,利用Opencv3.0进行手写数字识别的源代码及所需资源文件(训练图片、测试图片)
  • 图像处理开发资料、图像处理开发需求、图像处理接私活挣零花钱,可以搜索公众号"qxsf321",并关注! 图像处理开发资料、图像处理开发需求、图像处理接私活挣零花钱,...就是机器在学习时,有先验知识~SVM...
  • 使用KNN和SVM算法实现手写字体识别分类

    千次阅读 热门讨论 2020-12-19 15:25:17
    下面分别采用的是k近邻算法(KNN)和SVM实现的手写数字识别。 项目训练目标 学会调用数据集, 利用Python相关程序从数据集中读取数据 学会根据数据集训练分类器, 并在Python下实现算法 学会运用已学的知识完成实际...
  • 实验四、手写数字识别 一、实验目的 1. 学会用分类算法解决实际问题 二、实验工具 1. Anaconda 2. sklearn 3. matplotlib 4. pandas 三、实验简介 概念介绍 图像识别(Image Recognition)是指利用计算机对图像进行...
  • 摘要:本文详细介绍如何利用MATLAB实现手写数字识别,其中特征提取过程采用方向梯度直方图(HOG)特征,分类过程采用性能优异的支持向量机(SVM)算法,训练测试数据集为学术及工程上常用的MNIST手写数字数据集,...
  • 对手写数据集50000张图片实现阿拉伯数字0~9识别,并且对结果进行分析准确率, 手写数字数据集下载:http://yann.lecun.com/exdb/mnist/ 首先,利用图片本身的属性,图片的灰度平均值进行...其次,利用SVM算法,我...
  • 手写数字识别 vs+opencv

    2020-06-21 10:27:05
    0.引言: 支持向量机( Support Vecto r Machine, 简称SVM )是统计学习理论中最年轻的部分,主要内容1992年~ 1995年才完成,统计学习理论之所以受...我们利用支持向量机对手写数字识别的实进行探讨。实验中所采用的图
  • 配置:WIN7下Qt5.8+VS2013+Opencv3.0基于Qt5.8的简单手写数字识别界面是在 Opencv3.0 手写数字识别(Hog特征+SVM分类器)的基础上利用Qt5.8编写的一个简单界面。在该界面中可以通过鼠标手写输入数字进行识别,该...
  • 手写数字图像的识别,恰好之前在看机器学习和深度学习的书里也接触了一些相关东西,初步的想法是利用神经网络和BP算法进行图像的处理和相似度校正(也可以采用SVM或者K近邻算法,或者cnn神经网络等),误差损失函数...
  • 本ppt前两部分展示了作者利用PCA、SVM、ANN对ORL、MNIST数据集进行分类效果的比较及分析,同时最后一部分展示了GAN在图像生成中的简单应用

空空如也

空空如也

1 2 3
收藏数 47
精华内容 18
关键字:

利用svm识别手写数字