module Vision.Detector.Edge (canny) where
import Control.Monad (when)
import Control.Monad.ST (ST)
import Data.Int
import Data.Vector.Storable (enumFromN, forM_)
import Foreign.Storable (Storable)
import Vision.Image (
Image, ImagePixel, Manifest, MutableManifest, Grey, DerivativeType (..)
, (!), shape, linearIndex, fromFunction
, create, new', linearRead, linearWrite
, sobel
)
import Vision.Primitive (Z (..), (:.) (..), inShape, ix2)
data EdgeDirection = NorthSouth
| WestEast
| NorthEastSouthWest
| NorthWestSouthEast
canny :: ( Image src, Integral (ImagePixel src), Bounded res, Eq res
, Storable res)
=> Int
-> Int32
-> Int32
-> src
-> Manifest res
canny !derivSize !lowThres !highThres !img =
create $ do
edges <- newManifest
forM_ (enumFromN 0 h) $ \y -> do
let !lineOffset = y * w
forM_ (enumFromN 0 w) $ \x -> do
visitPoint edges x y (lineOffset + x) highThres'
return edges
where
!size@(Z :. h :. w) = shape img
(!lowThres', !highThres') = (square lowThres, square highThres)
dx, dy :: Manifest Int16
!dx = sobel derivSize DerivativeX img
!dy = sobel derivSize DerivativeY img
dxy :: Manifest Int32
!dxy = fromFunction size $ \pt ->
square (fromIntegral $ dx ! pt)
+ square (fromIntegral $ dy ! pt)
newManifest :: (Storable p, Bounded p) => ST s (MutableManifest p s)
newManifest = new' size minBound
visitPoint !edges !x !y !linearIX !thres = do
val <- linearRead edges linearIX
when (val == minBound) $ do
let !ptDxy = dxy `linearIndex` linearIX
ptDx = dx `linearIndex` linearIX
ptDy = dy `linearIndex` linearIX
direction = edgeDirection ptDx ptDy
when (ptDxy > thres && isMaximum x y ptDxy direction) $ do
linearWrite edges linearIX maxBound
visitNeighbour edges x y direction
visitNeighbour !edges !x !y !direction = do
let (!x1, !y1, !x2, !y2) =
case direction of
NorthSouth -> (x, y 1, x, y + 1)
WestEast -> (x 1, y, x + 1, y )
NorthEastSouthWest -> (x 1, y 1, x + 1, y + 1)
NorthWestSouthEast -> (x + 1, y 1, x 1, y + 1)
when (inShape size (ix2 y1 x1)) $
visitPoint edges x1 y1 (y1 * w + x1) lowThres'
when (inShape size (ix2 y2 x2)) $
visitPoint edges x2 y2 (y2 * w + x2) lowThres'
isMaximum !x !y !ptDxy !direction =
let (!x1, !y1, !x2, !y2) =
case direction of
NorthSouth -> (x 1, y, x + 1, y )
WestEast -> (x, y 1, x, y + 1)
NorthEastSouthWest -> (x + 1, y 1, x 1, y + 1)
NorthWestSouthEast -> (x 1, y 1, x + 1, y + 1)
in tryCompare ptDxy (>) (x1, y1) && tryCompare ptDxy (>=) (x2, y2)
tryCompare !ptDxy op !(x, y)
| inShape size (ix2 y x) = ptDxy `op` fromIntegral (dxy ! ix2 y x)
| otherwise = True
edgeDirection ptDx ptDy =
let !angle = atan2 (double ptDy) (double ptDx)
in if angle >= 0 then if | angle > pi8x7 -> NorthSouth
| angle > pi8x5 -> NorthEastSouthWest
| angle > pi8x3 -> WestEast
| angle > pi8 -> NorthWestSouthEast
| otherwise -> NorthSouth
else if | angle < pi8x7 -> NorthSouth
| angle < pi8x5 -> NorthWestSouthEast
| angle < pi8x3 -> WestEast
| angle < pi8 -> NorthEastSouthWest
| otherwise -> NorthSouth
!pi8 = pi / 8
!pi8x3 = pi8 * 3
!pi8x5 = pi8 * 5
!pi8x7 = pi8 * 7
square :: Num a => a -> a
square a = a * a
double :: Integral a => a -> Double
double = fromIntegral