dimanche 17 décembre 2017

Red and OpenCV: Find faces in image

Red can be used in conjonction with OpenCV (http://opencv.org/) for sophisticated image processing programs. You'll find here the code for accessing OpenCV with Red (https://github.com/ldci/OpenCV3-red).

How to install OpenCV binding for Red

You need first the last Red stable version (0.63 or newer). Basically, you don’t need to install OpenCV. You’ll find in /DLLs directory a 32-bit compiled version of the OpenCV framework (3.0 and 3.10) for the three main operating systems (Mac OS, Linux and Windows). Just copy the library (.dylib, .so or .dll) somewhere on your computer and then edit the platforms.reds file (in /libs) and make the links according to your path.


Red/System and Red for OpenCV

For this binding, most of 600 OpenCV functions were transformed to Red code with Red/System DSL. Imported OpenCV functions can be directly call by Red/System programs or can be accessed with routines if you use Red language. Routines are a fantastic tool that allows to use code written in Red/System inside Red code. Thus, routines give the possibility to access C functions included in DLL via the binding of the library written in Red/System. This means that you can use either Red/System DSL or Red language to write your image processing programs. Result will be the same. 

However, there are some differences when writing Red Language code. First it’s necessary to use the #system directive to include OpenCV libraries. This is also the place to declare any global variables that will be used by routines. Second, you have to write routines that behave as an interface between you red code and the Red/System functions. OpenCV functions and global Red/System variables are thus directly called inside routines.

Using OpenCV with Red/View 

The interest of using Red Language is that you can employ Red/View and Red/Draw DSL for creating GUI and developing sophisticated interface for computer vision such as in the next sample of face processing with Red.


OpenCV's face detection


Object Detection using Haar feature-based cascade classifiers is an effective object detection method proposed by Paul Viola and Michael Jones in 2001 (Rapid Object Detection using a Boosted Cascade of Simple Features). It is a machine learning based approach where a cascade function is trained from a lot of positive and negative images. It is then used to detect objects in other images. OpenCV already contains many pre-trained classifiers for face, eyes, smile or body. Those XML files are stored in opencv/data/haarcascades/ folder. 

Using Red 

All job is done in findFaces routine which allows to call red/system code accessing OpenCV functions. 

First let's load the required XML classifiers 
cascade: cvLoadHaarClassifierCascade classifier 20 20,
then detect faces with cvHaarDetectObjects function
faces: cvHaarDetectObjects pyrImg cascade storage sFactor minNB flag minS/x minS/y maxS/x maxS/y


Where the parameters are:
pyrImg : 8-bit Matrix  containing an image where objects have to be detected.
cascade: OpenCV pre-trained classifiers for face
storage: a opaque pointer used to get the result of face detection
sFactor: This scale factor is used to create scale pyramid. It means we're using a small step for resizing and we increase the chance of a matching size with the model for detection.
minNB: Parameter specifying how many neighbors each candidate rectangle should have to retain it. This parameter will affect the quality of the detected faces: higher value results in less detections but with higher quality
flags : Mode of operation. 
CV_HAAR_SCALE_IMAGE : for each scale factor used the function will downscale the image rather than "zoom" the feature coordinates in the classifier cascade. 
CV_HAAR_DO_CANNY_PRUNING : If it is set, the function uses Canny edge detector to reject some image regions that contain too few or too much edges and thus can not contain the searched object. The particular threshold values are tuned for face detection and in this case the pruning speeds up the processing.
CV_HAAR_FIND_BIGGEST_OBJECT: If it is set, the function finds the largest object (if any) in the image. That is, the output sequence will contain one (or zero) element(s).
CV_HAAR_DO_ROUGH_SEARCH: used only when CV_HAAR_FIND_BIGGEST_OBJECT is set and min_neighbors > 0. If the flag is set, the function does not look for candidates of a smaller size as soon as it has found the object (with enough neighbor candinates) at the current scale. Typically, when min_neighbors is fixed, the mode yields less accurate (a bit larger) object rectangle than the regular single-object mode (flags=CV_HAAR_FIND_BIGGEST_OBJECT), but it is much faster, up to an order of magnitude. A greater value of min_neighbors may be specified to improve the accuracy.
minS : Minimum possible object size. Objects smaller than minS are ignored.
maxS : Maximum possible object size. Objects larger than maxS are ignored.

Code Sample

Red [
    Title:   "Find Face"
    Author:  "F. Jouen"
    File:    %findFaces.red
    Needs:   'View
]

; import required OpenCV libraries
#system [
    #include %../../libs/include.reds ; all OpenCV  functions
    img: declare CvArr!
    imgCopy: declare CvArr!
    clone: declare CvArr!
    pyrImg: declare CvArr!
    cascade: declare CvHaarClassifierCascade!
    storage: declare CvMemStorage!
    faces: declare CvSeq! 
    faceRect: declare byte-ptr!
    ptr: declare int-ptr!
    roi: declare cvRect!
    nFaces: 0 
    classifier: "/red/OpenCV/cascades/haarcascades/haarcascade_frontalface_default.xml"
]

; global red variables to be passed as parameters to routines or used by red functions

set 'appDir what-dir 
margins: 5x5
clName: "haarcascade_frontalface_default.xml"
scaleFactor: 1.1
minNeighbors: 3
minSize: 0x0
maxSize: 0x0
isFile: false
src: 0
flagValue: 1
nbFaces: 0

; some routines for image conversion from openCV to Red 
#include %../../libs/red/cvroutines.red

