2014-05-19 10:37:22 u012809352 阅读数 2017
  • 易语言入门精品课程

    中文编程.易语言入门精品课程.讲师结合十几年的易语言学习经验.站在纯新手角度详细带你进入中文编程易语言的世界.本教程采用循序渐进原则构造.结合大量实战例子.相比于传统教程.学习本教程.更能增加编程逻辑.

    59 人正在学习 去看看 马龙

首先导入科大讯飞工具包

MainActivity源代码:

package com.example.viocedemo;


import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;


import com.iflytek.cloud.speech.RecognizerResult;
import com.iflytek.cloud.speech.SpeechConstant;
import com.iflytek.cloud.speech.SpeechError;
import com.iflytek.cloud.speech.SpeechListener;
import com.iflytek.cloud.speech.SpeechSynthesizer;
import com.iflytek.cloud.speech.SpeechUser;
import com.iflytek.cloud.speech.SynthesizerListener;
import com.iflytek.cloud.ui.RecognizerDialog;
import com.iflytek.cloud.ui.RecognizerDialogListener;


public class MainActivity extends Activity implements OnClickListener,
SynthesizerListener {
private EditText editText;
private Button button1;
private Button button2;
// 合成对象
private SpeechSynthesizer speechSynthesizer;
// 识别窗口
private RecognizerDialog recognizerDialog;


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);


// appid换成自己申请的
SpeechUser.getUser().login(MainActivity.this, null, null,
"appid=12345678", listener);


init();
setParam();


}


/**
* 初始化UI
*/
public void init() {
editText = (EditText) findViewById(R.id.editText1);
button1 = (Button) findViewById(R.id.button1);
button2 = (Button) findViewById(R.id.button2);
button1.setOnClickListener(this);
button2.setOnClickListener(this);
}


/**
* 初始化语音类别
*/
public void setParam() {
speechSynthesizer = SpeechSynthesizer.createSynthesizer(this);
speechSynthesizer.setParameter(SpeechConstant.VOICE_NAME, "xiaoyan");
speechSynthesizer.setParameter(SpeechConstant.SPEED, "50");
speechSynthesizer.setParameter(SpeechConstant.VOLUME, "50");
speechSynthesizer.setParameter(SpeechConstant.PITCH, "50");
}


/**
* 识别语音的弹出框
*/
public void setDialog() {
recognizerDialog = new RecognizerDialog(this);
recognizerDialog.setParameter(SpeechConstant.DOMAIN, "iat");
recognizerDialog.setParameter(SpeechConstant.SAMPLE_RATE, "16000");
editText.setText(null);
// 显示Dialog
recognizerDialog.setListener(dialogListener);
recognizerDialog.show();
}


/**
* 识别回调监听器
*/
private RecognizerDialogListener dialogListener = new RecognizerDialogListener() {
// 识别结果回调
@Override
public void onResult(RecognizerResult arg0, boolean arg1) {
// TODO Auto-generated method stub
String text = JsonParser.parseIatResult(arg0.getResultString());
editText.append(text);
editText.setSelection(editText.length());
}


// 识别结束回调
@Override
public void onError(SpeechError arg0) {
// TODO Auto-generated method stub


}
};


@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}


@Override
public void onClick(View v) {
// TODO Auto-generated method stub
switch (v.getId()) {
case R.id.button1:// 语音播放按钮
String text = editText.getText().toString();
speechSynthesizer.startSpeaking(text, this);
break;
case R.id.button2:// 语音识别
setDialog();
break;
default:
break;
}
}


// 缓冲进度回调通知
@Override
public void onBufferProgress(int arg0, int arg1, int arg2, String arg3) {
// TODO Auto-generated method stub


}


// 结束回调
@Override
public void onCompleted(SpeechError arg0) {
// TODO Auto-generated method stub

}


// 开始播放
@Override
public void onSpeakBegin() {
// TODO Auto-generated method stub
}


// 暂停播放
@Override
public void onSpeakPaused() {
// TODO Auto-generated method stub

}


// 播放进度
@Override
public void onSpeakProgress(int arg0, int arg1, int arg2) {
// TODO Auto-generated method stub

}


// 继续播放
@Override
public void onSpeakResumed() {
// TODO Auto-generated method stub

}


