限制谷歌视觉中的检测区域,文本识别

Posted

技术标签:

【中文标题】限制谷歌视觉中的检测区域,文本识别【英文标题】:Limiting the detection area in Google Vision, text recognition 【发布时间】:2020-06-20 01:20:53 【问题描述】:

我整天都在寻找解决方案。我已经检查了几个关于我的问题的线程。

Custom detector object Reduce bar code tracking window 还有更多...

但这对我没有多大帮助。基本上我希望相机预览是全屏的,但文本只能在屏幕中心被识别,那里绘制了一个矩形。

我正在使用的技术:

用于光学字符识别 (OCR) 的 Google Mobile Vision API 依赖:play-services-vision

我目前的状态:我创建了一个 BoxDetector 类:

public class BoxDetector extends Detector 
    private Detector mDelegate;
    private int mBoxWidth, mBoxHeight;

    public BoxDetector(Detector delegate, int boxWidth, int boxHeight) 
        mDelegate = delegate;
        mBoxWidth = boxWidth;
        mBoxHeight = boxHeight;
    

    public SparseArray detect(Frame frame) 
        int width = frame.getMetadata().getWidth();
        int height = frame.getMetadata().getHeight();
        int right = (width / 2) + (mBoxHeight / 2);
        int left = (width / 2) - (mBoxHeight / 2);
        int bottom = (height / 2) + (mBoxWidth / 2);
        int top = (height / 2) - (mBoxWidth / 2);

        YuvImage yuvImage = new YuvImage(frame.getGrayscaleImageData().array(), ImageFormat.NV21, width, height, null);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        yuvImage.compressToJpeg(new Rect(left, top, right, bottom), 100, byteArrayOutputStream);
        byte[] jpegArray = byteArrayOutputStream.toByteArray();
        Bitmap bitmap = BitmapFactory.decodeByteArray(jpegArray, 0, jpegArray.length);

        Frame croppedFrame =
                new Frame.Builder()
                        .setBitmap(bitmap)
                        .setRotation(frame.getMetadata().getRotation())
                        .build();

        return mDelegate.detect(croppedFrame);
    

    public boolean isOperational() 
        return mDelegate.isOperational();
    

    public boolean setFocus(int id) 
        return mDelegate.setFocus(id);
    

    @Override
    public void receiveFrame(Frame frame) 
        mDelegate.receiveFrame(frame);
    

并在这里实现了这个类的一个实例:

final TextRecognizer textRecognizer = new TextRecognizer.Builder(App.getContext()).build();

// Instantiate the created box detector in order to limit the Text Detector scan area
BoxDetector boxDetector = new BoxDetector(textRecognizer, width, height);

//Set the TextRecognizer's Processor but using the box collider

boxDetector.setProcessor(new Detector.Processor<TextBlock>() 
    @Override
    public void release() 
    

    /*
        Detect all the text from camera using TextBlock
        and the values into a stringBuilder which will then be set to the textView.
    */
    @Override
    public void receiveDetections(Detector.Detections<TextBlock> detections) 
        final SparseArray<TextBlock> items = detections.getDetectedItems();
        if (items.size() != 0) 

            mTextView.post(new Runnable() 
                @Override
                public void run() 
                    StringBuilder stringBuilder = new StringBuilder();
                    for (int i = 0; i < items.size(); i++) 
                        TextBlock item = items.valueAt(i);
                        stringBuilder.append(item.getValue());
                        stringBuilder.append("\n");
                    
                    mTextView.setText(stringBuilder.toString());
                
            );
        
    
);


    mCameraSource = new CameraSource.Builder(App.getContext(), boxDetector)
            .setFacing(CameraSource.CAMERA_FACING_BACK)
            .setRequestedPreviewSize(height, width)
            .setAutoFocusEnabled(true)
            .setRequestedFps(15.0f)
            .build();

执行时抛出此异常:

Exception thrown from receiver.
java.lang.IllegalStateException: Detector processor must first be set with setProcessor in order to receive detection results.
    at com.google.android.gms.vision.Detector.receiveFrame(com.google.android.gms:play-services-vision-common@@19.0.0:17)
    at com.spectures.shopendings.Helpers.BoxDetector.receiveFrame(BoxDetector.java:62)
    at com.google.android.gms.vision.CameraSource$zzb.run(com.google.android.gms:play-services-vision-common@@19.0.0:47)
    at java.lang.Thread.run(Thread.java:919)

如果有人有线索,我的错是什么或有任何替代方案,我将不胜感激。谢谢!

这就是我想要实现的,一个 Rect。文本区域扫描仪:

【问题讨论】:

你如何解决这个问题? @ShwetaChauhan 很遗憾,我做不到 【参考方案1】:

Google 视觉检测的输入是一帧。帧是图像数据并包含宽度和高度作为关联数据。您可以在将其传递给检测器之前处理此帧(将其剪切为较小的中心帧)。这个过程必须快速并且沿着相机处理图像进行。 在下面查看我的 Github,搜索 FrameProcessingRunnable。你可以在那里看到帧输入。你可以在那里自己做这个过程。

CameraSource

【讨论】:

