Image Haskell中的图像邻域处理
我是Haskell的新手,试图从图像处理的角度来学习它 到目前为止,我一直在思考如何在Haskell(或者任何函数式编程语言)中实现邻域过滤算法Image Haskell中的图像邻域处理,image,haskell,image-processing,functional-programming,Image,Haskell,Image Processing,Functional Programming,我是Haskell的新手,试图从图像处理的角度来学习它 到目前为止,我一直在思考如何在Haskell(或者任何函数式编程语言)中实现邻域过滤算法 空间平均滤波器(比如3x3内核,5x5图像)在功能上是如何编写的?来自一个完全必要的背景,我似乎无法想出一种方法来构造数据以使解决方案优雅,或者不通过迭代图像矩阵来实现,这似乎不是很具有声明性。使用函数式语言很容易优雅地处理邻里关系。像内核卷积这样的操作是高阶函数,可以用函数式编程语言的一种常用工具——列表来编写 为了编写一些真正有用的代码,我们将首先
空间平均滤波器(比如3x3内核,5x5图像)在功能上是如何编写的?来自一个完全必要的背景,我似乎无法想出一种方法来构造数据以使解决方案优雅,或者不通过迭代图像矩阵来实现,这似乎不是很具有声明性。使用函数式语言很容易优雅地处理邻里关系。像内核卷积这样的操作是高阶函数,可以用函数式编程语言的一种常用工具——列表来编写 为了编写一些真正有用的代码,我们将首先假装解释一个库 假装 可以将每个图像视为从图像中的坐标到该坐标处保存的数据值的函数。这将在所有可能的坐标上定义,因此将它与一些告诉我们函数定义位置的
边界配对将非常有用。这将建议使用以下数据类型
data Image coordinate value = Image {
lowerBound :: coordinate,
upperBound :: coordinate,
value :: coordinate -> value
}
Haskell有一种非常类似的数据类型,称为。此数据类型附带了图像
中的值
函数不具备的附加功能-它记住每个坐标的值,因此无需重新计算。我们将使用三个函数来处理Array
s,我将描述如何为上面的Image
定义它们。这将帮助我们看到,即使我们使用非常有用的数组
类型,所有内容都可以用函数和代数数据类型来编写
type Array i e = Image i e
bounds
获取数组的边界
bounds :: Array i e -> (i, i)
bounds img = (lowerBound img, upperBound img)
(!) :: Array i e -> i -> e
img ! coordinate = value img coordinate
makeArray :: Ix i => (i, i) -> (i -> e) -> Array i e
makeArray (lower, upper) f = Image lower upper f
代码>在数组中查找值
bounds :: Array i e -> (i, i)
bounds img = (lowerBound img, upperBound img)
(!) :: Array i e -> i -> e
img ! coordinate = value img coordinate
makeArray :: Ix i => (i, i) -> (i -> e) -> Array i e
makeArray (lower, upper) f = Image lower upper f
最后,makeArray
构建一个Array
bounds :: Array i e -> (i, i)
bounds img = (lowerBound img, upperBound img)
(!) :: Array i e -> i -> e
img ! coordinate = value img coordinate
makeArray :: Ix i => (i, i) -> (i -> e) -> Array i e
makeArray (lower, upper) f = Image lower upper f
Ix
是一个类型类,用于处理类似于图像坐标的对象,它们有一个范围
。大多数基本类型都有实例,如Int
,Integer
,Bool
,Char
,等等。例如(1,5)
的范围是[1,2,3,4,5]
。还有一个产品实例或事物元组实例,它们本身具有Ix
实例;元组的实例覆盖每个组件范围的所有组合。例如,范围(('a',1),('c',2))
是
[('a',1),('a',2),
('b',1),('b',2),
('c',1),('c',2)]`
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)]
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)]
我们只对Ix
typeclass中的两个函数感兴趣,range::Ix a=>(a,a)->[a]
和inRange::Ix a=>a->(a,a)->Bool
inRange
快速检查值是否在范围的结果中
现实
实际上,makeArray
不是由Data.Array
提供的,但是我们可以用listArray
来定义它,它从一个项目列表中以与其边界的范围
相同的顺序构造一个数组
import Data.Array
makeArray :: (Ix i) => (i, i) -> (i -> e) -> Array i e
makeArray bounds f = listArray bounds . map f . range $ bounds
当我们用一个核对一个数组进行卷积时,我们将通过把核中的坐标加到我们正在计算的坐标来计算邻域。Ix
typeclass不要求我们可以将两个索引组合在一起。在基中有一个候选类型类用于“组合的事物”,即幺半群,但没有Int
或Integer
或其他数字的实例,因为组合它们的方法不止一种:+
和*
。为了解决这个问题,我们将为与名为+的新操作符相结合的东西创建自己的类型类Offset
。
。通常我们不创建类型类,除非有规律。我们只能说,Offset
应该“合理地”使用Ix
class Offset a where
(.+.) :: a -> a -> a
Integer
s,Haskell在编写类似9
的整数文本时使用的默认类型,可以用作偏移量
instance Offset Integer where
(.+.) = (+)
此外,偏移量
的成对或元组可以成对组合
instance (Offset a, Offset b) => Offset (a, b) where
(x1, y1) .+. (x2, y2) = (x1 .+. x2, y1 .+. y2)
在写卷积之前,我们还有一条皱纹——我们将如何处理图像的边缘?为了简单起见,我打算用0
填充它们<代码>键盘背景
制作一个版本的
在数组的边界之外的任何地方定义,它返回背景
pad :: Ix i => e -> Array i e -> i -> e
pad background array i =
if inRange (bounds array) i
then array ! i
else background
我们现在准备为卷积
编写一个高阶函数<代码>卷积AB
将图像b
与内核a
卷积<代码>卷积
是高阶的,因为它的每个参数及其结果都是一个数组
,它实际上是一个函数的组合
及其边界
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)
为了使用内核a
对图像b
进行卷积,我们在与b
相同的边界上定义了一个新图像。图像中的每个点都可以通过函数f
计算,该函数求和s内核a
中的值与pad
ded图像b
中的值的乘积(*
),该函数用于内核a
的范围中的每个偏移o
例子
使用上一节中的六个声明,我们可以编写您请求的示例,一个应用于5x5图像的3x3内核的空间平均过滤器。下面定义的内核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
打印
ed输入