/**
* 通用回调接口
*/
private SpeechListener listener = new SpeechListener() {


// 消息回调
@Override
public void onEvent(int arg0, Bundle arg1) {
// TODO Auto-generated method stub


}


// 数据回调
@Override
public void onData(byte[] arg0) {
// TODO Auto-generated method stub


}


// 结束回调(没有错误)
@Override
public void onCompleted(SpeechError arg0) {
// TODO Auto-generated method stub

}
};


}


JsonParser类源代码:

package com.example.viocedemo;


import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONTokener;


import android.text.TextUtils;


//import com.iflytek.speech.ErrorCode;
//import com.iflytek.speech.SpeechError;
/**
 * 对云端返回的Json结果进行解析
 * @author iFlytek
 * @since 20131211
 */
public class JsonParser {

/**
* 听写结果的Json格式解析
* @param json
* @return
*/
public static String parseIatResult(String json) {
if(TextUtils.isEmpty(json))
return "";

StringBuffer ret = new StringBuffer();
try {
JSONTokener tokener = new JSONTokener(json);
JSONObject joResult = new JSONObject(tokener);


JSONArray words = joResult.getJSONArray("ws");
for (int i = 0; i < words.length(); i++) {
JSONArray items = words.getJSONObject(i).getJSONArray("cw");
JSONObject obj = items.getJSONObject(0);
ret.append(obj.getString("w"));
}
} catch (Exception e) {
e.printStackTrace();
} 
return ret.toString();
}

}


UI布局:


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >


    <EditText
        android:id="@+id/editText1"
        android:layout_width="match_parent"
        android:layout_height="250dip"
        android:gravity="top"
        android:ems="10" >


        <requestFocus />
    </EditText>


    <Button
        android:id="@+id/button1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="语音播放" />


    <Button
        android:id="@+id/button2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="语音听写" />


</LinearLayout>

源代码下载

2010-07-26 23:18:00 deepken 阅读数 712
  • 易语言入门精品课程

    中文编程.易语言入门精品课程.讲师结合十几年的易语言学习经验.站在纯新手角度详细带你进入中文编程易语言的世界.本教程采用循序渐进原则构造.结合大量实战例子.相比于传统教程.学习本教程.更能增加编程逻辑.

    59 人正在学习 去看看 马龙

在实验室待了一段时间了,在实验室的报告及小组会议中,深刻的体会到了HMM算法 ,SVM等的重要性.

 这几天翻译了一篇使用隐马尔科夫模型(HMM)进行语音识别的论文的部分. 

这篇论文是通过google的学术搜索搜到的,通过在互联网上的查找,发现这篇论文引用率很高。(排在google的第一位),并且没发现对其的汉语翻译。或许这篇是对该文的第一篇汉语翻译。原文一共有25页,由于时间的原因,只翻译其中部分内容。并对翻译较多的部分贴上原文对照。


使用隐马尔科夫进行语音识别
作者:B.H.Juang,L.R.Rabiner
语言语音研究所,Bell实验室
Murray Hill,NJ 07974

摘要:
       在近几年发表的论文和大型语言语音会议上中,隐马尔科夫定律已经成为语音识别研究的主导方法。这个方法之所以如此流行就在于其固有的统计框架:从有限语音训练集数据中训练出模型近似参数的简单易行;模型可根据特殊的词汇、声音等改变认知系统的大小、种类或模型的架构的灵活多变;实现整个认知系统的简单方便。在这篇解释性的文章中,我们将讲解应用在语音识别中的非常重要的统计方法,并讨论一系列尚未解决的原理性的和实际性的问题,因为他们很重要并对不同系统实现的性能有很大影响。
