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












samedi 16 décembre 2017

Red and Pandore Library

I really appreciate using Pandore library (https://clouard.users.greyc.fr/Pandore/) which provides  a lot of useful image processing operators. In many case when processing images, you have to apply successive operators: An image processing application is a chain of operators and Pandore offers very efficient operators. The library is 100% pure C++ code  and can't be easily binded to Red. But we have a solution with red call instruction which can be used to execute a shell process to run another process or program.

Installing Pandore

Go to Pandore website and download the lib which is optimized for Unix, Linux, Windows and macOS. 

The complete installation needs a C++ compiler which is required and  Qt (version >= 4.0.0)  or X11 and Motif for visualization operation (www.trolltech.com).Without these API, operators 'pvisu' and 'pdraw' are not available.  However, the rest of the operators works without Qt (or X11).

Then unpack the distribution on your computer.

Installation is straightforward:   
  1. ./configure
  2. make 
  3. make install
  4. make clean

Using Pandore with Red

The idea is now to call the operators from Red program with call. This is really simple. All operators are in pandore_6.6.7/bin directory and your red code must point to this directory
panhome: "Your access to/pandore_6.6.7"
change-dir to-file panhome
Then just use call operator 
call/output "bin/pversion" status 
The pversion operator can be used to test if the pandore lib is correctly installed. Here we use wait/output in order to redirect stdout to a string and get the result of the operator: SUCCESS or FAILURE. When using other operators which process image don't forget to use call/wait  to run the operator and wait for exit.

Pandore creates a specific image format (.pan) and thus you code must convert red image to pandore image with pany2pan operator.
In the code example we call  then the pthresholding operator which  build the output image  with the pixels of the input image  that have a value greater or equal than low or lower or equal than high provided by sliders. Other values are set to 0. Lastly we call ppan2jpeg operator which transform the filtered image to  jpg image.

Code sample

Red [
    Title:   "Pandore test"
    Author:  "Francois Jouen"
    File:    %threshold2.red
    Needs:   'View
]

status: ""
isFile: false
srcImg: none
f: ""
lowT: 0
highT: 255
prog: make string! ""

dSize: 256
gsize: as-pair dSize dSize
sldSize: as-pair ((dSize * 2) - 100) 16

; update according to you OS and pandore directory
panhome: "/Libraries/pandore_6.6.7"
change-dir to-file panhome

; is pandore installed?
call/output "bin/pversion" status

; Converts red loaded image to pandore image
red2pan: func [img [file!] return: [string!]] [
    fName: ""
    fName: form second split-path img
    fileName: copy/part fName (length? fName) - 4 ;removes .ext
    append fileName ".pan"
    prog: copy "bin/pany2pan " 
    append append append prog to-string img " /tmp/" fileName
    call/wait prog
    call/output "bin/pstatus" status
    fileName ; returns filename
]

; Pandore thresholding
{pthresholding builds the output image  with the pixels of the input image 
that have a value greater or equal than low or lower or equal than high. Other values are set to 0}

thresholdPan: func [fn [string!] t1 [integer!] t2 [integer!]] [
    prog: copy "bin/pthresholding "
    append append append prog form t1 " " form t2
    append prog " /tmp/" ; default dir for storing pandore images
    append append prog fn " /tmp/result.pan"
    call/wait prog 
    call/output "bin/pstatus" status    
]

;Converts to jpg
pan2JPG: does [
    prog: copy "bin/ppan2jpeg 1.0"
    append append prog " /tmp/result.pan" " /tmp/result.jpg"
    call/wait prog
    call/output "bin/pstatus" status 
]



; Removes all pandore images in /tmp/ directory
removePanImg: does [call "rm /tmp/*.pan" 
                    call "rm /tmp/*.jpg"
                    call "rm /tmp/pand*"
                    sb2/text: "All pandore images removed"]

; Loads red image
loadImage: does [
    isFile: false
    clear sb2/text
    canvas2/image: none
    tmpFile: request-file
    if not none? tmpFile [
        srcImg: load tmpFile
        canvas/image: srcImg
        isFile: true
        sb2/text: "Red Image loaded"
    ]
]

; Processes image
process: does [
    resImg: %/tmp/result.jpg
    sb2/text: copy "Shows filtered pan image: "
    thresholdPan f lowT highT   
    pan2JPG
    append sb2/text status
    if exists? resImg [canvas2/image: load resImg]
]

; ***************** Test Program ****************************
view win: layout [
        title "Pandore Thresholding"
        button 100 "Load Image"     [loadImage f: red2pan tmpFile process]          
        button 70 "Quit"            [removePanImg Quit]
        return
        text 50 "Low" 
        sl1: slider sldSize [lowT: to-integer face/data * 255 
                            lowsb/text: form lowT 
                            if isFile [process]] 
        lowsb: field 40 "0"
        return
        text 50 "High"
        sl2: slider sldSize [highT: to-integer face/data * 255 
                            highsb/text: form highT
                            if isFile [process]]  
        highsb: field 40 "255"
        return
        text dSize "Source"
        text dSize "Result"
        return
        canvas:  base gsize black
        canvas2: base gsize black
        return
        sb1: field dSize
        sb2: field dSize
        do [sb1/text: status sl1/data: lowT / 255.0 sl2/data: highT / 255.0]    
]

Result


Red and Pandore: a great association !



dimanche 3 décembre 2017

Face processing BRFV4

Since Red and RedCV are under development, I use other librairies for scientific research. This year we begin a series on maxillo-facial restoration in infants and children with Roselyne Lalauze-Pol from Robert Débré Hospital in Paris. I was looking for a nice library for face processing and I found BRFV4 (https://www.beyond-reality-face.com/overview) which is really useful and well-done.
Don't hesitate to test their demo code and download the trial version of the library. Really nice work !

SAMPLE

This code was written with QT 5.9.3 and BRFV4 library. Kid image was found on the Internet :)