Blog

Terug naar Blog

How do they do it? Part 1 – Kentekenherkenning (ANPR) in Unity

In opdracht van de Nationale Politie heeft Superbuff de opdracht gekregen om de app Automon te ontwikkelen. Dit is een app die burgers traint hoe zij actief mee kunnen zoeken naar gestolen auto’s. Om deze app te realiseren is kentekenherkenning nodig. In deze ‘How do they do it’ leggen wij jullie uit hoe wij het voor elkaar gekregen hebben om vanaf de game engine Unity kentekens te herkennen.

Kentekenherkenning is niets nieuws. Sinds 2013 is er al een organisatie die een library heeft geschreven, welke openbaar toegankelijk is en door iedereen gebruikt mag worden. Wij hebben deze library gebruikt, overnieuw gecompiled en hier een wrapper voor geschreven. Op deze manier kunnen wij de functionaliteit vanaf c# kunnen aanroepen.

Header van de Unity bridge


#import "Anpr.h"
    #ifdef __cplusplus
    #include "opencv2/highgui/highgui.hpp"
    #import <opencv2/videoio/cap_ios.h>
    using namespace cv;
    extern "C" {
    #endif
        void framework_init();
        void framework_GetResultsFromPath(const char* path);
        
        void framework_GetResultsByteArray(void* bytes, int imgWidth, int imgHeight);
        
        void framework_trigger_delegate();
        typedef void (*DelegateCallbackFunction)(char* number);
        void framework_setDelegate(DelegateCallbackFunction callback);
        
        
    #ifdef __cplusplus
    }
    #endif

Hierboven zie je een stuk c++ die ervoor zorgt dat de native iOS libraries worden aangeroepen. De bedoeling is dat je een data stream meestuurt. De wrapper geeft de data vervolgens aan de native library door.

Code die de native ANPR aanroept.


+ (void) GetResults:(void*)bytes par2:(int)imgWidth par3:(int)imgHeight
    {
        
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSInteger dataLength = imgWidth * imgHeight * sizeof(UInt8);
            NSData *data = [[NSData alloc] initWithBytes:(void*)bytes length:dataLength];
            UIImage *im = [[UIImage alloc] initWithData:data];
            
            //create cv image
            CGColorSpaceRef colorSpace = CGImageGetColorSpace(im.CGImage);
            cv::Mat image(im.size.height, im.size.width, CV_8UC4);
            CGContextRef contextRef = CGBitmapContextCreate(image.data,
                                                            im.size.width,
                                                            im.size.height,
                                                            8,
                                                            image.step[0],
                                                            colorSpace,
                                                            kCGImageAlphaNoneSkipLast |
                                                            kCGBitmapByteOrderDefault);
            CGContextDrawImage(contextRef, CGRectMake(0,0,im.size.width, im.size.height), im.CGImage);
            CGContextRelease(contextRef);
            //done creating cv image... imread does not work
            
            [plateScanner
             scanImage:image
             onSuccess:^(NSArray * results) {
                 //callback unity
                 if (results.count > 0)
                 {
                     if (res != NULL)
                         delete res;
                     dispatch_async(dispatch_get_main_queue(), ^{
                         Plate* plate = (Plate*)results[0];
                         cv::String str = cv::String([plate.number UTF8String]);
                         [Anpr sendNumberToDelegate:cStringCopy([plate.number UTF8String])];
                         
                     });
                 }
                 else
                 {
                     dispatch_async(dispatch_get_main_queue(), ^{
                         [Anpr sendNumberToDelegate:""];
                     });
                 }
                 NSLog(@"yea foud results %@", results);
             }
             onFailure:^(NSError * error) {
                 [Anpr sendNumberToDelegate:""];
                 NSLog(@"NOTHING FOUND");
             }];
        });
    }


Oh no, trouble..

Wij liepen tegen enkele problemen aan gedurende dit proces. Het grootste probleem was, dat de app crashte bij het maken van de wrapper. Dit gebeurde doordat er een memory lek in de plugin zat. Dit is opgelost door de string weer uit de memory te halen, nadat deze aan Unity is doorgegeven.

Android

Voor Android zijn we niet tegen problemen aangelopen. De Android versie varieert van de iOS plugin en bevat een simpele foto herkenning, wat betekent dat de Android versie wat meer van de processor vraagt. We zullen de data stream op Android in de toekomst nog ondersteunen.

iOS

Voor de iOS plugin hebben wij een stream detectie geschreven. Hierdoor is de plugin efficiënt en vraagt deze minder kracht van de processor dan de Android plugin. 

Kom jij er niet uit?

Loop jij tegen problemen aan? We helpen je graag verder. Neem contact met ons op!