关键词:
       Baum-Welch算法,Incomplete data problem ,Maximum a posteriori decoding;极大似然度
 
       机器语音识别已经达到了可以投入到实际使用的水平了。大量的语音识别系统已经应用在众多应用领域如语音拨号、语音应答、语音查询股价、语音报价等。导致这些有用的技术能够应用于实际是因为最近技术的进步使得语音认知系统能辨别不同的说话者并达到了一定量的认知词汇。其中的一项进步就是统计方法的使用,马尔科夫模型就是其中一个很有趣的方法。
       使用HMM来进行语音识别在过去的一段时间内很流行。虽然报告过的大量基于HMM的语音认知系统不易在此深入地讨论,列出其中最重要的部分和这些系统的成功之处仍然是值得的。其中包括在卡内基梅隆大学早期进行的Dragon System的工作,IBM公司在语音系统方面进行的长期的工作,在Bell实验室的工作,MIT林肯实验室的工作,Philips在使用HMM进行的整词识别的工作,DARPA资源管理任务,及其它在该相关领域的众多的工作。HMM的广泛流行可以归功于它简单的算法结构和它相对于其它语音识别方法的清晰高效性。
       性能,特别是精度,是评价一个语音认知系统实际价值的关键因素。语音识别任务经常根据它的需求,如是处理特定的还是非特定说话者,处理单个词汇的输入还是连续的一个句子的输入,来进行分类。如今,该技术能够轻松达到对非特定说话者的精确识别,当识别由非特定说话者说出的连续数字字串时,错误率仅有2-3%.更进一步,但在非特定说话者以特定的语法限制说出连续1000个词时,一些使用HMM的系统证实可以达到96%的识准率。这些结果说明了自动语音识别系统在指定的应用中的有用性和可用性。
       虽然隐马尔科夫模型显著地改善了当前语音识别系统的性能。完全流利的、非特定说话者的语音识别仍是一个普遍存在并等待着解决的问题。例如,没有一个系统能够识别没有限制(话题)的对话语音,也没有一个好的方法使用借助于有限语料库的统计方法去推断语言的结构。这篇解释性的文章的目的是提供HMM的原理的一个概述,讨论统计方法的作用,并指出一系列值得注意和理解的原理性和实践性问题,以便于推动语音识别这一领域的发展。
 
1.语音的度量和建模
语音是不稳定的信号量。当我们说话时,我们的发音器官(嘴唇、下颚、舌头,如图1所示)调节空气压力并影响气流产生一系列的声音。虽然任何一个声音的范围会是在几千赫兹的范围内,我们的关节配置(声道形状,舌头移动等)经常不能忍受每秒超过10次的动态变化。语音建模包括两个方面:(1)以10毫秒采样分析不同声音的短时间的范围属性,(2)根据关节配置的不同,以100毫秒采样去分析长时间声音的变化特征。
 
2.隐马尔科夫模型统计方法
       在HMM方法发展的过程中,如下问题显得特别有意思。首先,给出一个观察序列O和一个模型λ,我们怎么样有效的度量模型λ产生观察序列O的概率,即Pr(O|λ)?第二,给出观察序列O,反过来我们怎么解决估算模型λ中的参数?虽然(8)中的概率不完全依赖于q,(译者注:(8)是在论文前出现的一个公式),关于导致观察序列O的最可能的状态序列q的信息在很多的应用中都是需要的。第三个问题就是怎么有效地从观察序列O中推出最有可能的状态序列q.通常我们将这三个问题称为(1)评估问题(2)估计问题(3)解释问题。
在下面的段落中,我们将描述几个对这三个问题通用的解决方法。
2.1 评估问题
       在评估问题中主要关注的是计算的效率。如果没有复杂度约束,可以直接简单的直接计算出Pr(O|λ).在公式(8)中,一共有个可能的q序列。总共的计算需要2*T*个操作。计算公式(8)同时没有指数级增长的计算量,是HMM技术实现的第一个挑战。
幸运的是,使用著名的前向-后向算法,这个昂贵的计算开销可以轻松的减轻。
 
2.2 估计问题
       给出一个观察序列(或一个序列的集合)O.估计问题包括找到合适的模型参数使模型最可能产生给定的序列。在语音识别中,这经常被称为“训练”。我们用来获取模型参数的给定序列,被成为训练序列,即使这儿的准则是统计的。
 
2.3 解释问题
       正如前面所说的,我们经常对找到产生观察序列O极大似然度的状态序列感兴趣。
虽然HMM的概率度量定义中没有涉及到状态序列,在很多的应用场合中仍然需要知道极大似然度的状态序列。举个例来说,如果我们使用一个词汇模型的状态来代表该词汇中的特定的声音,就有必要知道语音片段和词的声音之间的关系,因为单独的语音片段为语音识别提供了有用信息。
 
2.4 使用HMM进行语音识别
       HMM在语音识别中的应用和其他传统的模式匹配方法差不多。成功的使用HMM算法包括一下步骤:
