首页 >> 大全

Android实现OCR文字识别并且转换为Excel、PDF格式输出

2024-01-11 大全 268 作者:考证青年

由于最近有一个项目有需要用到拍照识别字体,且将识别后的字体转换为对应的Excel,Word,PDF格式的文本输出,所以特地去网上搜集了很多资料来制作,但是网上的资料很少有写到如何在手机端实现OCR技术,即使有实现的人,也不会给出一个很全面的源码,所以小弟特地花了点时间去实现该技术,测试可用后记录下步骤,以备以后查看。

后面我会将我的Demo链接也给出,供大家下载互相交流

前期准备

因为使用的是文字识别技术,所以我这里使用的是的OCR引擎,这款相对于来说识别率还行。

是用c++实现的,需要封装Java API用于平台的调用,所以我们使用的是 Tools for 的一个git分支 -two,它集成了图形处理工具,使用起来很方便,但是网上的都是比较麻烦,需要自己去下载下来编译,才能在上实现,小弟使用的是一个已经编译好的,全部内容可以直接使用,无需再引用其他什么tess-two项目作为lib。

而至于将扫描出来的文字转换部分,小弟只实现了Excel 及 PDF 的转换,对于Word的转换失败了,一部分原因是因为Word的包和Excel,PDF出现了冲突,一部分是因为IText包对的支持还不是很全,中有一些java的API的使用还不支持,会出错。

其中包是某位大神重新对之前的iText进行了重新打包,使之能够支持在上使用中文的PDF,小弟就可耻的借来使用了

好了,下面开始进行实现。

代码实现

使用必须要在手机SD卡中建立// 并且将你的识别字库放入该文件夹下,否则你运行就会出错,很多朋友都推荐使用 adb 命令在手机中进行新建文件夹并推送字库进去,但这操作明显不符合一款成品软件的操作,难道要每个用户都在下载软件的时候 都回家使用adb命令推送一次吗?

所以我们手动写一段代码,在启动软件时,自动创建路径并推送字库进去

初始化OCR环境

先在我们的项目res中建立raw文件夹,并将字库.放进raw中等待稍后启动时推送

资源文件

将三个jar包下载下来,以及两个的so文件下载好后放入libs文件夹中

文字识别结果为__识别的文字怎么转换成表格

第三方包

操作完这几步后,我们要考虑几点

- SD卡的读写权限..GE

- SD卡中创建删除文件夹的权限..STEMS

- 我们拍照识别文字需要的拍照权限..

所以我们需要在清单文件中注册以下权限

权限注册

弄好这几步后,我们开始在中实现推送代码吧,我们原理很简单,先判断有没有SD卡,有就再进行判断SD卡中是否有该字库,没有就进行推送。

判断是否有SD卡

    /*** 判断手机是否有SD卡。* * @return 有SD卡返回true,没有返回false。*/public static boolean hasSDCard() {return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());}

将资源文件raw中的资源推送到目标位置

/*** 将资源转换成文件* * @param context*            上下文* * @param ResourcesId*            资源Id* * @param filePath*            目标文件地址* * @param fileName*            目标文件名* * @return 是否成功*/public static boolean ResourceToFile(Context context, int ResourceId,String filePath, String fileName) {InputStream is = null;FileOutputStream fs = null;try {if (!FileUtils.CreateFolder(filePath))return false;is = context.getResources().openRawResource(ResourceId);File file = new File(filePath + File.separator + fileName);fs = new FileOutputStream(file);while (is.available() > 0) {byte[] b = new byte[is.available()];is.read(b);fs.write(b);}return true;} catch (Exception e) {Log.e(TAG, e.toString());return false;} finally {try {if (is != null)is.close();} catch (Exception e) {Log.e(TAG, e.toString());}try {if (fs != null)fs.close();} catch (Exception e) {Log.e(TAG, e.toString());}}}

