module OpenCV.ImgProc.StructuralAnalysis
( contourArea
, pointPolygonTest
, findContours
, Contour(..)
, ContourAreaOriented(..)
, ContourRetrievalMode(..)
, ContourApproximationMethod(..)
, approxPolyDP
, arcLength
, minAreaRect
) where
import "primitive" Control.Monad.Primitive ( PrimMonad, PrimState, unsafePrimToPrim )
import "base" Control.Exception ( mask_ )
import "base" Control.Monad (guard)
import "base" Data.Functor (($>))
import "base" Data.Int
import "base" Data.Maybe (mapMaybe)
import "base" Data.Traversable (for)
import qualified "vector" Data.Vector as V
import "base" Data.Word
import "base" Foreign.C.Types
import "base" Foreign.Marshal.Alloc ( alloca )
import "base" Foreign.Marshal.Array ( peekArray )
import "base" Foreign.Marshal.Utils ( fromBool )
import "base" Foreign.Ptr ( Ptr )
import "base" Foreign.Storable ( peek )
import "base" System.IO.Unsafe ( unsafePerformIO )
import qualified "inline-c" Language.C.Inline as C
import qualified "inline-c-cpp" Language.C.Inline.Cpp as C
import qualified "inline-c" Language.C.Inline.Unsafe as CU
import "linear" Linear.V4 ( V4(..) )
import "this" OpenCV.Core.Types ( Mut )
import "this" OpenCV.Core.Types.Point
import "this" OpenCV.Core.Types.Vec ( fromVec )
import "this" OpenCV.Internal.C.Inline ( openCvCtx )
import "this" OpenCV.Internal.C.Types
import "this" OpenCV.Internal.Core.Types
import "this" OpenCV.Internal.Core.Types.Mat
import "this" OpenCV.Internal.Exception
import "this" OpenCV.TypeLevel
C.context openCvCtx
C.include "opencv2/core.hpp"
C.include "opencv2/imgproc.hpp"
C.using "namespace cv"
contourArea
:: (IsPoint2 point2 CFloat)
=> V.Vector (point2 CFloat)
-> ContourAreaOriented
-> CvExcept Double
contourArea contour areaOriented = unsafeWrapException $
withArrayPtr (V.map toPoint contour) $ \contourPtr ->
alloca $ \c'area ->
handleCvException (realToFrac <$> peek c'area) $
[cvExcept|
cv::_InputArray contour =
cv::_InputArray( $(Point2f * contourPtr)
, $(int32_t c'numPoints)
);
*$(double * c'area) = cv::contourArea(contour, $(bool c'oriented));
|]
where
oriented =
case areaOriented of
ContourAreaOriented -> True
ContourAreaAbsoluteValue -> False
c'numPoints = fromIntegral $ V.length contour
c'oriented = fromBool oriented
pointPolygonTest
:: ( IsPoint2 contourPoint2 CFloat
, IsPoint2 testPoint2 CFloat
)
=> V.Vector (contourPoint2 CFloat)
-> testPoint2 CFloat
-> Bool
-> CvExcept Double
pointPolygonTest contour pt measureDist = unsafeWrapException $
withArrayPtr (V.map toPoint contour) $ \contourPtr ->
withPtr (toPoint pt) $ \ptPtr ->
alloca $ \c'resultPtr ->
handleCvException (realToFrac <$> peek c'resultPtr) $
[cvExcept|
cv::_InputArray contour =
cv::_InputArray( $(Point2f * contourPtr)
, $(int32_t c'numPoints)
);
*$(double * c'resultPtr) =
cv::pointPolygonTest( contour
, *$(Point2f * ptPtr)
, $(bool c'measureDist)
);
|]
where
c'numPoints = fromIntegral $ V.length contour
c'measureDist = fromBool measureDist
data ContourAreaOriented
= ContourAreaOriented
| ContourAreaAbsoluteValue
data ContourRetrievalMode
= ContourRetrievalExternal
| ContourRetrievalList
| ContourRetrievalCComp
| ContourRetrievalTree
data ContourApproximationMethod
= ContourApproximationNone
| ContourApproximationSimple
| ContourApproximationTC89L1
| ContourApproximationTC89KCOS
c'CV_RETR_EXTERNAL = 0
c'CV_RETR_EXTERNAL :: (Num a) => a
c'CV_RETR_LIST = 1
c'CV_RETR_LIST :: (Num a) => a
c'CV_RETR_CCOMP = 2
c'CV_RETR_CCOMP :: (Num a) => a
c'CV_RETR_TREE = 3
c'CV_RETR_TREE :: (Num a) => a
c'CV_CHAIN_APPROX_NONE = 1
c'CV_CHAIN_APPROX_NONE :: (Num a) => a
c'CV_CHAIN_APPROX_SIMPLE = 2
c'CV_CHAIN_APPROX_SIMPLE :: (Num a) => a
c'CV_CHAIN_APPROX_TC89_L1 = 3
c'CV_CHAIN_APPROX_TC89_L1 :: (Num a) => a
c'CV_CHAIN_APPROX_TC89_KCOS = 4
c'CV_CHAIN_APPROX_TC89_KCOS :: (Num a) => a
marshalContourRetrievalMode
:: ContourRetrievalMode -> Int32
marshalContourRetrievalMode = \case
ContourRetrievalExternal -> c'CV_RETR_EXTERNAL
ContourRetrievalList -> c'CV_RETR_LIST
ContourRetrievalCComp -> c'CV_RETR_CCOMP
ContourRetrievalTree -> c'CV_RETR_TREE
marshalContourApproximationMethod
:: ContourApproximationMethod -> Int32
marshalContourApproximationMethod = \case
ContourApproximationNone -> c'CV_CHAIN_APPROX_NONE
ContourApproximationSimple -> c'CV_CHAIN_APPROX_SIMPLE
ContourApproximationTC89L1 -> c'CV_CHAIN_APPROX_TC89_L1
ContourApproximationTC89KCOS -> c'CV_CHAIN_APPROX_TC89_KCOS
data Contour =
Contour
{ contourPoints :: !(V.Vector Point2i)
, contourChildren :: !(V.Vector Contour)
} deriving Show
findContours
:: (PrimMonad m)
=> ContourRetrievalMode
-> ContourApproximationMethod
-> Mut (Mat ('S [h, w]) ('S 1) ('S Word8)) (PrimState m)
-> m (V.Vector Contour)
findContours mode method src = unsafePrimToPrim $
withPtr src $ \srcPtr ->
alloca $ \(contourLengthsPtrPtr :: Ptr (Ptr Int32)) ->
alloca $ \(contoursPtrPtr :: Ptr (Ptr (Ptr (Ptr C'Point2i)))) ->
alloca $ \(hierarchyPtrPtr :: Ptr (Ptr (Ptr C'Vec4i))) ->
alloca $ \(numContoursPtr :: Ptr Int32) -> mask_ $ do
[C.block| void {
std::vector< std::vector<cv::Point> > contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(
*$(Mat * srcPtr),
contours,
hierarchy,
$(int32_t c'mode),
$(int32_t c'method)
);
*$(int32_t * numContoursPtr) = contours.size();
cv::Point * * * * contoursPtrPtr = $(Point2i * * * * contoursPtrPtr);
cv::Point * * * contoursPtr = new cv::Point * * [contours.size()];
*contoursPtrPtr = contoursPtr;
cv::Vec4i * * * hierarchyPtrPtr = $(Vec4i * * * hierarchyPtrPtr);
cv::Vec4i * * hierarchyPtr = new cv::Vec4i * [contours.size()];
*hierarchyPtrPtr = hierarchyPtr;
int32_t * * contourLengthsPtrPtr = $(int32_t * * contourLengthsPtrPtr);
int32_t * contourLengthsPtr = new int32_t [contours.size()];
*contourLengthsPtrPtr = contourLengthsPtr;
for (std::vector< std::vector<cv::Point> >::size_type i = 0; i < contours.size(); i++) {
std::vector<cv::Point> & contourPoints = contours[i];
cv::Vec4i hierarchyInfo = hierarchy[i];
contourLengthsPtr[i] = contourPoints.size();
cv::Point * * newContourPoints = new cv::Point * [contourPoints.size()];
for (std::vector<cv::Point>::size_type j = 0; j < contourPoints.size(); j++) {
cv::Point & orig = contourPoints[j];
cv::Point * newPt = new cv::Point(orig.x, orig.y);
newContourPoints[j] = newPt;
}
contoursPtr[i] = newContourPoints;
hierarchyPtr[i] = new cv::Vec4i(
hierarchyInfo[0],
hierarchyInfo[1],
hierarchyInfo[2],
hierarchyInfo[3]
);
}
}|]
numContours <- fromIntegral <$> peek numContoursPtr
contourLengthsPtr <- peek contourLengthsPtrPtr
contourLengths <- peekArray numContours contourLengthsPtr
contoursPtr <- peek contoursPtrPtr
unmarshalledContours <- peekArray numContours contoursPtr
allContours <- for (zip unmarshalledContours contourLengths) $ \(contourPointsPtr,n) ->
fmap V.fromList
(peekArray (fromIntegral n) contourPointsPtr >>= mapM (fromPtr . pure))
hierarchyPtr <- peek hierarchyPtrPtr
(hierarchy :: [V4 Int32]) <-
peekArray numContours hierarchyPtr >>=
mapM (fmap fromVec . fromPtr . pure)
let treeHierarchy :: V.Vector ([Contour], Bool)
treeHierarchy = V.fromList $
zipWith
(\(V4 nextSibling previousSibling firstChild parent) points ->
( Contour { contourPoints = points
, contourChildren =
if firstChild < 0
then mempty
else V.fromList $ fst $ treeHierarchy V.! fromIntegral firstChild
} : if nextSibling < 0
then []
else fst $ treeHierarchy V.! fromIntegral nextSibling
, parent < 0 && previousSibling < 0
)
)
hierarchy
allContours
[CU.block| void {
delete [] *$(Point2i * * * * contoursPtrPtr);
delete [] *$(Vec4i * * * hierarchyPtrPtr);
delete [] *$(int32_t * * contourLengthsPtrPtr);
} |]
return $ V.fromList $ concat
$ mapMaybe (\(contours,isRoot) -> guard isRoot $> contours)
$ V.toList treeHierarchy
where
c'mode = marshalContourRetrievalMode mode
c'method = marshalContourApproximationMethod method
approxPolyDP
:: (IsPoint2 point2 Int32)
=> V.Vector (point2 Int32)
-> Double
-> Bool
-> V.Vector Point2i
approxPolyDP curve epsilon isClosed = unsafePerformIO $
withArrayPtr (V.map toPoint curve) $ \curvePtr ->
alloca $ \(pointsResPtrPtr ::Ptr (Ptr (Ptr C'Point2i))) ->
alloca $ \(numPointsResPtr :: Ptr Int32) -> mask_ $ do
[C.block| void {
std::vector<cv::Point> points_res;
cv::_InputArray curve = cv::_InputArray ($(Point2i * curvePtr), $(int32_t c'numPoints));
cv::approxPolyDP
( curve
, points_res
, $(double c'epsilon)
, $(bool c'isClosed)
);
*$(int32_t * numPointsResPtr) = points_res.size();
cv::Point * * * pointsResPtrPtr = $(Point2i * * * pointsResPtrPtr);
cv::Point * * pointsResPtr = new cv::Point * [points_res.size()];
*pointsResPtrPtr = pointsResPtr;
for (std::vector<cv::Point>::size_type i = 0; i < points_res.size(); i++) {
cv::Point & ptAddress = points_res[i];
cv::Point * newPt = new cv::Point(ptAddress.x, ptAddress.y);
pointsResPtr[i] = newPt;
}
}|]
numPoints <- fromIntegral <$> peek numPointsResPtr
pointsResPtr <- peek pointsResPtrPtr
(pointsResList :: [Point2i]) <- peekArray numPoints pointsResPtr >>= mapM (fromPtr . pure)
let pointsRes :: V.Vector (Point2i)
pointsRes = V.fromList pointsResList
[CU.block| void {
delete [] *$(Point2i * * * pointsResPtrPtr);
} |]
return pointsRes
where
c'numPoints = fromIntegral $ V.length curve
c'isClosed = fromBool isClosed
c'epsilon = realToFrac epsilon
arcLength
:: (IsPoint2 point2 Int32)
=> V.Vector (point2 Int32)
-> Bool
-> CvExcept Double
arcLength curve isClosed = unsafeWrapException $
withArrayPtr (V.map toPoint curve) $ \curvePtr ->
alloca $ \c'resultPtr ->
handleCvException (realToFrac <$> peek c'resultPtr) $
[cvExcept|
cv::_InputArray curve =
cv::_InputArray ( $(Point2i * curvePtr)
, $(int32_t c'numPoints)
);
*$(double * c'resultPtr) =
cv::arcLength( curve
, $(bool c'isClosed)
);
|]
where
c'isClosed = fromBool isClosed
c'numPoints = fromIntegral $ V.length curve
minAreaRect :: (IsPoint2 point2 Int32)
=> V.Vector (point2 Int32) -> RotatedRect
minAreaRect points =
unsafePerformIO $ fromPtr $
withArrayPtr (V.map toPoint points) $ \pointsPtr ->
[CU.exp|
RotatedRect * {
new RotatedRect(
cv::minAreaRect(
cv::_InputArray( $(Point2i * pointsPtr)
, $(int32_t c'numPoints))))
}
|]
where
c'numPoints = fromIntegral $ V.length points