jeudi 22 juin 2017

RedCV: Normalized convolution on matrices

2-D Convolution is very useful to process image and to code various filters. A 2-D convolution can be thought of as replacing each pixel with the weighted sum of its neighbors. The kernel is another image, of smaller size, which contains the weights as illustrated below.

Depending on kernel and pixel values, weighted sum of pixel neighbors is signed (positive or negative) and can be greater than byte values [0..255] that are used to represents RGB values of the images. 
In most of cases, 2D Convolution algorithms include an implicit cut-off which is similar to a binary filter: If the weighted sum is <= to 0, then weighted sum equals to 0 and if the weighted sum is > to 255, then weighted sum equals to 255
In redCV, rcvConvolveMat function integrates this binary processing of weighted sum.  However, depending on the nature of the image you process, this cut-off can induce more or less correct result when applying kernel filter.

Weighted sum normalization

A way to avoid this kind of problem is to use a normalization of the weighted sums. This is done by rcvConvolveNormalizedMat function in RedCV. 
The idea is rather simple. First we look for the minimal (wsMin) and maximal (wsMax) weighted sums that are calculated when applying the kernel convolution. The difference wsMax - wsMin gives the range of the variation in image when convolution is applied. This allows to calculate a scale factor which is equal to 255 / (wsMax - wsMin). Then, it's really easy to remplace each weighted sum (wsValue) with this formula  wsValue = (wsValue - wsMin) * scale: each wsValue will be in 0..255 range.

Code sample

Red [
    Title:   "Matrix tests "
    Author:  "Francois Jouen"
    File:    %matLaplacian.red
    Needs:   'View
]

#include %../../libs/redcv.red ; for redCV functions
; laplacian convolution filter for sample
mask: [-1.0 0.0 -1.0 0.0 4.0 0.0 -1.0 0.0 -1.0]
isize: 512x512
bitSize: 32
img1: rcvCreateImage isize
img2: rcvCreateImage isize
img3: rcvCreateImage isize

loadImage: does [
    canvas1/image/rgb: black
    canvas2/image/rgb: black
    canvas3/image/rgb: black
    tmp: request-file
    if not none? tmp [
        img1: rcvLoadImage tmp
        img2: rcvCreateImage img1/size
        img3: rcvCreateImage img1/size
        mat1: rcvCreateMat 'integer! bitSize img1/size
        mat2: rcvCreateMat 'integer! bitSize img1/size
        mat3: rcvCreateMat 'integer! bitSize img1/size
        ; Converts to  grayscale image and to 1 Channel matrix [0..255]
        rcvImage2Mat img1 mat1  
        ; Standard Laplacian convolution                                    
        rcvConvolveMat mat1 mat2 img1/size mask 1.0 0.0 
        ; Normalized Laplacian convolution          
        rcvConvolveNormalizedMat mat1 mat3 img1/size mask 1.0 0.0   
        ; From matrices to Red images
        rcvMat2Image mat2 img2                                      
        rcvMat2Image mat3 img3      
        ; show results                              
        canvas1/image: img1
        canvas2/image: img2
        canvas3/image: img3
        rcvReleaseMat mat1
        rcvReleaseMat mat2
        rcvReleaseMat mat2
    ]
]


; ***************** Test Program ****************************
view win: layout [
        title "Laplacian convolution on matrix"
        button "Load" [loadImage]
        button 60 "Quit" [  rcvReleaseImage img1 
                            rcvReleaseImage img2
                            Quit]
        return
        text 100 "Source" pad 412x0 
        text 120 "Standard convolution"
        pad 402x0 
        text "Normalized convolution"
        return
        canvas1: base isize img1
        canvas2: base isize img2
        canvas3: base isize img3
]

Result