Search code examples
haskellimage-processingpixelconvolutionblur

Blurring Image with convolution matrices - artifacts (Haskell)


I'm trying to do some image processing with Haskell (using JuicyPixels). I've done this function that apply Gaussian Blur to the image and there's some strange artifacts in processed image:

blur :: Image PixelRGBA8 -> Image PixelRGBA8 
blur img@Image {..} = generateImage blurrer imageWidth imageHeight
       where blurrer x y | x >= (imageWidth - offset) || x < offset
                          || y >= (imageHeight - offset) || y < offset = whitePx
                         | otherwise = do
                let applyKernel i j p | j >= matrixLength = applyKernel (i + 1) 0 p
                                      | i >= matrixLength = p 
                                      | otherwise = do 
                                         let outPixel = pxMultNum 
                                                         (pixelAt img (x + j - offset) (y + i - offset)) 
                                                         (gblurMatrix !! i !! j)
                                         applyKernel i (j+1) (outPixel `pxPlus` p)
                applyKernel 0 0 zeroPx
             gblurMatrix = [[1  / 255, 4  / 255,  6 / 255,  4 / 255, 1 / 255],
                            [4  / 255, 16 / 255, 24 / 255, 16 / 255, 4 / 255],
                            [6  / 255, 24 / 255, 36 / 255, 24 / 255, 6 / 255],
                            [4  / 255, 16 / 255, 24 / 255, 16 / 255, 4 / 255],
                            [1  / 255, 4  / 255,  6 / 255,  4 / 255, 1 / 255]]
             matrixLength = length gblurMatrix
             offset = matrixLength `div` 2

whitePx = PixelRGBA8 255 255 255 255
zeroPx = PixelRGBA8 0 0 0 255

pxPlus :: PixelRGBA8 -> PixelRGBA8 -> PixelRGBA8 
pxPlus (PixelRGBA8 r1 g1 b1 a1) (PixelRGBA8 r2 g2 b2 a2) = PixelRGBA8 (s r1 r2) (s g1 g2) (s b1 b2) 255
  where s p1 p2 | p1 + p2 > 255 = 255
                | p1 + p2 < 0 = 0
                | otherwise = p1 + p2

pxMultNum :: PixelRGBA8 -> Double -> PixelRGBA8
pxMultNum (PixelRGBA8 r g b a) q = PixelRGBA8 (m r) (m g) (m b) (m a)
  where m px = pxify $ fromIntegral px * q -- pxify is just synonym to function rounding a = (floor (a + 0.5))

Here are input image
input image
and output image
output image

It looks like blueish areas are a bit less with 1/256 coefficient in Gaussian Blur Matrix, but how to get rid of them at all?

FIX: I guess it's because of Pixel8 type 0-255 and Int, but I fixed whole thing just by changing 36 in the kernel center to 35, so sum of matrix coefficients is (I believe) 255/255 = 1.


Solution

  • You write

    | p1 + p2 > 255 = 255
    | p1 + p2 < 0 = 0
    

    to protect against overflow, but these conditions are never true. Word8 values are always between 0 and 255. A correct way to check for overflow looks like this:

    | p1 + p2 < p1 = 255
    

    But better would be to avoid overflow in the first place. I note that your blur matrix's coefficients add up to 256/255. Perhaps you could start by fixing that.