BlinkID / blinkid-in-browser

BlinkID In-browser SDK for WebAssembly-enabled browsers.
https://microblink.com/blinkid
59 stars 30 forks source link

Blink ID unable to scan some documents #51

Closed sachin321123 closed 3 years ago

sachin321123 commented 3 years ago

Hello Team,

We are facing some issues while scanning some of the docs. Please let us know about the particular resolution which you are looking for or something else related to quality of images, which might be missed by us. Also, please let us know about the max file size and the extensions which are supported by Blink ID.

Please find below the details of our implementation.

As per our requirement, we should allow the user to upload the doc for scanning and restrict him from using the web camera. So, we are using Blink ID for that and we have single scan option available for us. Please find below some information of the recognizer, as we are directly creating the recognizer and recognizer running classes. Please find below the code snippets which can help:-

const genericIDRecognizer = await BlinkIDSDK.createBlinkIdRecognizer(sdk); const idBarcodeRecognizer = await BlinkIDSDK.createIdBarcodeRecognizer(sdk);

const recognizerRunner = await BlinkIDSDK.createRecognizerRunner( // SDK instance to use sdk, // List of recognizer objects that will be associated with created RecognizerRunner object [genericIDRecognizer],
false ); // 3. Prepare image for scan action - keep in mind that SDK can only process images represented in // internal CapturedFrame data structure. Therefore, auxiliary method "captureFrame" is provided.
let file = null; const imageRegex = RegExp(/^image\//); for (let i = 0; i < fileList.length; ++i) { if (imageRegex.exec(fileList[i].type)) { file = fileList[i]; } }

const imageFrame = BlinkIDSDK.captureFrame(document.getElementById(name)); const processResult = await recognizerRunner.processImage(imageFrame); let genericIDResults; if (processResult !== BlinkIDSDK.RecognizerResultState.Empty) { genericIDResults = await genericIDRecognizer.getResult(); }

Kindly let us know if anything else is required from our end.

Regards, Sachin

vjekoart commented 3 years ago

Hi @sachin321123,

Supported image formats

Since everything is happening inside the browser, we support every format supported natively by the web browser.

This is the relevant code from the example:

scanImageElement.src = URL.createObjectURL( file );
await scanImageElement.decode();
const imageFrame = BlinkIDSDK.captureFrame( scanImageElement );

As you can see, we rely on the HTML <img> element. And everything that renders inside that element can be passed to the SDK with the auxiliary method BlinkIDSDK.captureFrame().

File size

Like the previous point, since everything is happening inside the browser, there is no file size limit. However, keep in mind that the bigger the size, the longer it will take for processing to complete.

Resolution

With the bigger resolution, one can expect better results :)

The minimal resolution we would advise is 720p, i.e. 1280x720 px.


Also, it's worth noting that it's a good practice to provide document images where there is padding between document edges and image edges.

I hope this helps.

Sincerely, Vjekoslav

sachin321123 commented 3 years ago

Hello @vjekoart Thanks for the information. I have found a setting:- // Create instance of recognizer const BlinkIdRecognizer = await BlinkIDSDK.createBlinkIdRecognizer( sdk ); // Retrieve current settings const settings = await BlinkIdRecognizer.currentSettings(); // Update desired settings settings[ "scanCroppedDocumentImage" ] = true; // Apply settings await BlinkIdRecognizer.updateSettings( settings );

Witht the below settings, I am able to scan some even lower resolution images, however it expects no padding, so fails to scan high resolution images with some padding. I was thinking to add both settings, if one fails add another setting. For that I need to create 2 intsances of recognizeRunner with an updated recognizer, Howver I get an exception while doing this:- Unhandled Rejection (DataCloneError): Failed to execute 'postMessage' on 'Worker': ArrayBuffer at index 0 is already detached.

Can you please let me know if there is some other way to apply such setting.

Regards, Sachin

vjekoart commented 3 years ago

Hi @sachin321123,

Sorry for the delayed response.

It's possible to achieve the functionality you described, but it has a couple of specific problems. They're also the cause of the error you're getting.

Cause of the error

The root of the error you're getting lies within the captureFrame method and the ImageData interface.

