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

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

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

前期准备

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

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

  • 文字识别使用tess-two.tesseract3.01-leptonica1.68-LibJPEG6b.jar
  • 两个armeabi包
  • 中文字库使用chi_sim.traineddata
  • 英文字库使用eng.traineddata

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

  • Excel使用jxl.jar
  • PDF使用iTextpdf.jar

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

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

代码实现

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

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

初始化OCR环境

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

资源文件


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


操作完这几步后,我们要考虑几点
- SD卡的读写权限android.permission.WRITE_EXTERNAL_STORAGE
- SD卡中创建删除文件夹的权限android.permission.MOUNT_UNMOUNT_FILESYSTEMS
- 我们拍照识别文字需要的拍照权限android.permission.CAMERA

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

弄好这几步后,我们开始在Activity中实现推送代码吧,我们原理很简单,先判断有没有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;
        }
    }

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

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

    @Override
    protected 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() {

            @Override
            public 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);

        //实例化TessBaseAPI
        TessBaseAPI baseApi = new TessBaseAPI();
        baseApi.setDebug(true);

        //初始化TessBaseAPI
        baseApi.init(TESSBASE_PATH, DEFAULT_LANGUAGE);

        //设置需要解析的图片
        baseApi.setImage(data);
        //解析图片
        recognizedText = baseApi.getUTF8Text();

        //解析完后清除内容
        baseApi.clear();
        //结束TessBaseAPI
        baseApi.end();

        if (!StringUtils.IsNullOrEmpty(recognizedText)) {
            ((TextView) findViewById(R.id.field))
                    .setText(recognizedText.trim());
        }
    }

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


转出到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<ArrayList<String>> getExcel(String fileName) {
        ArrayList<String> innerAList = null;
        ArrayList<ArrayList<String>> outerAlist = new ArrayList<ArrayList<String>>();

        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<String>();
                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 从当前单元格中获得一个已经转换为字符串类型的字符串,详见API
                                innerAList.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的操作我也单独写了一个操作工具类,使用的是itextpdf包,由于是高手修改过的,所以可以使用中文,不会那么麻烦,但是麻烦的是,导致我无法对Word进行操作,不知道有没有好心的高手,帮忙封装一个支持 pdf 和 word 的 中文 Android的 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.创建一个document
        Document 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下载地址http://download.csdn.net/download/u014371093/8729833

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
以下是一个简单的Java代码示例,演示了如何使用Tesseract OCR库和iText库实现OCR识别图片并转换成双层PDF: ```java import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import javax.imageio.ImageIO; import com.itextpdf.awt.DefaultFontMapper; import com.itextpdf.text.Document; import com.itextpdf.text.PageSize; import com.itextpdf.text.Rectangle; import com.itextpdf.text.pdf.PdfContentByte; import com.itextpdf.text.pdf.PdfLayer; import com.itextpdf.text.pdf.PdfLayerMembership; import com.itextpdf.text.pdf.PdfName; import com.itextpdf.text.pdf.PdfWriter; import net.sourceforge.tess4j.Tesseract; import net.sourceforge.tess4j.TesseractException; import net.sourceforge.tess4j.util.ImageHelper; public class OCRToPDF { public static void main(String[] args) { // 读取图片文件 File imageFile = new File("input.png"); // 定义输出PDF文件 File pdfFile = new File("output.pdf"); // 创建Tesseract OCR对象 Tesseract tesseract = new Tesseract(); // 设置OCR语言为英语 tesseract.setLanguage("eng"); try { // 读取图片并转换为灰度图像 BufferedImage image = ImageIO.read(imageFile); BufferedImage grayImage = ImageHelper.convertImageToGrayscale(image); // 进行OCR识别 String result = tesseract.doOCR(grayImage); // 创建PDF文档 Document document = new Document(PageSize.A4); // 创建PDF写入器 PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(pdfFile)); // 打开文档 document.open(); // 创建图层 PdfContentByte canvas = writer.getDirectContent(); PdfLayer imageLayer = new PdfLayer("Image Layer", writer); PdfLayer textLayer = new PdfLayer("Text Layer", writer); // 将图像添加到图像层 PdfLayerMembership imageMembership = new PdfLayerMembership(writer); imageMembership.addMember(imageLayer); canvas.beginLayer(imageMembership); canvas.drawImage(Image.getInstance(imageFile.getAbsolutePath()), 0, 0, PageSize.A4.getWidth(), PageSize.A4.getHeight()); canvas.endLayer(); // 将OCR识别文本添加到文本层 PdfLayerMembership textMembership = new PdfLayerMembership(writer); textMembership.addMember(textLayer); canvas.beginLayer(textMembership); canvas.beginText(); canvas.setFontAndSize(DefaultFontMapper.getAModelFont("Arial", false), 12); canvas.showTextAligned(result, 100, 100, PdfContentByte.ALIGN_LEFT); canvas.endText(); canvas.endLayer(); // 关闭文档 document.close(); } catch (IOException e) { e.printStackTrace(); } catch (TesseractException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } } ``` 请注意,此示例代码仅提供了基本的实现方法。在实际应用中,您需要根据具体需求进行更细粒度的调整和优化。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值