; Red Routines for OpenCV access

; release all image pointers
freeOpenCV: routine [] [
    releaseImage img
    releaseImage pyrImg
    releaseImage clone
    releaseImage imgCopy
]

loadTraining: routine [name [string!]/local fName][
    fName: as c-string! string/rs-head name;
    classifier: fName
]

; loads image with faces and returns image address as an integer
loadImg: routine [name [string!] return: [integer!] /local fName tmp isLoaded] [
    isLoaded: 0
    fName: as c-string! string/rs-head name;
    tmp: cvLoadImage fName CV_LOAD_IMAGE_COLOR ; CV_LOAD_IMAGE_ANYDEPTH OR CV_LOAD_IMAGE_ANYCOLOR; 
    img: as int-ptr! tmp
    clone: as int-ptr! cvLoadImage fName CV_LOAD_IMAGE_COLOR 
    imgCopy: as int-ptr! cvLoadImage fName CV_LOAD_IMAGE_COLOR
    pyrImg: as int-ptr! cvCreateImage tmp/width / 2  tmp/height / 2 IPL_DEPTH_8U 3
    storage: cvCreateMemStorage 0
    cvSmooth img img CV_GAUSSIAN 3 3 0.0 0.0      ;gaussian smoothing
    cvPyrDown img pyrImg CV_GAUSSIAN_5x5          ;reduce original size to improve speed in face recognition
    cvCopy img clone null
    cvFlip clone clone -1
    isLoaded: as integer! clone
    isLoaded  
]

; looks for faces 
findFaces: routine [sFactor [float!] minNB [integer!] flag [integer!] minS [pair!] maxS [pair!] return: [integer!] 
    /local c x y wd hg ] [
    cvCopy imgCopy img null
    cascade: cvLoadHaarClassifierCascade classifier 20 20 ;seems OK
    faces: cvHaarDetectObjects pyrImg cascade storage sFactor minNB flag minS/x minS/y maxS/x maxS/y
    nFaces: faces/total ; for faceCount routine
    if faces/total > 0 [
        c: 0
        until [
            faceRect: cvGetSeqElem faces c ; faceRect is a byte-ptr!
            ptr: as int-ptr! faceRect ; we cast to an int-ptr! since we have 4 integers to get here
            ; * 2 due to original image pyrdown
            x: ptr/1 * 2 
            y: ptr/2 * 2 
            wd: (ptr/1 + ptr/3) * 2 
            hg:  (ptr/2 + ptr/4) * 2
            roi: cvRect x y wd hg
            cvRectangle img roi/x roi/y roi/width roi/height 0.0 255.0 0.0 0.0 2 CV_AA 0
            c: c + 1
            c = faces/total
        ]
    ]
    cvCopy img clone null
    cvFlip clone clone -1
    as integer! clone 
]

;returns nb of found faces
countFaces: routine [return: [integer!]][nFaces]


;Red Functions calling routines 

loadImage: does [
    isFile: false
    canvas/image: black
    tmp: request-file 
    if not none? tmp [      
        fileName: to string! to-local-file tmp  
        src: loadImg fileName
        if src <> 0 [
            isFile: true
            win/text: fileName
            ; update faces
            wsz: getIWidth src wsz 
            hsz: getIHeight src hsz
            canvas/image: makeRedImage src wsz hsz
        ]
    ]
]

loadClassifier: does [
    tmp: request-file 
    if not none? tmp [      
        fileName: to string! to-local-file tmp
        info1/data: form second split-path tmp 
        loadTraining fileName
    ]   
]

faces: does [
    t1: now/time/precise
    src: findFaces scaleFactor minNeighbors flagValue minSize maxSize
    t2:  now/time/precise
    s: form countFaces
    append s " in "
    append s third t2 - t1 
    append s " sec"
    sb/data: s
    canvas/image: makeRedImage src wsz hsz
]

;Red GUI Interface
view win: layout [
    title "Find Faces"
    button 50 "Load"            [loadImage faces]
    button 75 "Classifier"      [loadClassifier if isFile [faces]]
    info1: field  220 clname
    text 35 "Flags"
    flag: drop-down 210x24 
        data ["CV_HAAR_DO_CANNY_PRUNING" "CV_HAAR_FIND_BIGGEST_OBJECT"
           "CV_HAAR_DO_ROUGH_SEARCH" "CV_HAAR_SCALE_IMAGE"] 
        select 1  
        on-change [
            if isFile [
                switch flag/selected[
                    1   [flagValue: 1]
                    2   [flagValue: 4]
                    3   [flagValue: 8]
                    4   [flagValue: 2]
                ]
                faces   
            ]
        ]     
    return
    text "Scale Increase"
    sl1: slider 100 [scaleFactor: 1.1 + to float! face/data 
                    tscale/data: 1.1 + face/data if isFile [faces]]
    tscale: field 40 "1.1"
    text  "Min Neighbors"
    field 30 "3" [minNeighbors: to-integer face/data if isFile [faces]]
    text 80 "Size Min Max"
    field 40 "0x0" [minSize: to-pair face/data if isFile [faces]]
    field 40 "0x0" [maxSize: to-pair face/data if isFile [faces]]
    button 50 "Quit" [if isFile [freeOpenCV] Quit]
    return
    canvas: base 640x480 black
    return
    text 100 "Found faces : " sb: field 130
    do [sl1/data: 0.0]
]

Result












Aucun commentaire:

Enregistrer un commentaire