forked from sinelaw/HOpenCV
/
Image.hsc
370 lines (322 loc) · 14.6 KB
/
Image.hsc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
{-# LANGUAGE ForeignFunctionInterface, EmptyDataDecls, ScopedTypeVariables,
TypeFamilies, MultiParamTypeClasses, FlexibleInstances, GADTs,
BangPatterns, FlexibleContexts, TypeSynonymInstances,
DataKinds, TemplateHaskell, ConstraintKinds #-}
{-# OPTIONS_GHC -funbox-strict-fields -fno-warn-unused-binds #-}
module OpenCV.Core.Image (
-- * Phantom types that statically describe image properties
Channels(..), ROIEnabled(..),
-- * Value-level reification of type-level properties
HasDepth(..),
-- * Typed support for image operations that take scalar (color) parameters
CvScalarT, AsCvScalar(..), ScalarOK,
-- * Low-level image data structure
Image(..), mkImage, mallocImage, blackImage, blackoutPixels,
withIplImage, bytesPerPixel, numChannels, peekIpl, pokeIpl,
freeROI, c_cvSetImageROI, c_cvResetImageROI, setROI, resetROI, imageHasROI,
UpdateROI(..)
) where
import OpenCV.Core.CxCore (IplImage,Depth(..),iplDepth8u, iplDepth16u,
iplDepth16s, iplDepth32f, iplDepth64f, cvFree,
CvRect(..), CvScalar(..))
import OpenCV.Core.CV (cvCvtColor)
import OpenCV.Core.ColorConversion (cv_GRAY2BGR, cv_BGR2GRAY)
import Control.Applicative ((<$>))
import Control.Monad (when)
import Data.Bits (complement, (.&.))
import Data.Int
import Data.Proxy
import Data.Singletons hiding (Proxy)
import Data.Singletons.TH
import Data.Word (Word8, Word16)
import Foreign.C.Types
import Foreign.ForeignPtr
import Foreign.Marshal.Alloc (alloca)
import Foreign.Marshal.Array (allocaArray, peekArray)
import Foreign.Ptr
import Foreign.Storable
import Unsafe.Coerce
#include <opencv2/core/types_c.h>
{-
typedef struct _IplImage
{
int nSize;
int ID;
int nChannels;
int alphaChannel;
int depth;
char colorModel[4];
char channelSeq[4];
int dataOrder;
int origin;
int align;
int width;
int height;
struct _IplROI *roi;
struct _IplImage *maskROI;
void *imageId;
struct _IplTileInfo *tileInfo;
int imageSize;
char *imageData;
int widthStep;
int BorderMode[4];
int BorderConst[4];
char *imageDataOrigin;
}
IplImage;
-}
-- *Phantom types that statically describe image properties
data Channels = Trichromatic | Monochromatic
data ROIEnabled = HasROI | NoROI
genSingletons [''Channels, ''ROIEnabled]
-- NOTE: The singletons library defines various things for us that we
-- don't explicitly pattern match on or export, leading GHC to issue
-- unused binding warnings. We disable those warnings for this file.
numChannels' :: Channels -> Int
numChannels' Trichromatic = 3
numChannels' Monochromatic = 1
-- |Extract number of channels from the singleton value associated
-- with a type of the 'Channels' kind.
numChannels :: forall c. SingI c => Proxy (c::Channels) -> Int
numChannels _ = numChannels' . fromSing $ (sing::Sing c)
hasROI :: forall r. SingI r => Proxy (r::ROIEnabled) -> Bool
hasROI _ = case fromSing (sing::Sing r) of
HasROI -> True
_ -> False
imageHasROI :: forall c d r. SingI r => Image c d r -> Bool
imageHasROI _ = case fromSing (sing::Sing r) of
HasROI -> True
_ -> False
class (Storable a, Num a) => HasDepth a where
depth :: a -> Depth
toDouble :: a -> Double
fromDouble :: Double -> a
instance HasDepth Word8 where
depth _ = iplDepth8u
toDouble = fromIntegral
fromDouble = round
instance HasDepth Word16 where
depth _ = iplDepth16u
toDouble = fromIntegral
fromDouble = round
instance HasDepth Int16 where
depth _ = iplDepth16s
toDouble = fromIntegral
fromDouble = round
instance HasDepth Float where
depth _ = iplDepth32f
toDouble = realToFrac
fromDouble = realToFrac
instance HasDepth Double where
depth _ = iplDepth64f
toDouble = id
fromDouble = id
-- |An image with a particular number of channels have an associated
-- scalar type built from the type of its pixels. This class lets us
-- ensure that a scalar value to be used in an operation with an image
-- is compatible with that image.
type family CvScalarT (c::Channels) d :: *
type instance CvScalarT Monochromatic d = d
type instance CvScalarT Trichromatic d = (d,d,d)
type ScalarOK s c d = (AsCvScalar s, s ~ CvScalarT c d)
-- |Scalar types are often round-tripped via doubles in OpenCV to
-- allow for non-overloaded interfaces of functions with scalar
-- parameters.
class AsCvScalar x where
toCvScalar :: x -> CvScalar
fromCvScalar :: CvScalar -> x
instance AsCvScalar Word8 where
toCvScalar = depthToScalar
fromCvScalar (CvScalar r _ _ _) = floor r
instance AsCvScalar Word16 where
toCvScalar = depthToScalar
fromCvScalar (CvScalar r _ _ _) = floor r
instance AsCvScalar Int16 where
toCvScalar = depthToScalar
fromCvScalar (CvScalar r _ _ _) = floor r
instance AsCvScalar Float where
toCvScalar = depthToScalar
fromCvScalar (CvScalar r _ _ _) = realToFrac r
instance AsCvScalar Double where
toCvScalar = depthToScalar
fromCvScalar (CvScalar r _ _ _) = realToFrac r
instance (HasDepth d, AsCvScalar d) => AsCvScalar (d,d,d) where
toCvScalar (r,g,b) = let f = realToFrac . toDouble
in CvScalar (f r) (f g) (f b) 0
fromCvScalar (CvScalar r g b _) = let f = fromDouble . realToFrac
in (f r, f g, f b)
depthToScalar :: HasDepth d => d -> CvScalar
depthToScalar x = let x' = realToFrac (toDouble x)
in CvScalar x' x' x' x'
bytesPerPixel :: HasDepth d => d -> Int
bytesPerPixel = (`div` 8) . fromIntegral . unSign . unDepth . depth
where unSign = (complement #{const IPL_DEPTH_SIGN} .&.)
-- |A data structure representing the information OpenCV uses from an
-- 'IplImage' struct. It includes the pixel origin, image width, image
-- height, image size (number of bytes), a pointer to the pixel data,
-- and the row stride. Its type is parameterized by the number of
-- color channels (i.e. 'Monochromatic' or 'Trichromatic'), the pixel
-- depth (e.g. 'Word8', 'Float'), and whether or not the image has a
-- region-of-interest (ROI) set ('HasROI' or 'NoROI').
data Image (c::Channels) d (r::ROIEnabled) where
Image :: (SingI c, HasDepth d, SingI r, UpdateROI r) =>
{ origin :: !Int
, width :: !Int
, height :: !Int
, roi :: !(Maybe CvRect)
, imageSize :: !Int
, imageData :: !(ForeignPtr d)
, imageDataOrigin :: !(ForeignPtr d)
, widthStep :: !Int
} -> Image c d r
-- |@mkImage w h ptr@ makes an 'Image' of width, @w@, and height, @h@,
-- using pixel data at @ptr@. Pixels are assumed to be continuous, and
-- starting at the given pointer.
mkImage :: forall a c d. (Integral a, SingI c, HasDepth d) =>
a -> a -> ForeignPtr d -> Image c d NoROI
mkImage w h pixels = Image 0 (fromIntegral w) (fromIntegral h)
Nothing (fromIntegral h*stride) pixels pixels stride
where stride = fromIntegral $
fromIntegral w * numChannels (Proxy::Proxy c) * bytesPerPixel (undefined::d)
-- |Set an image's region-of-interest.
setROI :: CvRect -> Image c d r -> Image c d HasROI
setROI r (Image o w h _ sz d ido ws) = Image o w h (Just r) sz d ido ws
{-# INLINE setROI #-}
-- |Clear any region-of-interest set for an image.
resetROI :: forall c d r. Image c d r -> Image c d NoROI
resetROI x@(Image o w h _ sz d ido ws)
| hasROI (Proxy::Proxy r) = Image o w h Nothing sz d ido ws
| otherwise = unsafeCoerce x
{-# INLINE resetROI #-}
-- |Prepare an 'Image' of the given width and height. The pixel and
-- color depths are gleaned from the type, and may often be inferred.
mallocImage :: forall a c d. (SingI c, HasDepth d, Integral a) =>
a -> a -> IO (Image c d NoROI)
mallocImage w h = mkImage w h <$> mallocForeignPtrArray (fromIntegral numBytes)
where numBytes = fromIntegral (w * h) *
numChannels (Proxy::Proxy c) * bytesPerPixel (undefined::d)
foreign import ccall "memset"
memset :: Ptr Word8 -> Word8 -> CInt -> IO ()
-- |Set an all of an 'Image'\'s pixels to black.
blackoutPixels :: Image c d r -> IO (Image c d r)
blackoutPixels img = do withForeignPtr (imageData img) $ \ptr ->
memset (castPtr ptr) 0 (fromIntegral $ imageSize img)
return img
-- |Prepare an 'Image' of the given width and height with all pixels
-- set to zero.
blackImage :: (SingI c, HasDepth d, Integral a) =>
a -> a -> IO (Image c d NoROI)
blackImage w h = mallocImage w h >>= blackoutPixels
foreign import ccall "HOpenCV_wrap.h c_cvSetRoi"
c_cvSetImageROI :: Ptr IplImage -> CInt -> CInt -> CInt -> CInt -> IO ()
foreign import ccall "opencv2/core/core_c.h cvResetImageROI"
c_cvResetImageROI :: Ptr IplImage -> IO ()
withROI :: Image c d r -> Ptr IplImage -> (Ptr IplImage -> IO a) -> IO a
withROI img p f = case roi img of
Nothing -> f p
Just (CvRect x y w h) -> do c_cvSetImageROI p x y w h
r <- f p
c_cvResetImageROI p
return r
-- |Provides the supplied function with a 'Ptr' to the 'IplImage'
-- underlying the given 'Image'.
withIplImage :: Image c d r -> (Ptr IplImage -> IO b) -> IO b
withIplImage img@(Image{}) f = alloca $ \p ->
withForeignPtr (imageData img)
(\hp -> do pokeIpl img p (castPtr hp)
withROI img p f)
h2c :: Int -> CInt
h2c = fromIntegral
c2h :: CInt -> Int
c2h = fromIntegral
-- |Read a 'Image' from a 'Ptr' 'IplImage'
peekIpl :: (SingI c, HasDepth d, SingI r, UpdateROI r) =>
Ptr IplImage -> IO (Image c d r)
peekIpl = peek . castPtr
-- Poke a 'Ptr' 'IplImage' with a specific imageData 'Ptr' that is
-- currently valid. This is solely an auxiliary function to
-- 'withHIplImage'.
pokeIpl :: forall c d r. (SingI c, HasDepth d) =>
Image (c::Channels) d r -> Ptr IplImage -> Ptr Word8 -> IO ()
pokeIpl himg ptr hp =
do (#poke IplImage, nSize) ptr ((#size IplImage)::CInt)
(#poke IplImage, ID) ptr (0::CInt)
(#poke IplImage, nChannels) ptr (h2c $ numChannels (Proxy::Proxy c))
(#poke IplImage, depth) ptr (unDepth (depth (undefined::d)))
(#poke IplImage, dataOrder) ptr (0::CInt)
(#poke IplImage, origin) ptr (h2c $ origin himg)
(#poke IplImage, align) ptr (4::CInt)
(#poke IplImage, width) ptr (h2c $ width himg)
(#poke IplImage, height) ptr (h2c $ height himg)
(#poke IplImage, roi) ptr nullPtr
(#poke IplImage, maskROI) ptr nullPtr
(#poke IplImage, imageId) ptr nullPtr
(#poke IplImage, tileInfo) ptr nullPtr
(#poke IplImage, imageSize) ptr (h2c $ imageSize himg)
(#poke IplImage, imageData) ptr hp
(#poke IplImage, widthStep) ptr (h2c $ widthStep himg)
(#poke IplImage, imageDataOrigin) ptr hp
foreign import ccall "HOpenCV_wrap.h c_cvGetROI"
c_cvGetImageROI :: Ptr IplImage -> Ptr CInt -> IO ()
freeROI :: Ptr IplImage -> IO ()
freeROI ptr = do p <- (#peek IplImage, roi) ptr
if (ptrToIntPtr p == 0) then return () else cvFree p
maybePeekROI :: Ptr IplImage -> Ptr () -> IO (Maybe CvRect)
maybePeekROI img p | p == nullPtr = return Nothing
| otherwise = allocaArray 4 $
\r -> do c_cvGetImageROI img r
[x,y,w,h] <- peekArray 4 r
return . Just $ CvRect x y w h
-- |An internal class that makes runtime guarantees about type level
-- ROI assertions.
class SingI a => UpdateROI (a::ROIEnabled) where
updateROI :: Maybe CvRect -> Image c d a -> Image c d b
-- These functions are runtime checks that type-level guarantees are
-- met.
instance UpdateROI NoROI where
updateROI Nothing x = unsafeCoerce $ resetROI x
updateROI _ _ = error "Tried to update the ROI of a NoROI Image"
instance UpdateROI HasROI where
updateROI (Just r) x = unsafeCoerce $ setROI r x
updateROI _ _ = error "Tried to null out the ROI of a HasROI Image"
-- |An 'Image' in Haskell conforms closely to OpenCV's 'IplImage'
-- structure type. Note that obtaining an 'Image' from an 'IplImage'
-- via 'peek' will not install a Haskell finalizer on the underlying
-- pixel data. That data is the responsibility of the provider of the
-- 'IplImage'. 'Image' values constructed within the Haskell runtime,
-- on the other hand, will have their underlying pixel data buffers
-- managed by the garbage collector.
instance forall c d r. (SingI c, HasDepth d, SingI r, UpdateROI r) =>
Storable (Image c d r) where
sizeOf _ = (#size IplImage)
alignment _ = alignment (undefined :: CDouble)
poke = error "Poking a 'Ptr Image' is unsafe."
peek ptr = do
numChannels' <- c2h <$> (#peek IplImage, nChannels) ptr
depth' <- Depth <$> (#peek IplImage, depth) ptr
width' <- c2h <$> (#peek IplImage, width) ptr
height' <- c2h <$> (#peek IplImage, height) ptr
roir <- (#peek IplImage, roi) ptr >>= maybePeekROI (castPtr ptr)
when (depth' /= (depth (undefined::d)))
(error $ "IplImage has depth "++show depth'++
" but desired Image has depth "++
show (depth (undefined::d)))
if numChannels (Proxy::Proxy c) /= numChannels'
then do img2' <- mallocImage width' height' :: IO (Image c d NoROI)
let img2 = updateROI roir img2' :: Image c d r
let conv = if numChannels' == 1
then cv_GRAY2BGR
else cv_BGR2GRAY
ptr' = castPtr ptr :: Ptr IplImage
withIplImage img2 $ \dst -> cvCvtColor (castPtr ptr')
(castPtr dst)
conv
(#peek IplImage, imageDataOrigin) ptr >>= cvFree
return $ unsafeCoerce img2
else do origin' <- c2h <$> (#peek IplImage, origin) ptr
imageSize' <- c2h <$> (#peek IplImage, imageSize) ptr
imageData' <- (#peek IplImage, imageData) ptr >>= newForeignPtr_
imageDataOrigin' <- (#peek IplImage, imageDataOrigin) ptr >>= newForeignPtr_
widthStep' <- c2h <$> (#peek IplImage, widthStep) ptr
return $ Image origin' width' height' roir imageSize'
imageData' imageDataOrigin' widthStep'