1.定义一个用来建模的L声音类的集合。例如音素或词汇,定义声音类V={v1,v2,..,v3};
2. 对于每一个类,积累一定量的已知的标记语音集合。
3.在训练集合的基础上,解决估计问题,为每个类Vi获取一个最好的模型λi.
4. 在认识的过程中,对每个未知观察序列O估计Pr(O|λi)(i=1,2,…,L)),并为每个类Vi确定产生O的语音.其满足:
Pr(O|λi) =  Pr(O|λi)
本文将不详细地描述如何实现一个HMM识别器。感兴趣的读者可以阅读Jelinek,Bahl,Mercer(1975)及Levinson,Rabiner,Sondhi(1983)的文章。
 
 
3.使用隐马尔科夫模型进行语音识别的优点
       HMM方法的优点体现在两个大的方面:(1)它的数学框架和(2)它的实现结构。在数学框架方面,我们讨论问题的连续统计方法学和它为相关问题提供的直接的解决方案。
在实现结构方面,我们讨论它在处理不同的、复杂的语音认知任务的灵活性和实现的简单性,这些都是在实际工程领域中需要考虑的关键问题。
3.1          HMM方法学的连续统计框架
3.2          HMM的训练算法
3.3          模型灵活性
 
4.进一步考虑隐马尔科夫定理的问题
5.总结
       在这篇文章中,我们复习了HMM的统计学方法,展示了这个方法的统计学框架及由其带来的灵活性和通用性,特别是在语音识别方面,以及其实现的简单性,使其在工程实现方面显出优势。我们还指出了在一般的HMM方法中值得注意的方面,希望有人能在这些方面取得进步,这些进步将会大大提高性能。这些领域包括建模标准,特别是最小分类错误,将新的特征和之前的语言学知识的融合,对状态的建模和其在语音识别领域中的应用。根据我们现在的理解,HMM系统识别非特定说话者在一定量词汇量的语音识别率已经高达95%。随着技术的发展,不难预料到基于HMM模型的语音识别系统将能应用到我们的日常生活中去。

 

2016-11-25 09:25:18 sparkexpert 阅读数 3467
  • 易语言入门精品课程

    中文编程.易语言入门精品课程.讲师结合十几年的易语言学习经验.站在纯新手角度详细带你进入中文编程易语言的世界.本教程采用循序渐进原则构造.结合大量实战例子.相比于传统教程.学习本教程.更能增加编程逻辑.

    59 人正在学习 去看看 马龙

前段时间,微软开源了认知服务的工具箱,直到近期才有时间进行测试。


看了文档,这个CNTK工具包还是非常厉害的,可以支持语音识别,图像分类,机器翻译等多种任务。里面也集成了多种深度学习的模型。such as deep neural networks (DNNs), convolutional neural networks (CNNs), recurrent neural networks (RNNs), long short term memory (LSTM), logistic regression, and maximum entropy model, that can be illustrated as a series of computational steps


基本上将主流的深度学习框架都涵盖在里面了。


(1) 安装步骤:

准备工作

1、 现在编译好的win下的安装包:https://github.com/Microsoft/CNTK/releases,(有不同版本)

2、 解压到下面文件夹:E:\\cntk安装\CNTK-2-0-beta4-0-Windows-64bit-GPU-1bit-SGD


安装必要环境:

E:\cntk安装\CNTK-2-0-beta3-0-Windows-64bit-GPU-1bit-SGD \cntk\prerequisites所有文件,以及安装python3.4以上版本,并安装https://repo.continuum.io/archive/Anaconda3-4.1.1-Windows-x86_64.exe

说明:本文Anaconda3安装目录为:E:\Anaconda3


创建cntk python环境

进入目录E:\Anaconda3\scripts

 

使用命令:conda env create --file E:\zhangxiong\cntk安装\CNTK-2-0-beta4-0-Windows-64bit-GPU-1bit-SGD\cntk\Scripts\install\windows\conda-windows-cntk-py34-environment.yml

注:这个过程可能无法一次成功,使用命令conda clean –-lock解锁之后,重新执行上述命令


pipinstall  E:\cntk安装\CNTK-2-0-beta4-0-Windows-64bit-GPU-1bit-SGD\cntk\cntk\Python\cntk-2.0.beta4.0-cp34-cp34m-win_amd64.whl

 

安装CNTK python 环境

安装上述教材会报错:xxxx.whl is not asupported wheel on this platform

