{-# LANGUAGE BangPatterns #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeSynonymInstances #-} -- | -- Module : Graphics.Image.Processing.Filter -- Copyright : (c) Alexey Kuleshevich 2017 -- License : BSD3 -- Maintainer : Alexey Kuleshevich -- Stability : experimental -- Portability : non-portable -- module Graphics.Image.Processing.Filter ( -- * Filter Filter (..) , applyFilter , Direction(..) -- * Gaussian , gaussianLowPass , gaussianBlur -- * Sobel , sobelFilter , sobelOperator -- * Prewitt , prewittFilter , prewittOperator -- * Laplacian , laplacianFilter -- * Laplacian of Gaussian , logFilter -- * Gaussian Smoothing , gaussianSmoothingFilter -- * Mean , meanFilter -- * Unsharp Masking , unsharpMaskingFilter ) where import Graphics.Image.Interface as I import Graphics.Image.Processing.Convolution import Graphics.Image.ColorSpace (X) -- | Filter that can be applied to an image using `applyFilter`. -- -- @since 1.5.3 data Filter arr cs e = Filter { applyFilter :: Image arr cs e -> Image arr cs e -- ^ Apply a filter to an image } -- | Used to specify direction for some filters. data Direction = Vertical | Horizontal -- | Create a Gaussian Filter. -- -- @since 1.5.3 gaussianLowPass :: (Array arr cs e, Array arr X e, Floating e, Fractional e) => Int -- ^ Radius -> e -- ^ Sigma -> Border (Pixel cs e) -- ^ Border resolution technique. -> Filter arr cs e gaussianLowPass !r !sigma border = Filter (correlate border gV' . correlate border gV) where !gV = compute $ (gauss / scalar weight) !gV' = compute $ transpose gV !gauss = makeImage (1, n) getPx !weight = I.fold (+) 0 gauss !n = 2 * r + 1 !sigma2sq = 2 * sigma ^ (2 :: Int) getPx (_, j) = promote $ exp (fromIntegral (-((j - r) ^ (2 :: Int))) / sigma2sq) {-# INLINE getPx #-} {-# INLINE gaussianLowPass #-} -- | Create a Gaussian Blur filter. Radius will be derived from standard -- deviation: @ceiling (2*sigma)@ and `Edge` border resolution will be -- utilized. If custom radius and/or border resolution is desired, -- `gaussianLowPass` can be used instead. -- -- @since 1.5.3 gaussianBlur :: (Array arr cs e, Array arr X e, Floating e, RealFrac e) => e -- ^ Sigma -> Filter arr cs e gaussianBlur !sigma = gaussianLowPass (ceiling (2*sigma)) sigma Edge {-# INLINE gaussianBlur #-} sobelFilter :: (Array arr cs e, Array arr X e) => Direction -> Border (Pixel cs e) -> Filter arr cs e sobelFilter dir !border = Filter (correlate border kernel) where !kernel = case dir of Vertical -> fromLists $ [ [ -1, -2, -1 ] , [ 0, 0, 0 ] , [ 1, 2, 1 ] ] Horizontal -> fromLists $ [ [ -1, 0, 1 ] , [ -2, 0, 2 ] , [ -1, 0, 1 ] ] {-# INLINE sobelFilter #-} -- sobelFilter :: Array arr cs e => -- Direction -> Border (Pixel cs e) -> Filter arr cs e -- sobelFilter dir !border = -- Filter (convolveCols border cV . convolveRows border rV) -- where -- !(rV, cV) = -- case dir of -- Vertical -> ([1, 2, 1], [1, 0, -1]) -- Horizontal -> ([1, 0, -1], [1, 2, 1]) -- {-# INLINE sobelFilter #-} sobelOperator :: (Array arr cs e, Array arr X e, Floating e) => Image arr cs e -> Image arr cs e sobelOperator !img = sqrt (sobelX ^ (2 :: Int) + sobelY ^ (2 :: Int)) where !sobelX = applyFilter (sobelFilter Horizontal Edge) img !sobelY = applyFilter (sobelFilter Vertical Edge) img {-# INLINE sobelOperator #-} prewittFilter :: (Array arr cs e, Array arr X e) => Direction -> Border (Pixel cs e) -> Filter arr cs e prewittFilter dir !border = Filter (convolveCols border cV . convolveRows border rV) where !(rV, cV) = case dir of Vertical -> ([1, 1, 1], [1, 0, -1]) Horizontal -> ([1, 0, -1], [1, 1, 1]) {-# INLINE prewittFilter #-} prewittOperator :: (Array arr cs e, Array arr X e, Floating e) => Image arr cs e -> Image arr cs e prewittOperator !img = sqrt (prewittX ^ (2 :: Int) + prewittY ^ (2 :: Int)) where !prewittX = applyFilter (prewittFilter Horizontal Edge) img !prewittY = applyFilter (prewittFilter Vertical Edge) img {-# INLINE prewittOperator #-} -- |The Laplacian of an image highlights regions of rapid intensity change -- and is therefore often used for edge detection. It is often applied to an -- image that has first been smoothed with something approximating a -- Gaussian smoothing filter in order to reduce its sensitivity to noise. -- More info about the algo at -- -- <> <> -- laplacianFilter :: (Array arr cs e, Array arr X e) => Border (Pixel cs e) -> Filter arr cs e laplacianFilter !border = Filter (correlate border kernel) where !kernel = fromLists $ [ [ -1, -1, -1 ] -- Unlike the Sobel edge detector, the Laplacian edge detector uses only one kernel. , [ -1, 8, -1 ] -- It calculates second order derivatives in a single pass. , [ -1, -1, -1 ]] -- This is the approximated kernel used for it. (Includes diagonals) {-# INLINE laplacianFilter #-} -- | 'Laplacian of Gaussian' (LOG) filter is a two step process of smoothing -- an image before applying some derivative filter on it. This comes in -- need for reducing the noise sensitivity while working with noisy -- datasets or in case of approximating second derivative measurements. -- -- The LoG operator takes the second derivative of the image. Where the image -- is basically uniform, the LoG will give zero. Wherever a change occurs, the LoG will -- give a positive response on the darker side and a negative response on the lighter side. -- More info about the algo at -- -- <> <> -- logFilter :: (Array arr cs e, Array arr X e) => Border (Pixel cs e) -> Filter arr cs e logFilter !border = Filter (correlate border kernel) where !kernel = fromLists $ [ [ 0, 1, 1, 2, 2, 2, 1, 1, 0 ] , [ 1, 2, 4, 5, 5, 5, 4, 2, 1 ] , [ 1, 4, 5, 3, 0, 3, 5, 4, 1 ] , [ 2, 5, 3, -12, -24, -12, 3, 5, 2 ] , [ 2, 5, 0, -24, -40, -24, 0, 5, 2 ] , [ 2, 5, 3, -12, -24, -12, 3, 5, 2 ] , [ 1, 4, 5, 3, 0, 3, 5, 4, 1 ] , [ 1, 2, 4, 5, 5, 5, 4, 2, 1 ] , [ 0, 1, 1, 2, 2, 2, 1, 1, 0 ] ] {-# INLINE logFilter #-} -- | The Gaussian smoothing operator is a 2-D convolution operator that is used to -- `blur' images and remove detail and noise. The idea of Gaussian smoothing is to use -- this 2-D distribution as a `point-spread' function, and this is achieved by convolution. -- Since the image is stored as a collection of discrete pixels we need to produce a -- discrete approximation to the Gaussian function before we can perform the convolution. -- More info about the algo at -- -- <> <> -- gaussianSmoothingFilter :: (Fractional e, Array arr cs e, Array arr X e) => Border (Pixel cs e) -> Filter arr cs e gaussianSmoothingFilter !border = Filter (I.map (/ 273) . correlate border kernel) where !kernel = fromLists $ [[ 1, 4, 7, 4, 1 ] -- Discrete approximation to the Gaussian function. ,[ 4, 16, 26, 16, 4 ] -- 273 is the sum of all values in the mask and hence used in rescaling. ,[ 7, 26, 41, 26, 7 ] ,[ 4, 16, 26, 16, 4 ] ,[ 1, 4, 7, 4, 1 ]] {-# INLINE gaussianSmoothingFilter #-} -- | The mean filter is a simple sliding-window spatial filter that replaces the -- center value in the window with the average (mean) of all the pixel values in -- the window. The window, or kernel, can be any shape, but this one uses the most -- common 3x3 square kernel. -- More info about the algo at -- -- <> <> -- meanFilter :: (Fractional e, Array arr cs e, Array arr X e) => Border (Pixel cs e) -> Filter arr cs e meanFilter !border = Filter (I.map (/ 9) . correlate border kernel) where !kernel = fromLists $[ [ 1, 1, 1 ] -- Replace each pixel with the mean value of its neighbors, including itself. , [ 1, 1, 1 ] , [ 1, 1, 1 ]] {-# INLINE meanFilter #-} -- | The unsharp-masking filter is a sharpening operator which derives its name from -- the fact that it enhances edges (and other high frequency components in an image) -- via a procedure which subtracts an unsharp, or smoothed, version of an image from -- the original image. It is commonly used in the photographic and printing industries -- for crispening edges. -- More info about the algo at -- -- <> <> -- unsharpMaskingFilter :: (Fractional e, Array arr cs e, Array arr X e) => Border (Pixel cs e) -> Filter arr cs e unsharpMaskingFilter !border = Filter (I.map (/256) . correlate border kernel) where !kernel = fromLists $ [[ -1, -4, -6, -4, -1 ] ,[ -4, -16, -24, -16, -4 ] -- Uses negative image to create a mask of the original image. ,[ -6, -24, 476, -24, -6 ] -- The unsharped mask is then combined with the positive (original) image. ,[ -4, -16, -24, -16, -4 ] -- So, the resulting image is less blurry, i.e clearer. ,[ -1, -4, -6, -4, -1 ]] {-# INLINE unsharpMaskingFilter #-}