用随机颜色填充封闭区域 - Haskell - 星期五 [英] Filling the enclosed areas with random colors - Haskell - Friday

查看:179
本文介绍了用随机颜色填充封闭区域 - Haskell - 星期五的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图执行不是非常复杂的图像分析,试图找到不同的形状,并计算一些参数,如面积和周长(以像素为单位),我试图在Haskell中做到这一点(我想这样做,尝试并使用函数式编程语言)。

第一项任务是计算图像上的勺子数量:

我正在使用我不能找到迭代所有像素的方式。
我在下面的包中找到读取 readLinear 函数:


I am trying to perform not very complex image analysis to try and find distinct shapes and calculate some of their parameters like area and perimeter (in pixels) and I am trying to do this in Haskell (I wanted to do that to try and work with functional programming language).

The first task in line is to count the amount of spoons on the image: I am using Friday Haskell package to work with images.

My idea was to use the Friday's edge detection and then fill all of the enclosed areas with it's fill function. The first one would require me to iterate over image's pixels until i've stumbled upon a black pixel. Than I would fill the area and continue the search in the image (which now has one of it's objects filled). I could color different objects with random colors and associate these colors with their objects to find their areas and perimeters.

Here is how this image looks after I've applied edge detection to it:

I was unable to find the way of iterating over all of the pixels though. I've found those read and readLinear functions in the following package: https://hackage.haskell.org/package/friday-0.2.2.0/docs/Vision-Image-Mutable.html#v:linearRead, but I am not sure how to use them and I was unable to deduce that from their type signature since I am very very new to Haskell.

Here is the code that does all of the image reading, grayscaling and edge detecting:

{-# LANGUAGE ScopedTypeVariables #-}
import Prelude hiding (filter)
import System.Environment (getArgs)

import Vision.Detector.Edge (canny)
import Vision.Image
import Vision.Image.Storage.DevIL (Autodetect (..), load, save)

detectEdges :: RGBA -> Grey
detectEdges img =
  let grey = convert img :: Grey
      -- Img blurring --
      blurRadius = 2
      blurred = gaussianBlur blurRadius (Nothing :: Maybe Double) grey :: Grey

      -- Sobel applying --
      sobelRadius = 2
      lowThreshold = 256
      highThreshold = 1024
  in (canny sobelRadius lowThreshold highThreshold blurred) :: Grey

processImg :: RGBA -> RGBA
processImg img =
  let edges = detectEdges img
  -- Here goes all of the important stuff
  in convert edges :: RGBA

main :: IO ()
main = do
  [input, output] <- getArgs

  io <- load Autodetect input
  case io of
    Left err             -> do
      putStrLn "Unable to load the image:"
      print err

    Right (img :: RGBA)  -> do
      mErr <- save Autodetect output (processImg img)
      case mErr of
        Nothing  ->
          putStrLn "Success."
        Just err -> do
          putStrLn "Unable to save the image:"
          print err

Thank you in advance.

解决方案

How do I find area and perimeter of connected components?

You can use the contour tracing from Vision.Image.Contour to get all contour perimeters. First lets start with getting the edges like you have:

{-# LANGUAGE ScopedTypeVariables #-}
import Prelude as P
import System.Environment (getArgs)

import Vision.Detector.Edge (canny)
import Vision.Image
import Vision.Primitive.Shape
import Vision.Image.Storage.DevIL (Autodetect (..), load, save)
import Vision.Image.Transform(floodFill)
import Control.Monad.ST (runST, ST)
import Vision.Image.Contour

-- Detects the edge of the image with the Canny's edge detector.
--
-- usage: ./canny input.png output.png
main :: IO ()
main = do
    [input, output] <- getArgs

    -- Loads the image. Automatically infers the format.
    io <- load Autodetect input

    case io of
        Left err             -> do
            putStrLn "Unable to load the image:"
            print err
        Right (grey :: Grey) -> do
            let blurred, edges :: Grey
                edges = canny 2 256 1024 blurred :: Grey

Here's where we acquire contours. Due to a bug in the draw function, which I use later, I'll blur first to get contours with distinct inner and outer points. This will get patched eventually...

                cs           = contours (blur 2 edges :: Grey)
                goodContours = P.filter goodSize (allContourIds cs)

Now we have a value of this Contours type which includes a valid ContourId for each connected component. For each ContourId you can get its area with contourSize and perimeter with contourPerimeter. The size of the perimeter is just the length of the list of perimeter points.

I just did a really overly-tailored filter, called goodSize to get the spoons, but you can play with area and perimeter all you'd like:

                goodSize x   = let ((xmin,xmax),(ymin,ymax)) = contourBox cs x
                               in xmax-xmin > 60 && xmax-xmin < 500 &&
                                  ymax-ymin > 100 && ymax-ymin < 500

                final, filledContours :: RGBA
                filledContours =
                   convert $ drawContours cs (shape edges) Fill goodContours

Optionally, for each contour use floodFill to get a color. Here I just use three colors and fill whichever contours are first in the list. The contours list is ordered top-to-bottom left-to-right so this will look odd-ish. You could sortBy xmin goodContours to get a left-right ordering.

                floodStart = concatMap (take 1 . contourPerimeter cs) goodContours
                colors = cycle [RGBAPixel 255 0 0 255, RGBAPixel 0 255 0 255, RGBAPixel 0 0 255 255]
                final = runST doFill

The fill operation is using the ST monad, which you can find many questions about here on StackOverflow.

                doFill :: forall s. ST s RGBA
                doFill = do
                          m <- thaw filledContours :: ST s (MutableManifest RGBAPixel s)
                          mapM_ (\(p,c) -> floodFill p c m) (zip floodStart colors)
                          return =<< unsafeFreeze m

            -- Saves the edges image. Automatically infers the output format.
            mErr <- save Autodetect output final
            case mErr of
                Nothing  ->
                    putStrLn "Success."
                Just err -> do
                    putStrLn "Unable to save the image:"
                    print err

contourBox cs x =
  let ps = contourPerimeter cs x
      (xs,ys) = unzip $ P.map (\(Z :. x :. y) -> (x,y)) ps
  in ((minimum xs, maximum xs), (minimum ys, maximum ys))

The end result is:

这篇关于用随机颜色填充封闭区域 - Haskell - 星期五的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