这是由于官方教程中python版本不一致(可能是更新所致),因此需要将cntk-2.0.beta3.0-cp34-cp34m-win_amd64.whl文件改名为cntk-2.0.beta3.0-cp35-cp35m-win_amd64.whl

再执行上述代码:


设置环境变量:

setx PATH=E:\cntk安装\CNTK-2-0-beta4-0-Windows-64bit-GPU-1bit-SGD\cntk\cntk;%PATH%


测试验证安装是否成功

进入example目录:E:\cntk安装\CNTK-2-0-beta4-0-Windows-64bit-GPU-1bit-SGD\cntk\Examples\Tutorials\LogisticRegressionAndMultiClass

 使用命令:

cntkconfigFile=lr_bs.cntk makeMode=false command=Train


训练成功:目录中生成model 文件夹,如图所示



训练并测试一个算法:

cntk configFile=lr_bs.cntk makeMode=false command=Train :Output:DumpNodeInfo:Test

根据测试文件生成了MC.txt.z测试结果文件(在Linux下可打开查看)


语音测试speech

进入目录E:\cntk安装\CNTK-2-0-beta4-0-Windows-64bit-GPU-1bit-SGD\cntk\Examples\Speech\AN4\Data

输入命令:

cntk configFile=../Config/FeedForward.cntk


同样可以得到训练模型


后续根据该训练模型就可以实现语音的识别。


2018-06-15 18:10:52 dxh1994 阅读数 1117
  • 易语言入门精品课程

    中文编程.易语言入门精品课程.讲师结合十几年的易语言学习经验.站在纯新手角度详细带你进入中文编程易语言的世界.本教程采用循序渐进原则构造.结合大量实战例子.相比于传统教程.学习本教程.更能增加编程逻辑.

    59 人正在学习 去看看 马龙

验证码介绍

        现在的验证码主要分为4类:识图验证码、计算验证码、滑块验证码、语音验证码。本文只针对taobao的识图验证码进行识别。目标验证码如下:

   

        可以看到这类验证码都是变形、粘连、大小不一、位置不固定的,不再像以前的可分割的验证码了,也就增加了机器识别的难度。这类粘连验证码就是本文主要的研究对象。

识别步骤

(1)图片预处理

        灰度化、 二值化、去噪声(本文验证码无噪声,没有此步骤)等

(2)字符切割

        CFS连通域分割、滴水法

(3)分类模型训练

        支持向量机或神经网络。本文训练的是卷积神经网络

图片预处理

        图片预处理之后的效果:


(1)灰度化

        彩色图片的像素点是由R、G、B三个原色组成,其值在0-255之间,有三个通道。灰度图是指只含有亮度信息,不含彩色信息的图像,即只有一个通道。灰度化就是将彩色图像转为灰度图像的过程。本文采用的灰度化方法是加权平均法。

def rgb2gray(img): # 求灰度值
    return np.dot(img[...,:3], [0.299, 0.587, 0.114])

(2)二值化

        二值化是指将灰度图像转换成只有黑白两色的图像,通常做法是选择一个阈值,如果灰度图像上的像素大于该阈值则为白色,否则为黑色。

def binary(gray):# 二值化
    height,width=gray.shape
    bin = gray.copy()
    for i in range(height):
        for j in range(width):
            if (gray[i,j]<=130):
                bin[i,j]=0
            else:
                bin[i,j]=1
    return bin

字符切割

        由于粘连的字符不好识别,所以我们将粘连的字符切割为单个字符,再训练模型进行识别。

(1)竖直投影法

        竖直投影法是最简单的字符分割方法,根据相邻的波谷或者极大极小值来确定字符边界,常用于字符没有粘连的情况,如车牌识别。但是由于目标验证码倾斜程度不小并且大多粘连,对于倾斜严重但实际并未粘连的两个字符,竖直投影的效果不太好,因此未采用这种方法。

(2)CFS连通域分割法

        假定每个字符由单独的一个连通域组成,因此只需要将不同连通域加以标记,就可分割出字符。此方法要求字符不粘连,无论旋转或扭曲。找到一个黑色像素并开始判断,直到所有相连的黑色像素都被遍历标记过后即可判断出字符的分割位置。

其算法原理:

        1.对二值化之后的图像,从左往右,从上往下扫描图片,遇到黑点,并且该点没有被访问,则将该点压栈,并标记为已经访问。

        2.如果栈不空,则继续探测周围4个点(四邻域,也可选择八邻域),并执行步骤1;如果栈空,则代表已经探测完一个字符块,并将该字符块的最左最右、最上最下坐标保存(本文只保存左右)。

        3.探测结束,最后通过每个字符的坐标进行切割。