判断是否有该文件

    /*** 判断文件是否存在* * @param fileName*            文件名,必须为文件完整路径* * @return 文件存在返回true,文件不存在返回false*/public static boolean IsFileExists(String fileName) {try {File file = new File(fileName);return file.exists();} catch (Exception e) {TrackerUtils.e(e);return false;}}

我们先定义好几个固定常量,然后在执行()的时候进行操作

    private static final String TESSBASE_PATH = "/mnt/sdcard/tesseract/";private static final String DEFAULT_LANGUAGE = "chi_sim";private static final String IMAGE_PATH = "/mnt/sdcard/ccc.jpg";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);findView();intercept();String path = TESSBASE_PATH;// 判断是否有SD卡if(FileUtils.hasSDCard()){// 判断字库是否存在if (!FileUtils.IsFileExists(path + DEFAULT_LANGUAGE))//推送字库到SD卡ResourceToFile(this, R.raw.chi_sim, path + "tessdata/",DEFAULT_LANGUAGE +".traineddata");}}

OCR扫描解析代码实现

做好前期工作后,我们开始Ocr代码使用部分的编写

public void testOcr() {mHandler.post(new Runnable() {@Overridepublic void run() {ocr();}});}protected void ocr() {//获取图片的显示方向,判断图片是否需要调整try {ExifInterface exif = new ExifInterface(IMAGE_PATH);int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_NORMAL);int rotate = 0;switch (exifOrientation) {case ExifInterface.ORIENTATION_ROTATE_90:rotate = 90;break;case ExifInterface.ORIENTATION_ROTATE_180:rotate = 180;break;case ExifInterface.ORIENTATION_ROTATE_270:rotate = 270;break;}Log.v(TAG, "Rotation: " + rotate);int w = mBitmap.getWidth();int h = mBitmap.getHeight();Matrix mtx = new Matrix();mtx.preRotate(rotate);mBitmap = Bitmap.createBitmap(mBitmap, 0, 0, w, h, mtx, false);mBitmap = mBitmap.copy(Bitmap.Config.ARGB_8888, true);} catch (IOException e) {Log.e(TAG, "Rotate or coversion failed: " + e.toString());Log.v(TAG, "in the exception");}//为了获得更好的解析效果,将图片二值化Bitmap data = handleBlackWitheBitmap(mBitmap);ImageView iv = (ImageView) findViewById(R.id.image);iv.setImageBitmap(data);iv.setVisibility(View.VISIBLE);//实例化TessBaseAPITessBaseAPI baseApi = new TessBaseAPI();baseApi.setDebug(true);//初始化TessBaseAPIbaseApi.init(TESSBASE_PATH, DEFAULT_LANGUAGE);//设置需要解析的图片baseApi.setImage(data);//解析图片recognizedText = baseApi.getUTF8Text();//解析完后清除内容baseApi.clear();//结束TessBaseAPIbaseApi.end();if (!StringUtils.IsNullOrEmpty(recognizedText)) {((TextView) findViewById(R.id.field)).setText(recognizedText.trim());}}

从代码上可以看出来,其实的使用很简单,就几行代码而已,但是识别率不是很高,所以我在图片操作上增加了一段二值化处理,为了使他更容易被解析,如果你们不需要,可以注释掉

转出到Excel

OCR解析完后,我们开始操作下如何转换成对应的输出格式,先放出转换成Excel的方法

Excel的保存和读取,我使用的是jxl包,研究了一会后稍微写了个方法,简单的实现了保存和读取,但是在我写的Demo中没有引用 读取的方法,只使用了保存的方法 ,且保存的方法也是用固定值进行保存的,各位要是有使用请自行修改

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;import jxl.Range;
import jxl.Workbook;
import jxl.read.biff.BiffException;
import jxl.write.Label;
import jxl.write.WritableSheet;
import jxl.write.WritableWorkbook;
import jxl.write.WriteException;
import jxl.write.biff.RowsExceededException;
import android.os.Environment;
import android.util.Log;
/**** Excel工具类**/
public class ExcelUtils {private static String TAG = ExcelUtils.class.getSimpleName();/*** 保存数据到Excel* @param tableName => 文件名,无需后缀*/public static boolean saveToExcel(String content, String fileName) {WritableWorkbook wwb = null;boolean result = false;fileName = Environment.getExternalStorageDirectory() + File.separator+ fileName + ".xls";Log.i(TAG, fileName);// 假设数据 五行五列int numrows = 5;int numcols = 5;String[][] records = {{ "lin.yifan", "lin.erfan", "lin.sanfan", "lin.sifan","lin.wufan" },{ "lin.yifan", "lin.erfan", "lin.sanfan", "lin.sifan","lin.wufan" },{ "lin.yifan", "lin.erfan", "lin.sanfan", "lin.sifan","lin.wufan" },{ "lin.yifan", "lin.erfan", "lin.sanfan", "lin.sifan","lin.wufan" },{ "lin.yifan", "lin.erfan", "lin.sanfan", "lin.sifan","lin.wufan" } };Log.i(TAG, "开始保存到Excel");try {// 1 使用Workbook类的工厂方法创建一个可写入的工作薄(Workbook)对象wwb = Workbook.createWorkbook(new File(fileName));} catch (IOException e) {e.printStackTrace();}if (wwb != null) {// 创建一个可写入的工作表// 2 创建工作表 Workbook的createSheet方法有两个参数,第一个是工作表的名称,第二个是工作表在工作薄中的位置WritableSheet ws = wwb.createSheet("sheet1", 0);// 3 添加单元格for (int i = 0; i < numrows; i++) {for (int j = 0; j < numcols; j++) {// 这里需要注意的是,在Excel中,第一个参数表示列,第二个表示行Label labelC = new Label(j, i, records[i][j]);try {// 将生成的单元格添加到工作表中ws.addCell(labelC);} catch (RowsExceededException e) {e.printStackTrace();} catch (WriteException e) {e.printStackTrace();}}}try {// 从内存中写入文件中wwb.write();// 关闭资源,释放内存wwb.close();result = true;Log.i(TAG, "保存到Excel成功");} catch (IOException e) {e.printStackTrace();} catch (WriteException e) {e.printStackTrace();}}return result;}/*** 读取数据* @param fileName => 文件名,无需后缀* @return*/public static ArrayList> getExcel(String fileName) {ArrayList innerAList = null;ArrayList> outerAlist = new ArrayList>();fileName = Environment.getExternalStorageDirectory() + File.separator+ fileName + ".xls";Log.i(TAG, fileName);if (!FileUtils.IsFileExists(fileName)) {Log.e(TAG, "the file is not exists! return");return outerAlist;}File file = new File(fileName);Workbook wb = null;try {// 1 创建一个工作簿对象wb,该对象的引用指向某个待读取的Excel文件wb = Workbook.getWorkbook(file);// 2 创建一个工作簿wb中的工作表对象jxl.Sheet sheet = wb.getSheet(0);int stRows = sheet.getRows(); // 得到当前工作表中所有非空行号的数目int stColumns = sheet.getColumns();// 得到当前工作表中所有非空列号的数目Range[] range = sheet.getMergedCells(); // 得到所有合并的单元格for (int i = 0; i < stRows; i++) {innerAList = new ArrayList();for (int j = 0; j < stColumns; j++) {// 3 创建一个单元格对象,来存放从sheet的(第j列,第i行)读取的单元格;jxl.Cell cell = sheet.getCell(j, i);if (range.length > 0) {for (int z = 0; z < range.length; z++) {int lr = range[z].getTopLeft().getRow();// 左上角单元格行号int lc = range[z].getTopLeft().getColumn();// 左上角单元格列号int rr = range[z].getBottomRight().getRow();// 右下角单元格行号int rc = range[z].getBottomRight().getColumn();// 右下角单元格列号// 判断是否是合并的单元格if (i >= lr && i <= rr && j <= rc && j >= lc) {if (i == lr && j == lc) {innerAList.add(cell.getContents());} else {innerAList.add("><><");}} else {// 4 从当前单元格中获得一个已经转换为字符串类型的字符串,详见APIinnerAList.add(cell.getContents());}}} else {innerAList.add(cell.getContents());}}outerAlist.add(innerAList);}} catch (FileNotFoundException fnf) {fnf.printStackTrace();} catch (IOException ioe) {ioe.printStackTrace();} catch (BiffException be) {be.printStackTrace();} finally {wb.close();}return outerAlist;}
}

转出到PDF

PDF的操作我也单独写了一个操作工具类,使用的是包,由于是高手修改过的,所以可以使用中文,不会那么麻烦,但是麻烦的是,导致我无法对Word进行操作,不知道有没有好心的高手,帮忙封装一个支持 pdf 和 word 的 中文 的 itext包

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;import android.os.Environment;
import android.util.Log;import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Font;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.text.pdf.parser.PdfTextExtractor;
/**** Pdf工具类**/
public class PdfUtils {private static String TAG = PdfUtils.class.getSimpleName();public static String getPDF(String fileName) {StringBuffer str = new StringBuffer();// 获取需要解析的文件路径fileName = Environment.getExternalStorageDirectory()+ File.separator + fileName + ".pdf";if(!FileUtils.IsFileExists(fileName)){Log.e(TAG, fileName + "the file is not exists! return");return null;}try {// 使用PdfReader打开pdf文件PdfReader reader = new PdfReader(fileName);// 模板// 获取总共页数int numberOfPages = reader.getNumberOfPages();// 循环获取每页内容for (int i = 0; i < numberOfPages; i++) {
//              str.append(new String(reader.getPageContent(i + 1), "UTF-8"));str.append(PdfTextExtractor.getTextFromPage(reader, i + 1));}reader.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} return str.toString();}/*** 转换为PDF格式* * @param content => 内容* @param fileName => 文件名,无需后缀*/public static boolean transformationToPDF(String content, String fileName) {fileName = Environment.getExternalStorageDirectory() + File.separator+ fileName + ".pdf";boolean result = false;// 1.创建一个documentDocument doc = new Document();FileOutputStream fos;try {Log.i(TAG, "开始生成PDF文件!");fos = new FileOutputStream(new File(fileName));Log.i(TAG, "路径:" + fileName);// 2.定义pdfWriter,指明文件输出流输出到一个文件PdfWriter.getInstance(doc, fos);// 3.打开文档doc.open();doc.setPageCount(1);// 字体 注意,如果是中文,则必须选择字体Font font = setChineseFont();// 4.添加内容Paragraph paragraph = new Paragraph(content, font);doc.add(paragraph);// 添加段落
//          for (int i = 0; i < 100; i++) {
//              doc.add(new Paragraph("HelloWorld" + "," + "Hello iText" + ","
//                      + "HelloxDuan"));
//          }// 5.关闭doc.close();fos.flush();fos.close();Log.i(TAG, "PDF文件生成成功!");result = true;} catch (FileNotFoundException e1) {e1.printStackTrace();} catch (DocumentException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}return result;}/***  产生PDF字体* @return*/public static Font setChineseFont() {BaseFont bf = null;Font fontChinese = null;try {bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H",BaseFont.NOT_EMBEDDED);fontChinese = new Font(bf, 12, Font.NORMAL);} catch (DocumentException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}return fontChinese;}
}

关键代码都给出来给大家参考了,可惜我使用的是官方给的中文字库,识别率实在不怎么样,但是,使用方法已经给出了,关于提升识别率的方法,过两天我会专门写一篇关于如何正确训练字库的方法,这次的内容如有错误的地方还请指出

下面给出小弟制作的Demo链接

Demo下载地址

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了