F #, increasing the contrast of the image by aligning the histograms

The histogram alignment operation (increasing contrast) is often used to increase image quality. Within the framework of this article, the concept of a histogram , the theory of an algorithm for its alignment, and practical examples of the operation of this algorithm will be considered . To facilitate image processing, we will use the OpenCV library . Also, we will make a comparative analysis of the results of our algorithm and the algorithm that is already built into OpenCV .



The histogram is a function h (x) , which returns the total number of pixels whose brightness is equal to x .


Histogram h of halftone image I is given by

h(m)=|(r,c)|I(r,c)=m|


, where m corresponds to the intervals of brightness values

Below is the pseudo-code for calculating the histogram.


//   H    I.

procedure histogram(I,H);
{
    //     .
    for i := 0 to MaxVal
        H[i] := 0;

    //   .
    for L := 0 to MaxRow
        for P := 0 to MaxCol {
            grayval := I[r,c];
            H[grayval] := H[grayval] + 1;
        };
}

Visually, the histogram is a rectangle, the width of which is equal to the maximum possible value of the brightness of a point in the original image. For grayscale images, we will work with a range of brightness of points from 0 to 255, which means that the width of the histogram will be 256. The height of the histogram can be any, but for clarity, we will work with rectangular histograms.


, — 256 ( ), . .


1. h(x)



. — cdf(x) .


, OpenCV , , . , , . F# OpenCVSharp4.


, Visual Studio, . , MacOS, , "runtimes/osx-x64/native/libOpenCvSharpExtern.dylib" , .
Unable to load shared library 'OpenCvSharpExtern' or one of its dependencies


,


open OpenCvSharp
open Microsoft.FSharp.NativeInterop

// mat -   Mat  OpenCV
let getHistogram (mat: Mat) =
    //   
    let hx = Array.zeroCreate<int> 256
    //  
    mat.ForEachAsByte(fun value _ ->
                           let v = int (NativePtr.get value 0)
                           hx.[v] <- hx.[v] + 1)
    //  
    hx

, . X


cdx(x)=h(0)+h(1)+...+h(x)


, X. 1 ( ).


, cdf(x)


let getCdx hx = // hx - 
   //   
   hx |> Array.mapi (fun x _ -> if x > 0 then hx.[0..(x-1)] |> Array.sum else 0)


1 , . ( ), . , , .


2.


2.



, .


:


f(x)=round(cdf(x)-cdfminpixels-1255)



cdf(x)X
cdf_min — ,
pixels
255 — . 255
round


F# :


//  cdf_min
let cdxMin = cdx |> Array.filter (fun v -> v > 0) |> Array.min
//      
let totalPixels = src.Rows * src.Cols // src -   Mat   

for y in 0..src.Rows do
    for x in 0..src.Cols do
        //     
        let s = int(src.At<byte>(y, x))
        //     
        let fx = (float(cdx.[s]) - float(cdxMin))/(float(totalPixels - 1))*255.
        //    
        equalizeImage.Circle(x, y, 1, new Scalar(double(fx)))

, . . , OpenCV .


// Learn more about F# at http://fsharp.org
open OpenCvSharp
open Microsoft.FSharp.NativeInterop

//     
let getHistogram (mat: Mat) =
    let hx = Array.zeroCreate<int> 256
    mat.ForEachAsByte(fun value _ ->
                           let v = int (NativePtr.get value 0)
                           hx.[v] <- hx.[v] + 1)
    hx

//      
let getCdx hx =
    hx |> Array.mapi (fun x _ -> if x > 0 then hx.[0..(x-1)] |> Array.sum else 0)

//     
let drawHistogramAndCdx hx cdx (mat: Mat) =
    let histoWidth = 256
    let histoHeight = 256

    //     
    let cdxMax = cdx |> Array.max
    //      ( )
    //    
    let cdxK = float(histoHeight)/float(cdxMax)

    let histMax = hx |> Array.max
    let histK = float(histoHeight)/float(histMax)

    let histMat = new Mat(histoWidth, histoHeight, MatType.CV_8UC4)
    hx
    |> Array.iteri (fun x v ->
                        let histDy = int(float(v)*histK)
                        let cdxDy = int(float(cdx.[x])*cdxK)
                        //   h(x)
                        mat.Line(x, histoHeight-1, x, histoHeight-1-histDy, Scalar.White)
                        //    cdx(x)
                        mat.Circle(x, histoHeight-cdxDy, 1, Scalar.Blue))

[<EntryPoint>]
let main argv =

    let histoWidth = 256
    let histoHeight = 256

    let src = Cv2.ImRead("cat.jpg", ImreadModes.Grayscale)
    let equalizeImage = new Mat(src.Rows, src.Cols, MatType.CV_8UC1)

    // calculate histogram h(x)
    let hx = getHistogram src

    // calculate cdf(x) = h(0) + h(1) + .. + h(x)
    let cdx = getCdx hx

    // draw histogram
    let histMat = new Mat(histoWidth, histoHeight, MatType.CV_8UC4)
    drawHistogramAndCdx hx cdx histMat

    // equalize the histogram
    let cdxMin = cdx |> Array.filter (fun v -> v > 0) |> Array.min
    let totalPixels = src.Rows * src.Cols

    for y in 0..src.Rows do
        for x in 0..src.Cols do
            let s = int(src.At<byte>(y, x))
            let fx = (float(cdx.[s]) - float(cdxMin))/(float(totalPixels - 1))*255.
            //equalizeImage.Set<Scalar>(y, x, new Scalar(double(fx)))
            equalizeImage.Circle(x, y, 1, new Scalar(double(fx)))

    // calculate equalize histogram
    let hx2 = getHistogram equalizeImage
    let cdx2 = getCdx hx2

    let histMat2 = new Mat(histoWidth, histoHeight, MatType.CV_8UC4)
    drawHistogramAndCdx hx2 cdx2 histMat2

    // opencv equalize histogram
    let opencCVImage = new Mat(src.Rows, src.Cols, MatType.CV_8UC1)
    let in1 = InputArray.Create(src)
    let in2 = OutputArray.Create(opencCVImage)

    Cv2.EqualizeHist(in1, in2)

    // get opencv histogram
    let hx3 = getHistogram opencCVImage
    let cdx3 = getCdx hx3

    let histMat3 = new Mat(histoWidth, histoHeight, MatType.CV_8UC4)
    drawHistogramAndCdx hx3 cdx2 histMat3

    // show results
    use w1 = new Window("original image", src)
    use w2 = new Window("original histogram", histMat)

    use w3 = new Window("custom equalize image", equalizeImage)
    use w4 = new Window("custom equalize histogram", histMat2)

    use w5 = new Window("opencv equalize image", opencCVImage)
    use w6 = new Window("opencv equalize histogram", histMat3)

    Cv2.WaitKey() |> ignore

    0 // return an integer exit code


3.


, , OpenCV.


( ), — , — OpenCV.







The author of the article is far from original and did not come up with anything new, presenting to the public another story about how the histogram alignment algorithm is implemented and why it is needed. The main purpose of the article was not only to talk about the algorithm, but also to give specific practical examples and source codes that you can copy and verify yourself, and possibly experiment. Of course, the histogram algorithm is not the only one used to filter images. But this is the topic of other issues.


All Articles