Haskell中的图像邻域处理

时间:2014-12-28 05:22:33

标签: image haskell image-processing functional-programming

我是Haskell的新手,并试图通过思考图像处理来学习它。

到目前为止,我一直在思考如何在Haskell(或任何函数式编程语言)中实现邻域过滤算法。

如何在功能上写入空间平均滤波器(例如3x3内核,5x5图像)?来自完全强制性的背景,我似乎无法想出一种方法来构建数据,以便解决方案优雅,或者不通过迭代图像矩阵来实现,这看起来并不是很好声明性的。

1 个答案:

答案 0 :(得分:3)

使用功能语言可以轻松地与社区合作。像内核卷积这样的操作是高阶函数,可以用函数式编程语言的常用工具之一 - 列表来编写。

要编写一些真实有用的代码,我们首先会假装解释一个库。

假装

您可以将每个图像视为一个函数,从图像中的坐标到该坐标处保存的数据的值。这将在所有可能的坐标上定义,因此将它与一些bounds配对会很有用,它会告诉我们函数的定义位置。这会建议像

这样的数据类型
data Image coordinate value = Image {
    lowerBound :: coordinate,
    upperBound :: coordinate,
    value      :: coordinate -> value
}

Haskell的数据类型非常相似,名为Array in Data.Array。此数据类型附带了valueImage函数不具备的附加功能 - 它会记住每个坐标的值,以便永远不需要重新计算。我们将使用三个函数与Array一起使用,我将根据如何为上面Image定义它们来描述这些函数。这将有助于我们看到即使我们使用非常有用的Array类型,也可以根据函数和代数数据类型编写所有内容。

 type Array i e = Image i e

bounds获取Array

的范围
 bounds :: Array i e -> (i, i)
 bounds img = (lowerBound img, upperBound img)

!Array

中查找一个值
 (!) :: Array i e -> i -> e
 img ! coordinate = value img coordinate

最后,makeArray构建了Array

 makeArray :: Ix i => (i, i) -> (i -> e) -> Array i e
 makeArray (lower, upper) f = Image lower upper f

Ix是类似图像坐标的类型类,它们有一个range。大多数基类型都有实例,例如IntIntegerBoolChar等。例如range (1, 5)[1, 2, 3, 4, 5]。还有一些产品或元组的实例本身有Ix个实例;元组的实例范围超出每个组件范围的所有组合。例如,range (('a',1),('c',2))

[('a',1),('a',2),
 ('b',1),('b',2),
 ('c',1),('c',2)]`

我们只对Ix类型类,range :: Ix a => (a, a) -> [a]inRange :: Ix a => a -> (a, a) -> Bool中的两个函数感兴趣。 inRange会快速检查某个值是否在range的结果中。

现实

实际上,makeArray并未提供Data.Array,但我们可以根据listArray来定义Array,该range从项目列表中构建bounds与其import Data.Array makeArray :: (Ix i) => (i, i) -> (i -> e) -> Array i e makeArray bounds f = listArray bounds . map f . range $ bounds

convolve订单相同
Ix

当我们Monoid一个带有内核的数组时,我们将通过将内核中的坐标添加到我们正在计算的坐标来计算邻域。 Int类型类不要求我们可以将两个索引组合在一起。在Integer基础上有一个“结合的东西”的候选类型类,但没有+*或其他数字的实例,因为有多种合理的方法可以将它们组合在一起:Offset.+.。为了解决这个问题,我们将为与名为Offset的新运算符组合的内容创建自己的类型类Ix。通常我们不会制作类型,除了有法律的东西。我们只是说class Offset a where (.+.) :: a -> a -> a 应该与Integer合理地“合作”。

9

instance Offset Integer where (.+.) = (+) s,Haskell在编写像Offset这样的整数文字时使用的默认类型,可以用作偏移量。

instance (Offset a, Offset b) => Offset (a, b) where
    (x1, y1) .+. (x2, y2) = (x1 .+. x2, y1 .+. y2)

此外,0可以成对组合的事物的对或元组。

pad background

在我们编写卷积之前,我们还有一个皱纹 - 我们将如何处理图像的边缘?为简单起见,我打算用!填充它们。 bounds生成的Array版本定义在任何地方,在background的{​​{1}}之外,它会返回pad :: Ix i => e -> Array i e -> i -> e pad background array i = if inRange (bounds array) i then array ! i else background

convolve

我们现在准备为convolve a b编写更高阶的函数。 b将图片a与内核convolve卷积在一起。 Array是高阶,因为它的每个参数及其结果都是!,它实际上是函数boundsconvolve :: (Num n, Ix i, Offset i) => Array i n -> Array i n -> Array i n convolve a b = makeArray (bounds b) f where f i = sum . map (g i) . range . bounds $ a g i o = a ! o * pad 0 b (i .+. o) 的组合。

convolve

b带有内核a的图片bounds,我们会在与b相同的f上定义新图片。图像中的每个点都可以通过函数sum来计算,*是内核a中值的乘积(pad)和b中的值。对于内核o range的{​​{1}}中的每个偏移bounds,{1}} ded image a

实施例

通过上一节中的六个声明,我们可以编写您请求的示例,一个空间平均滤波器,其中3x3内核应用于5x5图像。下面定义的内核a是一个3x3映像,它使用9个采样邻居中每个邻居的值的九分之一。 5x5图片b是一个渐变,从左上角的2增加到右下角的10

main = do 
    let
        a = makeArray ((-1, -1), (1, 1)) (const (1.0/9))
        b = makeArray ((1,1),(5,5)) (\(x,y) -> fromInteger (x + y))
        c = convolve a b
    print b
    print c

print输入b

array ((1,1),(5,5))
[((1,1),2.0),((1,2),3.0),((1,3),4.0),((1,4),5.0),((1,5),6.0)
,((2,1),3.0),((2,2),4.0),((2,3),5.0),((2,4),6.0),((2,5),7.0)
,((3,1),4.0),((3,2),5.0),((3,3),6.0),((3,4),7.0),((3,5),8.0)
,((4,1),5.0),((4,2),6.0),((4,3),7.0),((4,4),8.0),((4,5),9.0)
,((5,1),6.0),((5,2),7.0),((5,3),8.0),((5,4),9.0),((5,5),10.0)]

convolve d输出c

array ((1,1),(5,5))
[((1,1),1.3333333333333333),((1,2),2.333333333333333),((1,3),2.9999999999999996),((1,4),3.6666666666666665),((1,5),2.6666666666666665)
,((2,1),2.333333333333333),((2,2),3.9999999999999996),((2,3),5.0),((2,4),6.0),((2,5),4.333333333333333)
,((3,1),2.9999999999999996),((3,2),5.0),((3,3),6.0),((3,4),7.0),((3,5),5.0)
,((4,1),3.6666666666666665),((4,2),6.0),((4,3),7.0),((4,4),8.0),((4,5),5.666666666666666)
,((5,1),2.6666666666666665),((5,2),4.333333333333333),((5,3),5.0),((5,4),5.666666666666666),((5,5),4.0)]

根据您要执行的操作的复杂程度,您可能会考虑使用更常用的库,例如经常推荐的repa,而不是为自己实现图像处理工具包。