您好,首先感谢您的回答!我看到了您的代码并想知道,我必须在代码中进行哪些更改?我唯一需要添加的是帧处理部分吗? (2个私人课程)? 是的,你必须修改你的帧,然后才能将它传递给 Detector 的最后一个操作:mDetector.receiveFrame(outputFrame); 你能用我需要添加的代码编辑你的答案,这样我就可以把它编码出来并奖励你赏金吗?【参考方案2】:

您可以尝试像 @'Thành Hà Văn' 提到的那样预先解析 CameraSource 提要(我自己首先尝试过,但在尝试调整新旧相机 api 后丢弃),但我发现限制更容易您的搜索区域并使用默认视觉检测和 CameraSource 返回的检测。您可以通过多种方式做到这一点。例如,

(1) 通过根据屏幕/预览大小设置边界来限制屏幕区域 (2)创建一个自定义类,可以用来动态设置检测区域

我选择了选项 2(如果需要,我可以发布我的自定义类),然后在检测区域中,我将其过滤为仅在指定区域内进行检测:

                for (j in 0 until detections.size()) 
                    val textBlock = detections.valueAt(j) as TextBlock
                    for (line in textBlock.components)                         
                        if((line.boundingBox.top.toFloat()*hScale) >= scanView.top.toFloat() && (line.boundingBox.bottom.toFloat()*hScale) <= scanView.bottom.toFloat()) 
                            canvas.drawRect(line.boundingBox, linePainter)
                            
                            if(scanning)
                                if (((line.boundingBox.top.toFloat() * hScale) <= yTouch && (line.boundingBox.bottom.toFloat() * hScale) >= yTouch) &&
                                    ((line.boundingBox.left.toFloat() * wScale) <= xTouch && (line.boundingBox.right.toFloat() * wScale) >= xTouch) )                                     
                                    acceptDetection(line, scanCount)
                                
                        
                    
                

扫描部分只是我用来允许用户选择他们想要保留的检测的一些自定义代码。您将使用自定义代码替换 if(line....) 循环中的所有内容,以仅对裁剪的检测区域起作用。请注意,此示例代码仅垂直裁剪,但您也可以水平裁剪,也可以双向裁剪。

【讨论】:

【参考方案3】:

在 google-vision 中,您可以获得检测到的文本的坐标,如 How to get position of text in an image using Mobile Vision API?

中所述

您从TextRecognizer 中获得TextBlocks,然后通过TextBlock 的坐标过滤TextBlock,这可以通过TextBlocks 类的getBoundingBox()getCornerPoints() 方法确定:

文本识别器

识别结果由detect(Frame)返回。 OCR 算法 尝试推断文本布局并将每个段落组织成 TextBlock 实例。如果检测到任何文本,则至少有一个 TextBlock 实例将被返回。

[..]

公共方法

public SparseArray&lt;TextBlock&gt; detect (Frame frame) 检测并识别图像中的文本。目前仅支持位图和 NV21。 返回 int 到 TextBlock 的映射,其中 int 域表示文本块的不透明 ID。

来源:https://developers.google.com/android/reference/com/google/android/gms/vision/text/TextRecognizer

文本块

public class TextBlock extends Object implements Text

OCR 认为的文本块(将其视为段落) 引擎。

公共方法总结

Rect getBoundingBox() 返回 TextBlock 的轴对齐边界框。

List&lt;? extends Text&gt; getComponents() 构成此实体的更小组件(如果有)。

Point[] getCornerPoints() 从左上角开始顺时针方向的 4 个角点。

String getLanguage() TextBlock 中的流行语言。

String getValue()以字符串形式检索识别的文本。

来源:https://developers.google.com/android/reference/com/google/android/gms/vision/text/TextBlock

所以你基本上像 How to get position of text in an image using Mobile Vision API? 那样进行,但是你不会将任何块分割成行,然后将任何行分割成像

这样的单词
//Loop through each `Block`
            foreach (TextBlock textBlock in blocks)
            
                IList<IText> textLines = textBlock.Components; 

                //loop Through each `Line`
                foreach (IText currentLine in textLines)
                
                    IList<IText>  words = currentLine.Components;

                    //Loop through each `Word`
                    foreach (IText currentword in words)
                    
                        //Get the Rectangle/boundingBox of the word
                        RectF rect = new RectF(currentword.BoundingBox);
                        rectPaint.Color = Color.Black;

                        //Finally Draw Rectangle/boundingBox around word
                        canvas.DrawRect(rect, rectPaint);

                        //Set image to the `View`
                        imgView.SetImageDrawable(new BitmapDrawable(Resources, tempBitmap));


                    

                
            

相反,您将获得所有文本块的边界框,然后选择坐标最接近屏幕/框架中心的边界框或您指定的矩形(即How can i get center x,y of my view in android?) .为此,您使用 TextBlocksgetBoundingBox()getCornerPoints() 方法 ...

【讨论】:

我试过但不知道如何正确实现

以上是关于限制谷歌视觉中的检测区域,文本识别的主要内容,如果未能解决你的问题,请参考以下文章

谷歌视觉 api 文本检测

谷歌视觉文本检测响应逐行

连通区域

Paddle OCR文字识别学习

OCR场景文本识别:文字检测+文字识别

文本的检测识别实战:使用 Tesseract 进行 OpenCV OCR 和文本识别