Android基于百度ocr识别相机预览回调onPreviewF

2019-10-10  本文已影响0人  坑逼的严

百度ocr的官方的示例有的都是点击拍照的,那要是直接从相机onPreviewFrame回调中获取实时数据呢?目前的需求就是实时扫描检测有没有对应文字,识别到了展示AR模型,好问题来了,这样的话肯定不能连续拍照,因为takePicture调用一次卡顿一次,这样的话就成了ppt了,所以只能从onPreviewFrame入手。

onPreviewFrame

onPreviewFrame是会实时返回相机的数据,

@Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        
    }

但是遗憾的是这里面的byte[]是yuv,弄过ffmpeg的同学都知道手机屏幕支持的就是yuv。那么我们要转成我们通用的图片格式。第一时间就想到了YuvImage 这个系统提供的工具类,所以一顿乱搞,写出了如下代码:

YuvImage yuvImage = new YuvImage(data, ImageFormat.NV21, width, height, null);
ByteArrayOutputStream os = new ByteArrayOutputStream(data.length);
int fixWidth=width;
 if (height*4!=width*3)
        fixWidth=height*4/3;

 if (yuvImage.compressToJpeg(new Rect((width-fixWidth)/2, 0, fixWidth, height), 75, os)) {
       byte[] tmp = os.toByteArray();
}

一运行,华为P9崩溃,其他手机保存出来的图片不是一片绿就是一片红,而且搜索解决答案的时候还碰到了这篇文章
https://blog.csdn.net/q979713444/article/details/80446404
虽然我没验证,但至少前人踩过了坑啊。决定弃坑。
于是又找到了ScriptIntrinsicYuvToRGB这个工具类。
https://blog.csdn.net/qq1137830424/article/details/81980673

public class NV21ToBitmap {

    private RenderScript rs;
    private ScriptIntrinsicYuvToRGB yuvToRgbIntrinsic;
    private Type.Builder yuvType, rgbaType;
    private Allocation in, out;

    public NV21ToBitmap(Context context) {
        rs = RenderScript.create(context);
        yuvToRgbIntrinsic = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs));
    }

    public byte[] nv21ToBitmap(byte[] nv21, int width, int height) {
        if (yuvType == null) {
            yuvType = new Type.Builder(rs, Element.U8(rs)).setX(nv21.length);
            in = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT);

            rgbaType = new Type.Builder(rs, Element.RGBA_8888(rs)).setX(width).setY(height);
            out = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT);
        }

        in.copyFrom(nv21);

        yuvToRgbIntrinsic.setInput(in);
        yuvToRgbIntrinsic.forEach(out);

        Bitmap bmpout = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
//        byte[] bytes = new byte[];
        out.copyTo(bmpout);
        Bitmap rotatedBitmap = getRotatedBitmap(bmpout, 90);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        rotatedBitmap.compress(Bitmap.CompressFormat.JPEG, 75, baos);
        byte[] data = baos.toByteArray();
//        out.copyTo(bytes);
        return data;

    }

    public Bitmap getRotatedBitmap(Bitmap bitmap, int rotation) {
        Matrix matrix = new Matrix();
        matrix.postRotate(rotation);
        return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
                bitmap.getHeight(), matrix, false);
    }

    public File byteToFile(byte[] bytes, String path) {
        File localFile = null;
        try {
            // 根据绝对路径初始化文件
            localFile = new File(path);
            if (!localFile.exists()) {
                localFile.createNewFile();
            }
            // 输出流
            OutputStream os = new FileOutputStream(localFile);
            os.write(bytes);
            os.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return localFile;
    }
}

getRotatedBitmap是我对作者的改进,因为不旋转90度的话图片是横向的。

初始化ocr
public class OCRService {
    private volatile static OCRService instence;
    private boolean hasGotToken;

    public synchronized static OCRService getInstence() {
        if(instence == null){
            synchronized (OCRService.class){
                if(instence == null){
                    instence = new OCRService();
                }
            }
        }
        return instence;
    }
    public OCRService(){

    }

    /**
     * 用明文ak,sk初始化
     */
    public void initAccessTokenWithAkSk(Context context) {
        OCR.getInstance(context).initAccessTokenWithAkSk(new OnResultListener<AccessToken>() {
            @Override
            public void onResult(AccessToken result) {
                String token = result.getAccessToken();
                hasGotToken = true;
            }

            @Override
            public void onError(OCRError error) {
                ((Activity)context).runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        error.printStackTrace();
                        Toast.makeText(context, "AK,SK方式获取token失败"+error.getMessage(), Toast.LENGTH_SHORT).show();
                    }
                });
            }
        }, ((Activity)context).getApplicationContext(),  "DIr4b4G94yWH2KpO757k6DqV", "KveP20sb53fQ5GU2hQbDx1mkhaBVIMyz");
    }

    public boolean checkTokenStatus(Context context) {
        if (!hasGotToken) {
            Toast.makeText(context, "token还未成功获取", Toast.LENGTH_LONG).show();
        }
        return hasGotToken;
    }
}

上面是将ocr代码统一放入一个类管理。然后再activity中一开始就初始化他。

OCRService.getInstence().initAccessTokenWithAkSk(OCRActivity.this);

最后在相机回调中写识别代码

@Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        .......
        if (!OCRService.getInstence().checkTokenStatus(OCRActivity.this)) {
                    return;
        }
        if(!mIsRun){
              return;
        }
        mIsRun = false;
                Camera.Size pictureSize = cameraUtils.getParameters().getPictureSize();
                byte[] bytes = nv21ToBitmap.nv21ToBitmap(data, pictureSize.width, pictureSize.height);
                File file = nv21ToBitmap.byteToFile(bytes, Environment.getExternalStorageDirectory().getPath() + File.separator + "shpd.jpg");
                if(file != null){
                    OcrRequestParams param = new OcrRequestParams();
                    param.setImageFile(file);

                    OCR.getInstance(OCRActivity.this).recognizeHandwriting(param, new OnResultListener<OcrResponseResult>() {
                        @Override
                        public void onResult(OcrResponseResult result) {
                            Log.d("yanjin","ocr-----result="+result.getJsonRes());
                        }

                        @Override
                        public void onError(OCRError error) {
                            Log.d("yanjin","ocr-----error="+error.getMessage());
                        }
                    });
                }
    }

这里本人做的是点击一下按钮识别一次,通过mIsRun来控制,如果要做成实时识别也只要做一个标志位,默认为true,进入到onPreviewFrame里面的第一行代码就判断是否识别,如果不能就直接返回,如果可以就设置为false后执行识别代码,然后再ocr的回调,成功和失败都将标志位复原为true,这样基本能完成,如果涉及到多线程,最好写成同步方法,标志位也用volatile修饰,尽量解决线程冲突问题。

上一篇 下一篇

猜你喜欢

热点阅读