我是Haskell的新手,我试着从图像处理的角度来学习它。
到目前为止,我一直在思考如何用Haskell (或者任何函数式编程语言)来实现邻里过滤算法。
空间平均过滤器(例如3x3内核,5x5映像)将如何编写功能?从一个完全命令式的背景出发,我似乎无法想出一种方法来构造数据,以便解决方案是优雅的,或者不通过迭代图像矩阵来完成它,而这看起来并不是非常具有声明性。
发布于 2014-12-28 07:42:38
使用函数式语言很容易与社区打交道。像核卷积这样的操作是高阶函数,可以用函数编程语言的一种常用工具--列表来编写。
为了编写一些真正的、有用的代码,我们将首先扮演假装解释一个库。
装作
您可以将每个图像看作一个函数,从图像中的坐标到在该坐标下保存的数据的值。这将在所有可能的坐标上定义,因此将其与一些bounds配对是有用的,后者告诉我们函数的定义位置。这意味着数据类型类似于
data Image coordinate value = Image {
lowerBound :: coordinate,
upperBound :: coordinate,
value :: coordinate -> value
}Haskell有一个非常类似的数据类型,名为Data.Array。此数据类型附带了value函数在Image中不会具有的另一个特性--它记住每个坐标的值,这样就不需要重新计算。我们将使用三个函数来处理Array,我将根据上面如何为Image定义它们来描述这些函数。这将帮助我们看到,即使我们使用的是非常有用的Array类型,一切都可以用函数和代数数据类型编写。
type Array i e = Image i ebounds获得了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 fIx是行为类似于图像坐标的事物的典型类型,它们有一个range。大多数基本类型都有实例,如Int、Integer、Bool、Char等。例如,range of (1, 5) is [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来定义它,它根据与其bounds的range相同顺序的项列表构造Array
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 -> aIntegers是Haskell在编写整数文本(如9 )时使用的默认类型,可以用作偏移量。
instance Offset Integer where
(.+.) = (+)另外,Offset的对或元组可以成对组合。
instance (Offset a, Offset b) => Offset (a, b) where
(x1, y1) .+. (x2, y2) = (x1 .+. x2, y1 .+. y2)在写卷积之前,我们还有一个皱纹--我们将如何处理图像的边缘?为了简单起见,我打算用0填充它们。pad background制作了一个在任何地方定义的!版本,在返回background的Array的bounds之外。
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的组合。
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中值的乘积(*),以及内核a的bounds的range中的每个偏移量o的padd图像b中的值。
示例
使用上一节中的六个声明,我们可以编写您所请求的示例,一个空间平均过滤器,其中包含一个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 cprinted输入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)]convolved输出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)]根据您想要做的事情的复杂性,您可以考虑使用更成熟的库,比如oft推荐的雷帕,而不是为自己实现一个图像处理工具包。
https://stackoverflow.com/questions/27673882
复制相似问题