android loadsvm raw,OpenCV机器学习:Android上利用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

version="1.0" encoding="utf-8"?>

"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">

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!" />

id="@+id/handWriteView"

android:layout_below="@id/intro"

android:layout_width="match_parent"

android:background="@drawable/draw_background"

android:layout_height="400dp" />

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" />

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" />

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: " />

在界面设计上,除了两个交互性的按钮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

^-^ 欢迎交流讨论!

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值