首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Haskell的图像邻域处理

Haskell的图像邻域处理
EN

Stack Overflow用户
提问于 2014-12-28 05:22:33
回答 1查看 498关注 0票数 1

我是Haskell的新手,我试着从图像处理的角度来学习它。

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

空间平均过滤器(例如3x3内核,5x5映像)将如何编写功能?从一个完全命令式的背景出发,我似乎无法想出一种方法来构造数据,以便解决方案是优雅的,或者不通过迭代图像矩阵来完成它,而这看起来并不是非常具有声明性。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2014-12-28 07:42:38

使用函数式语言很容易与社区打交道。像核卷积这样的操作是高阶函数,可以用函数编程语言的一种常用工具--列表来编写。

为了编写一些真正的、有用的代码,我们将首先扮演假装解释一个库。

装作

您可以将每个图像看作一个函数,从图像中的坐标到在该坐标下保存的数据的值。这将在所有可能的坐标上定义,因此将其与一些bounds配对是有用的,后者告诉我们函数的定义位置。这意味着数据类型类似于

代码语言:javascript
复制
data Image coordinate value = Image {
    lowerBound :: coordinate,
    upperBound :: coordinate,
    value      :: coordinate -> value
}

Haskell有一个非常类似的数据类型,名为Data.Array。此数据类型附带了value函数在Image中不会具有的另一个特性--它记住每个坐标的值,这样就不需要重新计算。我们将使用三个函数来处理Array,我将根据上面如何为Image定义它们来描述这些函数。这将帮助我们看到,即使我们使用的是非常有用的Array类型,一切都可以用函数和代数数据类型编写。

代码语言:javascript
复制
 type Array i e = Image i e

bounds获得了Array的界

代码语言:javascript
复制
 bounds :: Array i e -> (i, i)
 bounds img = (lowerBound img, upperBound img)

!Array中查找一个值

代码语言:javascript
复制
 (!) :: Array i e -> i -> e
 img ! coordinate = value img coordinate

最后,makeArray构建了一个Array

代码语言:javascript
复制
 makeArray :: Ix i => (i, i) -> (i -> e) -> Array i e
 makeArray (lower, upper) f = Image lower upper f

Ix是行为类似于图像坐标的事物的典型类型,它们有一个range。大多数基本类型都有实例,如IntIntegerBoolChar等。例如,range of (1, 5) is [1, 2, 3, 4, 5]。还有一个产品的实例或拥有Ix实例的事物的元组;元组的实例涵盖每个组件范围的所有组合。例如,range (('a',1),('c',2))

代码语言:javascript
复制
[('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) -> BoolinRange快速检查range的结果中是否有一个值。

现实

实际上,makeArray不是由Data.Array提供的,但是我们可以用listArray来定义它,它根据与其boundsrange相同顺序的项列表构造Array

代码语言:javascript
复制
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,但是没有IntInteger或其他数字的实例,因为组合它们的方法不止一种:+*。为了解决这个问题,我们将为与一个名为Offset的新运算符相结合的东西制作我们自己的类型化.+.。通常我们不做打字机,除了有法律的东西。我们只想说,Offset应该“明智地”使用Ix

代码语言:javascript
复制
class Offset a where
    (.+.) :: a -> a -> a

Integers是Haskell在编写整数文本(如9 )时使用的默认类型,可以用作偏移量。

代码语言:javascript
复制
instance Offset Integer where
    (.+.) = (+)

另外,Offset的对或元组可以成对组合。

代码语言:javascript
复制
instance (Offset a, Offset b) => Offset (a, b) where
    (x1, y1) .+. (x2, y2) = (x1 .+. x2, y1 .+. y2)

在写卷积之前,我们还有一个皱纹--我们将如何处理图像的边缘?为了简单起见,我打算用0填充它们。pad background制作了一个在任何地方定义的!版本,在返回backgroundArraybounds之外。

代码语言:javascript
复制
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,它实际上是一个函数!和它的bounds的组合。

代码语言:javascript
复制
convolve :: (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创建图像b,我们将在与b相同的bounds上创建一个新映像。图像中的每个点都可以由函数f计算,函数sum是内核a中值的乘积(*),以及内核aboundsrange中的每个偏移量opadd图像b中的值。

示例

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

代码语言:javascript
复制
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

printed输入b

代码语言:javascript
复制
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)]

convolved输出c

代码语言:javascript
复制
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)]

根据您想要做的事情的复杂性,您可以考虑使用更成熟的库,比如oft推荐的雷帕,而不是为自己实现一个图像处理工具包。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/27673882

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档