Android OpenCV应用篇三:提取图片中的文字
上篇我们大致了解了如何运用OpenCV在Android上进行图片但简单处理
Android OpenCV应用篇二:图片处理
接下来我们就运用之前的一些相关技术来搞点事情:
如何从一张图片中将文字提取出来?
前言
在开始之前,我们先来看一个字
高
可以看到,这个字体是上中下结构,相应的,我们的汉字还有左中右结构等等,我们如何在识别的过程中,不会吧这些特殊结构的字体识别成多个?
这里我们就用到了我们前一篇中的技术:**腐蚀**
看一下高字的腐蚀效果:
可以看出,完全融为一体,这为我们后面准确的提取文字区域提供了基础。
当然,我们传统的都是认为白纸黑字,但实际上并不一定都是这一,所以在此之前我们需要进行排除环境影响,如何处理?
这里也是用到我们上一篇但知识:
- 降噪
- 阈值化
开始我们的文字提取
OK前面大致介绍了一下基本操作,我们下面总结一下,然后开始实现
步骤:
- 阈值化
- 腐蚀
- 降噪
- 文字区域检测
- 文字提取
第一步:阈值化&腐蚀
由于字体大小,或其他一些字体间的间距、行宽等影响,不同的字体图片可能需要用到等腐蚀效果不同,这里我们只进行原理上的处理,我选了一张比较适中等图片进行操作演示。
/*** 阈值化,并腐蚀* @param src*/private void erode(Mat src) {// 阈值化Imgproc.threshold(src, src, 100, 255, Imgproc.THRESH_BINARY);
// Imgproc.adaptiveThreshold(opMat,opMat,255.0,Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C,Imgproc.THRESH_BINARY,3,0.0);Mat erodeKernel = Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size(5, 5));Imgproc.erode(src, src, erodeKernel);}
第一步处理效果:
可以看出,完全转换成了白纸黑字,标题上等红色背景框完全除去掉了。但可以看出有明显的噪声,比如身字左下角有一个点等。
第二步 滤波降噪
这里我们采用最为简单的中值滤波进行降噪
/*** 采用中值滤波进行降噪** @param src*/private void medianBlur(Mat src) {Imgproc.medianBlur(src, src, 7);}
处理后的效果:
与上一步骤相比可以看出,噪声明显减少。
第三步 区域检测
区域检测,这里我们采用检测连通区域再根据联通区域计算边框的方式进行检测,当然,还有其他的一些检测方法,比如轮廓检测法、边缘检测等方法。
- 连通区域检测
- Rect边框计算
连通区域检测
首先进行连通区域检测,这里我们采用种子填充法进行连通区域检测,比较常用的还有两遍扫描法等
种子填充法
详情请查阅 百度百科
下面开始进行种子填充算法,为了方便展示填充效果,我将每个连通区域都进行了重新赋值,这样就可以得到一个不同颜色值的联通区域展示图。具体代码如下:
/*** 种子填充法进行联通区域检测*/private Mat seedFill(Mat binImg, Mat src) {// 用来记录连通区域都数据图Mat lableImg = new Mat();// 这个是用来展示连通区域都效果图。Mat showMat = new Mat(binImg.size(), CvType.CV_8UC3);// 不需要记录额外都数据,一个通道就够了。binImg.convertTo(lableImg, CvType.CV_32SC1);int rows = lableImg.rows();int cols = lableImg.cols();double lable = 0;for (int r = 0; r < rows; r++) {for (int c = 0; c < cols; c++) {// 获取种子像素点double[] data = lableImg.get(r, c);if (data == null || data.length < 1) {continue;}if (data[0] == 255) {// 不是我们要都种子像素,继续// 展示图背景设置为白色showMat.put(r, c, 255, 255, 255);continue;}if (data[0] != 0) {// 已经标记过了,继续continue;}// 走到这里说明找到了新的种子像素点,新的填充开始lable++;// 随机生成一个颜色,用来填充展示图double[] color = {Math.random() * 255, Math.random() * 255, Math.random() * 255};// 开始种子填充LinkedList<Point> neighborPixels = new LinkedList<>();neighborPixels.push(new Point(r, c));while (!neighborPixels.isEmpty()) {Point curPx = neighborPixels.pop();int row = (int) curPx.x;int col = (int) curPx.y;lableImg.put(row, col, lable);showMat.put(row, col, color);// 左边double[] left = lableImg.get(row, col - 1);if (left != null && left.length > 0 && left[0] == 0) {neighborPixels.push(new Point(row, col - 1));}// 右边double[] right = lableImg.get(row, col + 1);if (right != null && right.length > 0 && right[0] == 0) {neighborPixels.push(new Point(row, col + 1));}// 上边double[] top = lableImg.get(row - 1, col);if (top != null && top.length > 0 && top[0] == 0) {neighborPixels.push(new Point(row - 1, col));}// 下边double[] bottom = lableImg.get(row + 1, col);if (bottom != null && bottom.length > 0 && bottom[0] == 0) {neighborPixels.push(new Point(row + 1, col));}}}}// 返回展示图return showMat;}
效果如下:
从最下面都那幅图可以看出,我们进行都连通区域检测还是比较成功的,可以发现每个字体都是不同都颜色,有一点缺陷就是式字的那个点没有连通进去,还有拼音都连成了一体。这个就是前面有提到的字体、风格、字体大小、间距等等影响。当然,这些缺陷都是可以通过更智能但处理方式进行优化的。
字体边框计算
通过上一步骤的检测,我们可以得到每个连通区域的像素点,修改上一步骤的代码,在开始新的连通区域检测之前,我们定义一个连通区域坐标点列表集合:
List<List<Point>> texts = new LinkedList<>();
在进行连通区域检测的时候,每次出栈一个点,就将这个点加入到这个坐标点集合中去:
Point curPx = neighborPixels.pop();
int row = (int) curPx.x;
int col = (int) curPx.y;
textPoint.add(new Point(col, row));
这样当我们把连通区域检测完成之后就得到一个连通区域点集合的列表:texts
然后开始计算每个连通区域的边框
for (List<Point> data : texts) {MatOfPoint mat = new MatOfPoint();mat.fromList(data);Rect rect = Imgproc.boundingRect(mat);Imgproc.rectangle(showMat, rect.tl(), rect.br(), new Scalar(255, 0, 0, 255));}
上述代码我们利用连通区域点集合计算连通区域边框,并将边框绘制到了效果图上,下面我们看下效果:
可以看到每个不同颜色的外围都一个矩形边框,包括标点符号以及我们缺陷的 **式** 字的点,我们成功得到了每个字体的边框,距离我们成功提取文字只有一步之遥。
截取文字
下面我们进行文字的截取,并将他们展示在一个列表中。
继续修改上一步的代码:
// 定义一个文字图片列表用来保存截取出来的文字。List<Mat> textMats = new LinkedList<>();for (List<Point> data : texts) {MatOfPoint mat = new MatOfPoint();mat.fromList(data);// 计算边框Rect rect = Imgproc.boundingRect(mat);// 绘制边框Imgproc.rectangle(showMat, rect.tl(), rect.br(), new Scalar(255, 0, 0, 255));// 利用边框截取文字,并加入到列表中。textMats.add(src.submat(rect));}adapter.setData(textMats);
我们在得到每个连通区域后,利用Mat提供的截取方法进行图片的截取,然后我们得到一个文字图片列表,并把它们展示在一个ListView中:
下面是我从阅读软件中截的一张图,字体、间距等都是比较常规的,我们看下效果:
可以看出效果还不错,除了第一行都标题由于色值原因被过滤掉了,正文基本上都成功提取出来了。
在来看下另一张丰富排版都提取效果:
可以看的出来,从这个图片中提取文字效果就每那么好了,对待这种排版,我们需要一种更为智能都识别方式。
最为基本的应用我们就分享到这了。结合文字匹配程序,我们可以在此基础上做更多好玩的工具:身份证信息提取、发票信息提取、名片信息提取等等。
附上完整的关键部分处理代码:
import android.graphics.Bitmap;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import com.hankang.opencv.R;
import com.hankang.opencv.base.BasePicturePickActivity;
import org.opencv.android.Utils;
import org.opencv.core.*;
import org.opencv.imgproc.Imgproc;import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;public class TextActivity extends BasePicturePickActivity {private ImageView imageView;private ImageView imageView2;private ImageView imageView3;private Mat src;private Mat org;private ListView listView;private TextAdapter adapter;static {System.loadLibrary("opencv_java3");}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_text_layout);imageView = findViewById(R.id.image1);imageView2 = findViewById(R.id.image2);imageView3 = findViewById(R.id.image3);listView = findViewById(R.id.listview);adapter = new TextAdapter();listView.setAdapter(adapter);findViewById(R.id.button1).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {erode(src);showResult(src, imageView2);}});findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {medianBlur(src);showResult(src, imageView2);}});findViewById(R.id.button3).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Mat result = seedFill(src, org);showResult(result, imageView3);
// adapter.setData(result);}});}/*** 阈值化,并腐蚀** @param src*/private void erode(Mat src) {// 阈值化Imgproc.threshold(src, src, 100, 255, Imgproc.THRESH_BINARY);
// Imgproc.adaptiveThreshold(opMat,opMat,255.0,Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C,Imgproc.THRESH_BINARY,3,0.0);Mat erodeKernel = Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size(5, 5));Imgproc.erode(src, src, erodeKernel);}/*** 采用中值滤波进行降噪** @param src*/private void medianBlur(Mat src) {Imgproc.medianBlur(src, src, 7);}/*** 种子填充法进行联通区域检测*/private Mat seedFill(Mat binImg, Mat src) {Mat lableImg = new Mat();Mat showMat = new Mat(binImg.size(), CvType.CV_8UC3);binImg.convertTo(lableImg, CvType.CV_32SC1);int rows = lableImg.rows();int cols = lableImg.cols();double lable = 0;List<List<Point>> texts = new LinkedList<>();for (int r = 0; r < rows; r++) {for (int c = 0; c < cols; c++) {// 获取种子double[] data = lableImg.get(r, c);if (data == null || data.length < 1) {continue;}if (data[0] == 255) {// 背景showMat.put(r, c, 255, 255, 255);continue;}if (data[0] != 0) {// 已经标记过了continue;}// 新的填充开始lable++;double[] color = {Math.random() * 255, Math.random() * 255, Math.random() * 255};// 开始种子填充LinkedList<Point> neighborPixels = new LinkedList<>();neighborPixels.push(new Point(r, c));List<Point> textPoint = new LinkedList<>();while (!neighborPixels.isEmpty()) {Point curPx = neighborPixels.pop();int row = (int) curPx.x;int col = (int) curPx.y;textPoint.add(new Point(col, row));lableImg.put(row, col, lable);showMat.put(row, col, color);// 左边double[] left = lableImg.get(row, col - 1);if (left != null && left.length > 0 && left[0] == 0) {neighborPixels.push(new Point(row, col - 1));}// 右边double[] right = lableImg.get(row, col + 1);if (right != null && right.length > 0 && right[0] == 0) {neighborPixels.push(new Point(row, col + 1));}// 上边double[] top = lableImg.get(row - 1, col);if (top != null && top.length > 0 && top[0] == 0) {neighborPixels.push(new Point(row - 1, col));}// 下边double[] bottom = lableImg.get(row + 1, col);if (bottom != null && bottom.length > 0 && bottom[0] == 0) {neighborPixels.push(new Point(row + 1, col));}}texts.add(textPoint);}}List<Mat> textMats = new LinkedList<>();for (List<Point> data : texts) {MatOfPoint mat = new MatOfPoint();mat.fromList(data);Rect rect = Imgproc.boundingRect(mat);Imgproc.rectangle(showMat, rect.tl(), rect.br(), new Scalar(255, 0, 0, 255));textMats.add(src.submat(rect));}adapter.setData(textMats);return showMat;}@Overridepublic void onImageLoadSuccess() {if (getImageBitmap() == null) {return;}imageView.setImageBitmap(getImageBitmap());src = new Mat(getImageBitmap().getHeight(), getImageBitmap().getWidth(), CvType.CV_8UC4);Utils.bitmapToMat(getImageBitmap(), src);org = new Mat();src.copyTo(org);Imgproc.cvtColor(src, src, Imgproc.COLOR_RGBA2GRAY);}class TextAdapter extends BaseAdapter {List<Mat> data = new ArrayList<>();@Overridepublic int getCount() {return data.size();}public void setData(List<Mat> data) {this.data.clear();this.data.addAll(data);notifyDataSetChanged();}@Overridepublic Object getItem(int position) {return data.get(position);}@Overridepublic long getItemId(int position) {return 0;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {ViewHolder holder;View view;if (convertView == null) {holder = new ViewHolder();view = LayoutInflater.from(TextActivity.this).inflate(R.layout.layout_list_item, null, false);holder.imageView = view.findViewById(R.id.image);view.setTag(holder);} else {holder = (ViewHolder) convertView.getTag();view = convertView;}Mat mat = data.get(position);Bitmap bitmap = Bitmap.createBitmap(mat.cols(), mat.rows(), Bitmap.Config.ARGB_8888);Utils.matToBitmap(mat, bitmap);holder.imageView.setImageBitmap(bitmap);return view;}class ViewHolder {public ImageView imageView;}}
}
当然,我们这里只是简单的运用OpenCV进行文字的提取,配合一些其他技术例如:TensorFlow,或者其他的一些人工智能,机器学习技术,我们可以将提取出来的文字图片进行文字识别翻译等等。
在此基础上,我们可以做更多的事情,比如:将图片式的文字自动转换成text文本,拍照翻译、拍照提取文字等等。
OK,这篇分享到此就结束了。
如果对你有所帮助的话,
记得点赞关注。
未完待续。。。
Android OpenCV应用篇三:提取图片中的文字相关推荐
- opencv学习-形态学操作应用-提取图片中的文字
提取图片中的文字 一.步骤 1.读取原图 2.灰度化 3.二值化 4.膨胀 5.腐蚀 6.像素值取反 二.全部代码 #include<iostream> #include<openc ...
- Android日志[进阶篇]三-Logcat 命令行工具
Android日志[进阶篇]一-使用 Logcat 写入和查看日志 Android日志[进阶篇]二-分析堆栈轨迹(调试和外部堆栈) Android日志[进阶篇]三-Logcat命令行工具 Androi ...
- 在线工具:电脑怎么提取图片中的文字?图片如何转化为文字?
电脑怎么提取图片中的文字? 图片记录着我们生活的点点滴滴,比如各种办公截图.查快递单号.布置的课堂作业等等,都离不开这种便捷的方法.而我们有时难免需要从图片中提取想要的文字,总不能就靠打字打到手软吧, ...
- 电脑怎么提取图片中的文字?
图片记录着我们生活的点点滴滴,比如各种办公截图.查快递单号.布置的课堂作业等等,都离不开这种便捷的方法.而我们有时难免需要从图片中提取想要的文字,总不能就靠打字打到手软吧,那么电脑怎么提取图片中的文字 ...
- 【Microsoft Office Document Imaging 2007】提取图片中的文字
使用Microsoft Office 2007工具里的程序:Microsoft Office Document Imaging提取图片中的文字 一.首先查看自己是否安装了Microsoft Offic ...
- iOS 提取图片中的文字
Vision Vision 是用于各种识别的框架,比如人脸识别.文字识别等.本篇文章只介绍图片文字识别的部分,只是因为在最近的项目中有用到过,其他的识别方式以后可能会更新. 先上代码 extensio ...
- 教你如何提取图片中的文字?
在生活中,我们经常会拍这样那样的照片,比如课堂总结要点.会议总结等等,而不少图片中的内容是我们需要的,这让我们不得不把图片中的文字提取出来,变成文字.以便于我们后续的使用.当然了,如果是那种纯文字图片 ...
- 提取图片中的文字怎么做?这几种简单方法别错过
图片中的文字怎么提取出来?图片中的文字需要手动输入或复制粘贴到其他文档中,而通过提取文字,可以节省用户的时间和劳动力.用户可以使用提取文字功能,快速地将图片中的文字提取出来,并且避免手动输入或复制粘贴 ...
- 如何简单的提取图片中的文字
如何简单的提取图片中的文字呢?随着互联网技术的不断发展,信息获取的媒介.渠道在增多,相信很多人都习惯在电脑上或是手机上进行快阅读.而随着人们阅读习惯的变化,人们现在也习惯与用手机截图来记录一些自己喜欢 ...
最新文章
- 解决HttpServletResponse输出中文乱码问题
- 超百家金融机构争相出席,只因飞贷宣布输出全球领先的移动信贷整体技术
- hive 字符串替换指定字符_hive学习笔记:substr()——字符串截取
- python 类 对象 魔法方法概念+习题
- 微信公众号开发之服务器接入指南之Java版本
- Json.Net系列教程 3.Json.Net序列化和反序列化设置
- spring mvc 接收页面数据
- C++:18---函数模板(template)
- 笔记+R︱风控模型中变量粗筛(随机森林party包)+细筛(woe包)
- CodeForces 255C. Almost Arithmetical Progression (DP)
- iSCSI网络存储之IPSAN
- 微信小程序开发-轮播图的实现
- python stm32f401_STM32学习之GPIO配置 (STM32F401ZGT6)
- 地表反射率影响因素_【专题突破】影响气候形成的因素及主要气候判别(高中地理),务必掌握!...
- 阿里云视频点播解密DecryptKMSDataKeyRequest爬坑
- 『Less』学习笔记
- secureCRT串口传送文件
- 虹科Pico动态 |【盖世汽车-走进东风商用车技术展】精彩回顾
- java 防火墙_java如何穿越防火墙
- 指定文件名无效或太长,请指定另一文件名
热门文章
- 关于STM32 程序烧录不进去
- JAVA:使用华为云存储OBS处理文件
- 正好杠杆炒股短期继续震荡筑底
- dll注入失败原因总结
- Windows 安全资料库网站
- web项目 在线预览doc文档
- 三维点云处理06-2D/3DIoU计算
- vue.runtime.esm.js?2b0e:619 [Vue warn]: Error in v-on handler (Promise/async): “Error: 失败“found in
- 设计模式学习笔记(一)
- mysql写出总分最高的学生姓名_编写SQL语句查询出每个各科班分数最高的同学的名字,班级名称,课程名称,分数...