ImageProcessing
Python3
julia1.0
JuliaImages

JuliaImagesの備忘録その4


Factored kernelsを使った簡単な実験

前回の内容を踏まえて、Factored kernelsのご利益を検証してみました。

ドキュメントでFactored kernelsの例としてGaussian kernelsが引き合いに出されていましたが、Kernelモジュールは2次元配列をそのまま返してしまいますので、KernelFactorsモジュールを使って比較します。なお、どちらもImageFilteringパッケージに含まれています。

実験に使ったスクリプトはこちら


前準備

テスト画像とベンチマーク計測用の関数を用意しておきます。関数filteringfiltering!はそれぞれimfilterimfilter!用です。

using Images, TestImages, ImageFiltering

using BenchmarkTools

println("nthreads = $(Threads.nthreads())")
img = testimage("")

function filtering(img, kernel)
imfilter(img, kernel) # precompile
bm = @benchmark imfilter(img, kernel)
display("text/plain", bm)
println("")
end

function filtering!(out, img, kernel)
imfilter!(out, img, kernel) # precompile
bm = @benchmark imfilter!(out, img, kernel)
display("text/plain", bm)
println("")
end


パフォーマンスの比較

Gaussian blurを題材に、Kernel, KernelFactorsPython+OpenCVを比べてみます。


Kernelモジュールの場合

まず、2次元のGaussianカーネルを準備します。カーネルサイズは13x13です。

# Kernel module ver.

kernel = Kernel.gaussian((3, 3))

これを使ってimfilterimfilter!のベンチマークを取ってみます。なお、下記の結果はexport JULIA_NUM_THREADS=1とした状態です。

julia> filtering(img, kernel)

BenchmarkTools.Trial:
memory estimate: 4.14 MiB
allocs estimate: 309
--------------
minimum time: 12.670 ms (0.00% GC)
median time: 13.316 ms (0.00% GC)
mean time: 13.907 ms (4.15% GC)
maximum time: 72.896 ms (78.81% GC)
--------------
samples: 359
evals/sample: 1

julia> out = similar(img)
julia> filtering!(out, img, kernel)
BenchmarkTools.Trial:
memory estimate: 413.09 KiB
allocs estimate: 119
--------------
minimum time: 13.554 ms (0.00% GC)
median time: 14.015 ms (0.00% GC)
mean time: 14.413 ms (1.58% GC)
maximum time: 73.900 ms (79.41% GC)
--------------
samples: 347
evals/sample: 1

どちらも約14ms程度でした。これは遅い。


KernelFactorsモジュールの場合

前回の記事で紹介したFactored kernelの特性を利用した方法で計算させてみます。KernelモジュールをKernelFactorsに切り替えるだけで、あとは先程と同じです。

## KernelFactors module ver.

kernel = KernelFactors.gaussian((3, 3))

先ほどと同じようにベンチマークを取ってみます。なお、下記の結果もexport JULIA_NUM_THREADS=1とした状態です。

julia> filtering(img, kernel)

BenchmarkTools.Trial:
memory estimate: 4.13 MiB
allocs estimate: 292
--------------
minimum time: 9.564 ms (0.00% GC)
median time: 10.171 ms (0.00% GC)
mean time: 10.691 ms (5.07% GC)
maximum time: 73.211 ms (83.50% GC)
--------------
samples: 467
evals/sample: 1

julia> out = similar(img)
julia> filtering!(out, img, kernel)
BenchmarkTools.Trial:
memory estimate: 398.23 KiB
allocs estimate: 102
--------------
minimum time: 10.450 ms (0.00% GC)
median time: 10.801 ms (0.00% GC)
mean time: 11.042 ms (1.67% GC)
maximum time: 71.028 ms (82.64% GC)
--------------
samples: 453
evals/sample: 1

4ms程度速くなりましたが、まだまだ遅く感じます。


Python + OpenCVの場合

ここで、比較対象としてOpenCVがどの程度か確認してみます。

In [1]: import cv2

In [2]: img = cv2.imread("cameraman.png")
In [6]: %%timeit
...: cv2.GaussianBlur(img, (13, 13), 3)
...:
1.53 ms ± 215 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)


  • Python 3.7.0

  • opencv-python v3.4.2


ちょっとしたチューニング

上記の結果は少し期待はずれだったのですが、上記の実験ではどうもCPUを使い切れていないようでした。そこで、Juliaが使えるスレッド数を増やしてパフォーマンスの変換を観察してみます。

やり方は、


  • 今までの実験を一つのスクリプトとして保存しておきます


  • export JULIA_NUM_THREADS=n(n=1~4)とする

  • 先程のスクリプトを実行

この結果のサマリは次のようになりました。なお、imfilterimfilter!の速度はほぼ同じだったので、以降ではimfilter!の結果のみを示します。また、各スレッド数のベンチマークは@benchmarkのmean timeのみを示します。


Kernelモジュールとimfilter!

スレッドを増やすとパフォーマンスが落ちてしまいました。

nthreads
Benchmark

1
14.413

2
27.138

3
27.249

4
24.990


KernelFactorsモジュールとimfilter!

スレッド数2で頭打ちでした。

nthreads
Benchmark

1
11.042

2
5.211

3
5.812

4
5.394

以上をまとめたのが次のグラフです。スレッド数が1か2以上でパフォーマンスが大きく変わってしまうみたいですね。

imfilter!.png


あとがき


  • Kernelのバージョンでスレッド数を増やすと、なんでこんなにパフォーマンス落ちたんでしょう。

  • アルゴリズムの改良は大事ですね。

  • OpenCV速いですね。

実行環境

               _

_ _ _(_)_ | Documentation: https://docs.julialang.org
(_) | (_) (_) |
_ _ _| |_ __ _ | Type "?" for help, "]?" for Pkg help.
| | | | | | |/ _` | |
| | |_| | | | (_| | | Version 1.0.0 (2018-08-08)
_/ |\__'_|_|_|\__'_| | Official https://julialang.org/ release
|__/ |

julia> versioninfo()
Julia Version 1.0.0
Commit 5d4eaca0c9 (2018-08-08 20:58 UTC)
Platform Info:
OS: macOS (x86_64-apple-darwin14.5.0)
CPU: Intel(R) Core(TM) i5-6360U CPU @ 2.00GHz
WORD_SIZE: 64
LIBM: libopenlibm
LLVM: libLLVM-6.0.0 (ORCJIT, skylake)