代码(复制别人的:https://www.cnblogs.com/qqandfqr/p/7866650.html):

# ---------------------------------------CFS连通域------------------------------------------
import queue
def cfs(im,x_fd,y_fd): # 用队列和集合记录遍历过的像素坐标代替单纯递归以解决cfs访问过深问题
    xaxis=[]
    yaxis=[]
    visited =set()
    q = queue.Queue()
    q.put((x_fd, y_fd))
    visited.add((x_fd, y_fd))
    offsets=[(1, 0), (0, 1), (-1, 0), (0, -1)]#四邻域

    while not q.empty():
        x,y=q.get()
        for xoffset,yoffset in offsets:
          x_neighbor,y_neighbor = x+xoffset,y+yoffset

          if (x_neighbor,y_neighbor) in (visited):
              continue  # 已经访问过了

          visited.add((x_neighbor, y_neighbor))

          try:
              if im[x_neighbor, y_neighbor] == 0:
                  xaxis.append(x_neighbor)
                  yaxis.append(y_neighbor)
                  q.put((x_neighbor,y_neighbor))
          except IndexError:
              pass
    # print(xaxis)
    if (len(xaxis) == 0 | len(yaxis) == 0):
        xmax = x_fd + 1
        xmin = x_fd
        ymax = y_fd + 1
        ymin = y_fd
    else:
        xmax = max(xaxis)
        xmin = min(xaxis)
        ymax = max(yaxis)
        ymin = min(yaxis)
        #ymin,ymax=sort(yaxis)
    return ymax,ymin,xmax,xmin

def detectFgPix(im,xmax): # 搜索区块起点
    h,w = im.shape[:2]
    for y_fd in range(xmax+1,w):
      for x_fd in range(h):
          if im[x_fd,y_fd] == 0:
              return x_fd,y_fd

def CFS(im): # 切割字符位置

    zoneL=[]#各区块长度L列表
    zoneWB=[]#各区块的X轴[起始,终点]列表
    zoneHB=[]#各区块的Y轴[起始,终点]列表

    xmax=0#上一区块结束黑点横坐标,这里是初始化
    for i in range(10): #几个字符,最终运行几次
      try:
          x_fd,y_fd = detectFgPix(im,xmax)
          # print(y_fd,x_fd)
          xmax,xmin,ymax,ymin=cfs(im,x_fd,y_fd)
          L = xmax - xmin
          H = ymax - ymin
          zoneL.append(L)
          zoneWB.append([xmin,xmax])
          zoneHB.append([ymin,ymax])
      except TypeError:
          return zoneL,zoneWB,zoneHB
    return zoneL,zoneWB,zoneHB

def cutting_img(im,im_position,xoffset = 1,yoffset = 1):
    cut = []
    # filename =  './out_img/' + img.split('.')[0]
    # 识别出的字符个数
    im_number = len(im_position[1])
    # 切割字符
    for i in range(im_number):
        im_start_X = im_position[1][i][0]
        im_end_X = im_position[1][i][1]
        # im_start_Y = im_position[2][i][0]
        # im_end_Y = im_position[2][i][1]
        # 判断一下
        # if im_start_X > 0 : # 非边界
        #     im_start_X = im_start_X - xoffset
        # if im_end_X < im.shape[1]:  # 非边界
        #     im_end_X = im_end_X + xoffset
        # if im_start_Y > 0: # 非边界
        #     im_start_Y = im_start_Y -yoffset
        # if im_end_Y < im.shape[0]: # 非边界
        #     im_end_Y = im_end_Y + yoffset
        cropped = im[0:im.shape[0]+1, im_start_X:im_end_X]
        # cv2.imwrite(filename + '-cutting-' + str(i) + '.jpg',cropped)
        cut.append(cropped)
    return cut

(3)滴水算法

        滴水算法主要是模仿水滴从高处向低处落的过程来对粘连字符进行切割。水滴从字符串顶部在重力的作用下,只能沿字符轮廓向下滴落或水平滚动(周围5个点,具有优先级)。最终水滴所经过的轨迹就构成了字符的切割路径。

        滴水法决定因素主要包括:起始点、移动规则以及方向的确定。原理自行百度。

代码(复制别人的,参考链接https://www.jianshu.com/p/deee3e7e463b,好像原作者已经删除):

# --------------------------------------------------滴水法--------------------------------------------------------------
def binImgHist(img, bin_width=1, direction=1): # 二值化图像在y轴或者x轴方向的投影统计
    height, width = img.shape
    bins = None
    if direction == 0:# 在y轴方向上统计
        bins = int(height / bin_width)
    else:
        bins = int(width / bin_width)
    # 获取非零元素坐标
    nonzero_points = np.nonzero(img != 0)
    # 获取非零元素坐标中的x坐标集合或者y坐标集合
    nonzero_idx_x = nonzero_points[direction]
    # 返回的统计直方图
    hist = np.histogram(np.int64(nonzero_idx_x), bins=bins)[0]
    return hist

COLOR_BACK = 1 # 背景颜色
COLOR_PEN = 0 # 笔触的颜色

# 首先将原来的图片转换成布尔矩阵
# bool_img = gray == COLOR_PEN
# print(bool_img[15:25,15:25])

# 定义邻居坐标 的位置关系
# NEIGHBOR_IDX = {1: (-1, 0), 2: (-1, 1), 3: (0, 1), 4: (1, 1), 5: (1, 0)}

# 根据不同优先级情况,对应的下一个点的位置 n_i
# CASE_NEXT_POINT = {1: 3, 2: 2, 3: 3, 4: 4, 5: 5, 6: 1}

def is_case_p1(n1, n2, n3, n4, n5):
    '''
    优先级 :1
    下落方向: 正下方
    下落位置: n3
    备注: 全为背景或者全为笔迹
    '''
    if n1 and n2 and n3 and n4 and n5 == True:
        # 全为数字部分
        return True
    elif not (n1) and not (n2) and not (n3) and not (n4) and not (n5) == True:
        # 全为背景部分
        return True
    return False

def is_case_p2(n1, n2, n3, n4, n5):
    '''
    优先级 :2
    下落方向: 左下方
    下落位置: n2
    备注: n2点为背景,且其他点至少有一个为笔迹的颜色
    '''
    return not (n2) and (n1 or n3 or n4 or n5)

def is_case_p3(n1, n2, n3, n4, n5):
    '''
    优先级 :3
    下落方向: 正下方
    下落位置: n3
    备注: 左下角为笔迹的颜色,正下方为背景色
    '''
    return n2 and not (n3)

def is_case_p4(n1, n2, n3, n4, n5):
    '''
    优先级 :4
    下落方向:右下方
    下落位置: n4
    备注: 左下角跟正下方为笔迹的颜色,右下方为背景色
    '''
    return n2 and n3 and not (n4)

def is_case_p5(n1, n2, n3, n4, n5):
    '''
    优先级 :5
    下落方向:右边
    下落位置: n5
    备注:下方全为笔迹颜色,且左边为背景色
    '''
    return n2 and n3 and n4 and not (n5)

def is_case_p6(n1, n2, n3, n4, n5):
    '''
    优先级 6
    下落方向:左边
    下落点:n1
    备注 除了左边是背景色,其他均为笔迹颜色
    '''
    return not (n1) and n2 and n3 and n4 and n5

def drop_fall(neighbors,CASE_NEXT_POINT = {1: 3, 2: 2, 3: 3, 4: 4, 5: 5, 6: 1}):
    '''
    传统滴水算法  核心代码
    根据优先级实现
    neighbors = [n1, n2, n3, n4, n5]

    返回 :下落点的邻居编号
    '''
    for priority in range(1, 7):
        if eval('is_case_p{}(*neighbors)'.format(priority)):
            return CASE_NEXT_POINT[priority]

def is_legal_pt(img, x, y):
    '''
    是否为合法的坐标
    判断是否超出正常的数值范围
    '''
    h, w = img.shape
    if x < 0 or y < 0 or x >= w or y >= h:
        return False
    return True

def get_neighbors(bool_img, x, y,NEIGHBOR_IDX = {1: (-1, 0), 2: (-1, 1), 3: (0, 1), 4: (1, 1), 5: (1, 0)}):
    '''
    给定逻辑图跟坐标, 返回邻居的有序布尔数组
    '''
    neighbors = []
    for n_idx in range(1, 6):
        dx, dy = NEIGHBOR_IDX[n_idx]
        new_x, new_y = x + dx, y + dy
        if not is_legal_pt(bool_img, new_x, new_y):
            # 如果坐标不合法 填充背景
            neighbors.append(False)
        else:
            neighbors.append(bool_img[new_y][new_x])
    # print(neighbors)
    return neighbors

def get_split_path(bool_img,start_x,height,width,NEIGHBOR_IDX = {1: (-1, 0), 2: (-1, 1), 3: (0, 1), 4: (1, 1), 5: (1, 0)}): # 给出分割路径
    min_x = 0  # x坐标的左边界
    max_x = width - 1  # x坐标的右边界
    max_y = height - 1
    # 当前的点
    cur_pt = (start_x, 0)
    # 最近的点
    last_pt = cur_pt
    # 分割路径
    split_path = [cur_pt]

    while cur_pt[1] < max_y:
        neighbors = get_neighbors(bool_img, cur_pt[0], cur_pt[1])
        n_idx = drop_fall(neighbors)
        dx, dy = NEIGHBOR_IDX[n_idx]
        next_pt = None
        next_x, next_y = cur_pt[0] + dx, cur_pt[1] + dy
        if not is_legal_pt(bool_img, next_x, next_y):
            # x/y越界 向下渗透
            next_pt = (cur_pt[0], cur_pt[1] + 1)
        else:
            next_pt = (next_x, next_y)
        # 判断点是否重复出现,左右往复平移
        if next_pt in split_path:
            # 已经判断重复,进行渗透
            next_pt = (cur_pt[0], cur_pt[1] + 1)

        cur_pt = next_pt
        split_path.append(cur_pt)
    return split_path

切割后效果:

分类模型

        将验证码切割好,手动打标10000张单个验证码字符,再利用keras训练一个类似于mnist例子的卷积模型。在测试集(400张验证码,260张有粘连)上测试,单个验证码字符准确率有0.88,整体验证码准确率有0.68,结果还算勉强。实践证明,这类验证码有粗有细、倾斜粘连,不像mnist那么规范,样本量还是太少,泛性不高,但是人工打标又太费时间。

数字验证码

对于taobao的6位数字验证码也进行了尝试,目标验证码及切割效果如下:


切割过程及效果如下图:


taobao这种验证码很坑的地方在于,第一个字符或最后一个字符只出现部分,甚至偶尔只有5位字符,如下图:


参考链接:

        https://www.cnblogs.com/qqandfqr/p/7866650.html

        https://www.jianshu.com/p/deee3e7e463b

2019-10-30 20:18:50 sd4567855 阅读数 103
  • 易语言入门精品课程

    中文编程.易语言入门精品课程.讲师结合十几年的易语言学习经验.站在纯新手角度详细带你进入中文编程易语言的世界.本教程采用循序渐进原则构造.结合大量实战例子.相比于传统教程.学习本教程.更能增加编程逻辑.

    59 人正在学习 去看看 马龙

自然语言与编程语言

  1. 自然语言中词汇比编程语言中词汇丰富。
  2. 自然语言是非结构化,编程语言是结构化的。结构化:信息具有明确的结构关系。
  3. 自然语言含有大量歧义。
  4. 容错性。
  5. 易变性。
  6. 简略性。

自然语言处理层次

  1. 语音、图像、文本,其中文本是重中之重。
  2. 词法分析:将文本分割为有意义的词语(中文分词),确定每个词语的类别和浅层的歧义消除(词性标注),识别出专有名词(命名实体识别)。
  3. 信息抽取。
  4. 文本分类、文本聚类。
  5. 句法分析。
  6. 语义分析:确定一个词在语境中的含义,而不是词性(词义消除),标注句子中的谓语和其他成分的关系(语义角色标注),分析句子中词语之间的语义关系(语义依存分析)。

自然语言处理的流派

  1. 基于规则的专家系统
  2. 基于统计的学习方法
  3. 传统方法与深度学习

机器学习

  1. 模型:被学习的算法。
  2. 特征。
  3. 数据集/语料库。
  4. 监督学习。
  5. 无监督学习。
  6. 半监督学习。

语料库

  1. 中文分词语料库
  2. 词性标注语料库
  3. 命名实体识别语料库
  4. 词法分析语料库
  5. 文本分类语料库
没有更多推荐了,返回首页