中blur函数_Comonad在图像处理中的应用
前幾天我回答了一個關(guān)于comonad的問題Monad和Comonad到底是什么東西?。其中有講到comonad的應(yīng)用例子,但都還不夠直觀和實用。后來找到一個Comonad在圖像處理中的應(yīng)用的例子,覺得不錯,在這里重新整理一下寫出來。與大家分享一下這個例子。
我們已經(jīng)知道,Comonad最通常的用法是作為一個無窮的stream,可以源源不斷的通過擴展復(fù)制生成新的無窮的stream。只要提供從stream中計算得到值的cokleisli函數(shù),我們就可以得到新的stream,或者新的有限的列表,或者一個累加的值。
例如下面就是生成無窮的自然數(shù)的stream,和對自然數(shù)的stream進(jìn)行滑動窗口求和的Comonad的應(yīng)用例子。
data Stream a = a :< Stream a deriving Show instance Functor Stream wherefmap f (x :< xs) = f x :< fmap f xs instance Comonad Stream whereextract (x :< _) = xduplicate s@(_:<xs) = s :< duplicate xsgenerate :: (a -> a) -> a -> Stream a generate f x = x :< generate f x =>> (f . extract)nats :: Stream Integer nats = generate (+1) 0getWindowSum :: Integer -> Stream Integer -> Integer getWindowSum n (x :< xs)| n == 1 = x| n > 1 = x + getWindowSum (n-1) xs| otherwise = undefinedtensWindows :: Stream Integer tensWindows = nats =>> getWindowSum 10這里nats調(diào)用generate生成了一個有無窮多個自然數(shù)的stream,其類型是Stream Integer。
nats生成自然數(shù)的stream的計算步驟效果如下所示,逐行累加起來就得到了自然數(shù)的stream:
0 0 0 0 0 0 0 0 ...1 1 1 1 1 1 1 1 ...1 1 1 1 1 1 1 1 ...1 1 1 1 1 1 1 1 ...1 1 1 1 1 1 1 1 ...1 1 1 1 1 1 1 1 ...1 1 1 1 1 1 1 1 ...nats的值如下所示:
> nats 0 :< 1 :< 2 :< 3 :< 4 :< 5 :< 6 :< 7 :< ...而getWindowSum函數(shù)則是取出自然數(shù)的stream的前10個自然數(shù),求這10個自然數(shù)的和,得到一個新的無窮的stream。這和信號處理中的滑動濾波的窗口計算是一樣的,其值如下所示:
> tensWindows 45 :< 55 :< 65 :< 75 :< 85 :< 95 :< 105 :< ...我們現(xiàn)在來看圖像處理,圖像可以看成一個有限的二維陣列的數(shù)據(jù)結(jié)構(gòu),如下面的圖所示,并不是一個無窮的stream。圖像處理中用的最多的一種場景是圖像濾波,根據(jù)當(dāng)前圖像的一個點的鄰域像素的值計算出新圖像上該點的值,這種場景是可以應(yīng)用Comonad來計算的。
在圖像濾波的計算中,我們關(guān)心的是圖像上的一個點及其鄰域的點,因此有一個焦點,如下圖所示:
于是,我們可以把圖像看成一個帶焦點的二維陣列的數(shù)據(jù)結(jié)構(gòu)。其中二維陣列可以用帶寬和高參數(shù)的一維向量來表示,再加上焦點坐標(biāo)就得到了我們要的圖像數(shù)據(jù)結(jié)構(gòu)定義。如下所示:
-- | 用一維向量表示的圖像二維陣列 data BoxedImage a = BoxedImage{ biWidth :: !Int, biHeight :: !Int, biData :: !(V.Vector a)}instance Functor BoxedImage wherefmap f (BoxedImage w h d) = BoxedImage w h (fmap f d)-- | 帶焦點的圖像數(shù)據(jù)結(jié)構(gòu) data FocusedImage a = FocusedImage { boxedImage :: !(BoxedImage a), cx :: !Int, cy :: !Int}instance Functor FocusedImage wherefmap f (FocusedImage bi x y) = FocusedImage (fmap f bi) x y-- | 帶焦點的圖像和一般圖像的轉(zhuǎn)換函數(shù) focus :: BoxedImage a -> FocusedImage a focus bi| biWidth bi > 0 && biHeight bi > 0 = FocusedImage bi 0 0| otherwise = error "Can not focus on empty image"unfocus :: FocusedImage a -> BoxedImage a unfocus (FocusedImage bi _ _) = bi帶焦點的圖像數(shù)據(jù)結(jié)構(gòu)FocusedImage a 我們可以將其看成一個Comonad。其extract函數(shù)就是直接把焦點處的值取出來。duplicate函數(shù)就是把這個圖像的二維陣列中的每一個點的值都擴展為一個以該點為焦點的帶焦點的圖像數(shù)據(jù)結(jié)構(gòu),這個新的圖像數(shù)據(jù)結(jié)構(gòu)的二維陣列和原來的是一樣的,保持了相似性。extend函數(shù)的參數(shù)f 是個函數(shù),這個函數(shù)f 將新的帶焦點的圖像數(shù)據(jù)結(jié)構(gòu)reduce為一個圖像的像素值。這里我們回顧一下 extend f = fmap f . duplicate。
instance Comonad FocusedImage whereextract (FocusedImage bi x y) = (biData bi) V.! (biWidth bi * y + x)duplicate (FocusedImage bi@(BoxedImage w h _) x y) = FocusedImage(BoxedImage w h $ V.generate (w * h) $ i ->let (y', x') = i `quotRem` win FocusedImage bi x' y')x y如果extend函數(shù)的參數(shù)f 這個函數(shù)只關(guān)心圖像中的焦點及其鄰域的點的值(一般鄰域是3 x 3,5 x 5),則extend f就是這個帶焦點的圖像數(shù)據(jù)結(jié)構(gòu)的濾波處理函數(shù),得到一個新的帶焦點的圖像數(shù)據(jù)結(jié)構(gòu),這個新的圖像就是濾波處理后的圖像。
例如一個常見的gauss模糊濾波的處理,其計算核大小是3 x 3的,如下所示:
我們就可以定義如下的計算核函數(shù),可以看到其只關(guān)心3 x 3 大小的鄰域的點的值:
blur :: Integral a => V.Vector a -> a blur v = fromIntegral$ (`quot` 16)$ ( nbi 0 + nbi 1 * 2 + nbi 2+ nbi 3 * 2 + nbi 4 * 4 + nbi 5 * 2+ nbi 6 + nbi 7 * 2 + nbi 8)where {-# INLINE nbi #-}nbi :: Int -> Intnbi i = fromIntegral $ v V.! i然后定義如下的獲取鄰域點的值的函數(shù),注意這個neighbour函數(shù)對超出圖像邊界的鄰域點做了特殊處理,使用了回繞的處理方式,這是一種效果較好的處理方式:
neighbour :: Int -> Int -> FocusedImage a -> a neighbour dx dy (FocusedImage bi x y) = (biData bi) V.! (biWidth bi * y' + x')where x' = wrap (x + dx) 0 (biWidth bi - 1)y' = wrap (y + dy) 0 (biHeight bi - 1){-# INLINE wrap #-}wrap i lo hi| i < lo = lo - i -- 坐標(biāo)回繞處理| i > hi = hi - (i - hi) -- 坐標(biāo)回繞處理| otherwise = i再定義一個取遍焦點的鄰域的所有點的值的函數(shù),把這幾個函數(shù)組合起來,就得到了帶焦點的圖像數(shù)據(jù)結(jié)構(gòu)的高斯濾波處理函數(shù)blurImage:
filterImage :: Integral a => (V.Vector a -> a) -> Int -> FocusedImage a -> a filterImage filter kernelW fimg@(FocusedImage bi x y) = filter $V.generate kernelSize $ i -> neighbour (nbx i) (nby i) fimgwhere nbx i = (-(kernelW `quot` 2)) + (i `rem` kernelW);nby i = (-(kernelW `quot` 2)) + (i `quot` kernelW);{-# INLINE kernelSize #-}kernelSize = kernelW * kernelW{-# INLINE blurImage #-} blurImage :: Integral a => FocusedImage a -> a blurImage = filterImage blur 3然后在main函數(shù)中調(diào)用blurImage函數(shù),就可以對圖像進(jìn)行高斯模糊處理了,這里的readImage讀取jpg 文件并將其轉(zhuǎn)換為灰度圖的帶焦點的圖像數(shù)據(jù)結(jié)構(gòu)FocusedImage a,writePng則將帶焦點的圖像數(shù)據(jù)結(jié)構(gòu)FocusedImage a 寫入到png 文件中。這兩個函數(shù)用了JuicyPixels包中提供的readImage和writePng兩個函數(shù),JuicyPixels是非常優(yōu)秀的加載、保存、轉(zhuǎn)換圖像的的Haskell實現(xiàn)。使用它,減少了自己寫加載和保存圖像的函數(shù)的麻煩。
main :: IO () main = doputStrLn "Hello, Haskell!"img <- readImage "data/yingwuhua.jpg"-- writePng "output.png" . unfocus . (=>> ebossImage) . focus $ imgwritePng "output.png" . unfocus . (=>> blurImage) . focus $ img文章的題圖經(jīng)過這個高斯模糊濾波處理后的灰度圖效果如下:
我們還可以定義其他濾波處理函數(shù),比如浮雕效果的濾波處理。我們先定義計算核函數(shù),計算核大小同樣是3 x 3的:
eboss :: Integral a => V.Vector a -> a eboss v = fromIntegral$ (+ 127)$ ( nbi 0 * 3 `quot` 2 + 0 + 0+ 0 + 0 + 0+ 0 + 0 + nbi 8 * (-3) `div` 2)where {-# INLINE nbi #-}nbi :: Int -> Intnbi i = fromIntegral $ v V.! i重復(fù)利用前面定義的取遍焦點的鄰域的所有點的函數(shù),就得到了浮雕效果的濾波處理函數(shù):
{-# INLINE ebossImage #-} ebossImage :: Integral a => FocusedImage a -> a ebossImage = filterImage eboss 3這里由于Haskell的惰性計算特性,eboss計算核函數(shù)中的0 項不參與計算,計算速度比高斯模糊濾波處理快了60%左右,其灰度圖的效果圖如下:
Comonad的cokleisli函數(shù)是可以組合的,因此我們也可以把這些帶焦點的圖像的濾波處理函數(shù)組合起來,得到一個多種濾波組合的功能更豐富的濾波處理函數(shù):
reduceNoise :: Integral a => FocusedImage a -> a reduceNoise fimg =let !original = extract fimg!blurred = blurImage fimg!edged = fromIntegral original - fromIntegral blurred :: Int!threshold = if edged < 7 && edged < (-7) then 0 else edgedin fromIntegral $ fromIntegral blurred + threshold由于上面的濾波處理函數(shù)的計算核是一樣大小的,其組合起來所耗費的濾波處理時間比單個的濾波處理時間差別很小。
這是Comonad的一個很有意思的應(yīng)用例子,大家可以在此基礎(chǔ)上做些進(jìn)一步的嘗試。
參考和引用:
Image Processing with Comonads?jaspervdj.be總結(jié)
以上是生活随笔為你收集整理的中blur函数_Comonad在图像处理中的应用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 动画学信奥 漫画学算法 CSP-J入门级
- 下一篇: python判断_python的判断