I'll try to explain further, assuming that you're familiar with the example code we provided.

  1. During the first scanning session, the variable imageFrame is defined.
    • The value of that variable is an instance of the ImageData interface, i.e. this variable holds the data of an image you want to scan.
    • The actual image data is stored inside the ArrayBuffer structure.
  2. When the scanning process is started with the recognizerRunner.processImage method, the image data is sent from the main thread to the web worker.
    • The extraction process happens on the web worker for better performance.
  3. To successfully send the image data to the web worker, BlinkID SDK must read the data from the ImageFrame interface. Once the data is read, the ArrayBuffer in the imageFrame variable becomes detached - empty.
  4. When you try to start the scanning process again, the imageFrame variable contains empty property, and therefore data cannot be extracted. This causes an error that you see in the browser console.

Solution

The main idea of this solution is to create a new instance of the imageFrame variable before every call to the recognizerRunner.processImage method.

I've changed the relevant part of the original example to support your approach. New logic would scan the same image twice if the first scanning process wasn't successful.

Scanning logic has been moved from the startScan function to the scanImage function for better readability.

/**
 * Scan single side of an identity document with web camera.
 */
async function startScan( sdk, fileList )
{
    document.getElementById( "screen-start" )?.classList.add( "hidden" );
    document.getElementById( "screen-scanning" )?.classList.remove( "hidden" );

    // Make sure that image file is provided
    let file = null;
    const imageRegex = RegExp( /^image\// );
    for ( let i = 0; i < fileList.length; ++i )
    {
        if ( imageRegex.exec( fileList[ i ].type ) )
        {
            file = fileList[ i ];
        }
    }
    if ( !file )
    {
        alert( "No image files provided!" );
        return;
    }

    // Prepare image element that will be used with `captureFrame` method
    scanImageElement.src = URL.createObjectURL( file );
    await scanImageElement.decode();

    // Start scan
    console.log( "Trying to scan uncropped image..." );
    let results = await scanImage( sdk, scanImageElement, false );

    if ( results === null )
    {
        console.log( "Trying to scan cropped image..." );
        results = await scanImage( sdk, scanImageElement, true );
    }

    console.log( "Result", results || "could not extract information" );

    // Hide scanning screen and show scan button again
    inputImageFile.value = "";
    document.getElementById( "screen-start" )?.classList.remove( "hidden" );
    document.getElementById( "screen-scanning" )?.classList.add( "hidden" );
}

async function scanImage( sdk, scanImageElement, cropped )
{
    // 1. Create a recognizer object which will be used to recognize a single image or stream of images.
    //
    // Generic ID Recognizer - scan various ID documents
    const genericIDRecognizer = await BlinkIDSDK.createBlinkIdRecognizer( sdk );

    if ( cropped )
    {
        const settings = await genericIDRecognizer.currentSettings();
        settings[ "scanCroppedDocumentImage" ] = true;
        await genericIDRecognizer.updateSettings( settings );
    }

    // 2. Create a RecognizerRunner object which orchestrates the recognition with one or more
    //    recognizer objects.
    const recognizerRunner = await BlinkIDSDK.createRecognizerRunner
    (
        // SDK instance to use
        sdk,
        // List of recognizer objects that will be associated with created RecognizerRunner object
        [ genericIDRecognizer ],
        // [OPTIONAL] Should recognition pipeline stop as soon as first recognizer in chain finished recognition
        false
    );

    // 3. Prepare image for scan action - keep in mind that SDK can only process images represented in
    //    internal CapturedFrame data structure. Therefore, auxiliary method "captureFrame" is provided.
    const imageFrame = BlinkIDSDK.captureFrame( scanImageElement );

    // 4. Start the recognition and await for the results
    const processResult = await recognizerRunner.processImage( imageFrame );

    // 5. If recognition was successful, obtain the result and display it
    let results = null;
    if ( processResult !== BlinkIDSDK.RecognizerResultState.Empty )
    {
        const genericIDResults = await genericIDRecognizer.getResult();
        if ( genericIDResults.state !== BlinkIDSDK.RecognizerResultState.Empty )
        {
            results = genericIDResults;
        }
    }

    // 6. Release all resources allocated on the WebAssembly heap and associated with camera stream

    // Release memory on WebAssembly heap used by the RecognizerRunner
    await recognizerRunner?.delete();

    // Release memory on WebAssembly heap used by the recognizer
    await genericIDRecognizer?.delete();

    return results;
}

There is no need to change the main function. Everything should work as expected with the same code.


I hope this helps. Feel free to respond here if you find any new problems.

Sincerely, Vjekoslav

vjekoart commented 3 years ago

Hi @sachin321123,

Have you managed to get the SDK to work as intended with this solution?

Sincerely, Vjekoslav

sachin321123 commented 3 years ago

Hi @vjekoart Sorry for the late reply. Yes, I have managed to get the SDK work, I will close this issue.

Regards, Sachin Dhawan