4

問題

フォルダーを調べて、フォルダー内の各画像の平均色を見つける Haskell プログラムを作成しました。hackage の repa-devil パッケージを使用して、画像を repa 配列に読み込みます。赤、青、緑のすべての値を加算し、ピクセル数で割って平均を求めます。

-- compiled with -O2
import qualified Data.Array.Repa as R
import Data.Array.Repa.IO.DevIL
import Control.Monad.Trans (liftIO)
import System.Directory (getDirectoryContents)

size :: (R.Source r e) => R.Array r R.DIM3 e -> (Int, Int)
size img = (w, h)
    where (R.Z R.:. h R.:. w R.:. 3) = R.extent img

averageColour :: (R.Source r e, Num e, Integral e) => R.Array r R.DIM3 e -> (Int, Int, Int)
averageColour img = (r `div` n, g `div` n, b `div` n)
    where (w, h)  = size img
          n       = w * h
          (r,g,b) = f 0 0 0 0 0
          f row col r g b
            | row >= w  = f 0 (col + 1) r g b
            | col >= h  = (r, g, b)
            | otherwise = f (row + 1) col (addCol 0 r) (addCol 1 g) (addCol 2 b)
            where addCol x v = v + fromIntegral (img R.! (R.Z R.:. col R.:. row R.:. x))

main :: IO ()
main = do
    files <- fmap (map ("images/olympics_backup/" ++) . filter (`notElem` ["..", "."])) $ getDirectoryContents "images/olympics_backup"
    runIL $ do
        images <- mapM readImage files
        let average = zip (map (\(RGB img) -> averageColour img) images) files
        liftIO . print $ average

このプログラムも、Python イメージ ライブラリを使用して Python で作成しました。同じ方法で画像の平均を見つけます。

import Image

def get_images(folder):
    images = []
    for filename in os.listdir(folder):
        images.append(folder + filename)
    return images

def get_average(filename):
    image = Image.open(filename)
    pixels = image.load()
    r = g = b = 0
    for x in xrange(0, image.size[0]):
        for y in xrange(0, image.size[1]):
            colour = pixels[x, y]
            r += colour[0]
            g += colour[1]
            b += colour[2]
    area = image.size[0] * image.size[1]
    r /= area
    g /= area
    b /= area
    return [(r, g, b), filename, image]

def get_colours(images):
    colours = []
    for image in images:
        try:
            colours.append(get_average(image))
        except:
            continue
    return colours

imgs = get_images('images/olympics_backup/')
print get_colours(imgs)

これらの両方を 301 個のイメージを含むフォルダーで実行すると、Haskell バージョンは 0.2 秒 (0.87 対 0.64) 優れています。Haskell はコンパイル済み言語 (インタープリター型言語よりも高速であることが多い) であり、repa 配列のパフォーマンスが優れていると聞いていたため、これは奇妙に思えます (ただし、これはリストなどの他の Haskell データ型と比較しただけかもしれません)。

私が試したこと

私が最初にしたことは、明示的な再帰を使用していることに気づいたので、折り畳みを使用して置き換えることにしました。これは、配列の境界を超えているかどうかを確認する必要がなくなったことも意味します。

(r,g,b) = foldl' f (0,0,0) [(x, y) | x <- [0..w-1], y <- [0..h-1]]
f (r,g,b) (row,col) = (addCol 0 r, addCol 1 g, addCol 2 b)
        where addCol x v = v + fromIntegral (img R.! (R.Z R.:. col R.:. row R.:. x))

これにより実行速度が遅くなった (1.2 秒) ため、コードをプロファイリングして、ほとんどの時間が費やされている場所を確認することにしました (明らかなボトルネックが発生した場合や、repa-devil パッケージが単に遅い場合に備えて)。プロファイルによると、時間の約 58% が f 関数に費やされ、時間の約 35% が addCol に費やされました。

残念ながら、これをより速く実行する方法は考えられません。関数は単なる配列インデックスと加算です - Python コードと同じです。このコードのパフォーマンスを改善する方法はありますか、それとも Python イメージ ライブラリはパフォーマンスを向上させるだけですか?

4

1 に答える 1