hip-1.5.5.0: src/Graphics/Image/Processing/Filter.hs
{-# 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 <lehins@yandex.ru>
-- Stability : experimental
-- Portability : non-portable
--
module Graphics.Image.Processing.Filter
( -- * 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 <https://homepages.inf.ed.ac.uk/rbf/HIPR2/log.htm>
--
-- <<images/yield.jpg>> <<images/yield_laplacian.png>>
--
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 <https://homepages.inf.ed.ac.uk/rbf/HIPR2/log.htm>
--
-- <<images/yield.jpg>> <<images/yield_log.png>>
--
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 <https://homepages.inf.ed.ac.uk/rbf/HIPR2/gsmooth.htm>
--
-- <<images/GSM_gsn_yield_IP.jpg>> <<images/GSM_gsn_yield_OP.png>>
--
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 <http://homepages.inf.ed.ac.uk/rbf/HIPR2/mean.htm>
--
-- <<images/yield.jpg>> <<images/yield_mean.png>>
--
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 <https://homepages.inf.ed.ac.uk/rbf/HIPR2/unsharp.htm>
--
-- <<images/yield_gray.png>> <<images/yield_unsharpMasking.png>>
--
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 #-}