Halideによる画像処理について

 Halideによる画像処理について

4bffdca1c8d7fcc1fe5cc2f576581eb4?s=128

saturday06

March 27, 2017
Tweet

Transcript

  1. Halideによる 画像処理について 2017/03/14 ピクシブ株式会社 茂木 勇

  2. 目次 - Halide概要 - Halideでぼかし処理 - Halideで画像縮小処理 - ベンチマーク

  3. Halideとは? - C++の画像処理ライブラリ - 「ハロゲン化合物」という意味 - ライブラリではあるが「ハイパフォーマンスな画像処理を行うことができる新しい言 語」と名乗っている - 開発者「1500行のマルチスレッドとアセンブラで最適化されたPhotoshopのとある

    フィルタのC++のコードを3ヶ月で10倍高速化した」 - http://halide-lang.org/assets/lectures/Halide_CVPR_intro.pdf
  4. None
  5. None
  6. Halideの特徴 - C++の文法で数式を記述し、Halideが内部に持っているLLVMでコンパイルするこ とで画像処理用の関数を生成する - 数式自体をコンパイル、最適化をするので「縮小して輝度を上げる」など複数の画 像処理を組み合わせた際にパフォーマンスが向上する可能性がある - SSE,AVX,NEON等CPUのSIMD命令及びCUDA,OpenCLなどGPUを使った最適 化に対応している。

    - CPU用に書いた処理をほとんど変更せず GPUでも動かせる
  7. 例: 近傍4ピクセルを使ったぼかし処理 blur(x, y) = (src(x-1,y) + src(x,y-1) + src(x,y)

    + src(x+1,y) + src(x,y+1))/5
  8. 例: 近傍4ピクセルを使ったぼかし処理(Halide版) Func src = …; Func blur; Var x,

    y; blur(x, y) = (src(x-1,y)+src(x,y-1)+src(x,y)+src(x+1, y)+src(x,y+1))/5;
  9. 例: 近傍4ピクセルを使ったぼかし処理(Halide版) Func src = BoundaryConditions::repeat_edge(...); Func blur; Var x,

    y; blur(x, y) = (src(x-1,y)+src(x,y-1)+src(x,y)+src(x+1, y)+src(x,y+1))/5; 画像端のデータを繰り返す
  10. 例: 近傍4ピクセルを使ったぼかし処理(Halide版) ImageParam input(type_of<uint8_t>(), 2); Func src = BoundaryConditions::repeat_edge(input); Func

    blur; Var x, y; blur(x, y) = (src(x-1,y)+src(x,y-1)+src(x,y)+src(x+1, y)+src(x,y+1))/5; 入力画像データの定義
  11. 例: 近傍4ピクセルを使ったぼかし処理(Halide版) ImageParam input(type_of<uint8_t>(), 2); Func src = BoundaryConditions::repeat_edge(input); Func

    blur; Var x, y; blur(x, y) = (src(x-1,y)+src(x,y-1)+src(x,y)+src(x+1, y)+src(x,y+1))/5; blur.compile_to_static_library("blur", {input}); これらを内部のLLVMで コンパイルして、 Cのライブラリとして出力
  12. 例: 近傍4ピクセルを使ったぼかし処理(Halide版) #include <Halide.h> int main() { using namespace Halide;

    ImageParam input(type_of<uint8_t>(), 2); Func src = BoundaryConditions::repeat_edge(input); Func blur; Var x, y; blur(x, y) = (src(x-1,y)+src(x,y-1)+src(x,y)+src(x+1, y)+src(x,y+1))/5; blur.compile_to_static_library("blur", {input}); return 0; }
  13. #include <Halide.h> int main() { using namespace Halide; ImageParam input(type_of<uint8_t>(),

    2); Func src = BoundaryConditions::repeat_edge(input); Func blur; Var x, y; blur(x, y) = (src(x-1,y)+src(x,y-1)+src(x,y)+src(x+1, y)+src(x,y+1))/5; blur.compile_to_static_library("blur", {input}); return 0; } 例: 近傍4ピクセルを使ったぼかし処理(Halide版)
  14. #include <Halide.h> int main() { using namespace Halide; ImageParam input(type_of<uint8_t>(),

    2); Func src = BoundaryConditions::repeat_edge(input); Func blur; Var x, y; blur(x, y) = (src(x-1,y)+src(x,y-1)+src(x,y)+src(x+1, y)+src(x,y+1))/5; blur.compile_to_static_library("blur", {input}); return 0; } 例: 近傍4ピクセルを使ったぼかし処理(Halide版)
  15. 例: 近傍4ピクセルを使ったぼかし処理(Halide版) ImageParam input(type_of<uint8_t>(), 2); Func src = BoundaryConditions::repeat_edge(input); Func

    blur; Var x, y; blur(x, y) = (src(x-1,y)+src(x,y-1)+src(x,y)+src(x+1, y)+src(x,y+1))/5;
  16. 例: 近傍4ピクセルを使ったぼかし処理(Halide版) ImageParam input(type_of<uint8_t>(), 2); Func src = BoundaryConditions::repeat_edge(input); Func

    blur; Var x, y; blur(x, y) = (src(x-1,y)+src(x,y-1)+src(x,y)+src(x+1, y)+src(x,y+1))/5; 画素値は8bit整数のため 足し算結果がオーバーフローしている
  17. 例: 近傍4ピクセルを使ったぼかし処理(Halide版) ImageParam input(type_of<uint8_t>(), 2); Func src = BoundaryConditions::repeat_edge(input); Func

    blur; Var x, y; blur(x, y) = src(x-1,y)/5+src(x,y-1)/5+src(x,y)/5+src(x+1, y)/5+src(x,y+1)/5; オーバーフローしにくいように修正 (型変換とかもできる)
  18. #include <Halide.h> int main() { using namespace Halide; ImageParam input(type_of<uint8_t>(),

    2); Func src = BoundaryConditions::repeat_edge(input); Func blur; Var x, y; blur(x, y) = src(x-1,y)/5+src(x,y-1)/5+src(x,y)/5+src(x+1, y)/5+src(x,y+1)/5; blur.compile_to_static_library("blur", {input}); return 0; } 例: 近傍4ピクセルを使ったぼかし処理(Halide版)
  19. blur(x, y) = src(x-1,y)/5+src(x,y-1)/5+src(x,y)/5+src(x+1, y)/5+src(x,y+1)/5; blur.gpu_tile(x, y, 16, 16); auto

    target = Halide::get_host_target(); target.set_feature(Target::OpenCL); blur.compile_to_static_library("blur", {input}, "", target); return 0; } 例: 近傍4ピクセルを使ったぼかし処理(Halide版) GPU使う場合
  20. 画像縮小処理 - pixivではサムネイル作成用の画像縮小にffmpeg(libswscale) を用いているが、幾つかの問題点があり乗り換え先を検討し ている - 高速で、高品質な縮小処理が求められている - halideが代わりとして使えないか?

  21. 例: Nearest Neighber法による画像縮小処理 縮小

  22. 例: Nearest Neighber法による画像縮小処理 out(out_x, out_y) = in(nearest_in_x(out_x),nearest_in_y(out_y)) in画像 out画像

  23. 例: Nearest Neighber法による画像縮小処理(Halide) out(out_x, out_y) = in(nearest_in_x(out_x),nearest_in_y(out_y))

  24. 例: Nearest Neighber法による画像縮小処理(Halide) Func out; Var out_x, out_y; out(out_x, out_y)

    = in(nearest_in_x(out_x),nearest_in_y(out_y)) 出力変数、関数定義
  25. 例: Nearest Neighber法による画像縮小処理(Halide) ImageParam in(type_of<uint8_t>(), 2); Param<uint32_t> out_width, out_height; Func

    out; Var out_x, out_y; out(out_x, out_y) = in(nearest_in_x(out_x),nearest_in_y(out_y)) 入力画像、出力サイズ 定義
  26. 例: Nearest Neighber法による画像縮小処理(Halide) ImageParam in(type_of<uint8_t>(), 2); Param<uint32_t> out_width, out_height; Func

    nearest_in_x; Var x; nearest_in_x(x) = x * in.width() / out_width; Func out; Var out_x, out_y; out(out_x, out_y) = in(nearest_in_x(out_x),nearest_in_y(out_y)) 「最近傍のx座標を取得」関数定義
  27. 例: Nearest Neighber法による画像縮小処理(Halide) ImageParam in(type_of<uint8_t>(), 2); Param<uint32_t> out_width, out_height; Func

    nearest_in_y; Var y; nearest_in_y(y) = y * in.height() / out_height; Func nearest_in_x; Var x; nearest_in_x(x) = x * in.width() / out_width; Func out; Var out_x, out_y; out(out_x, out_y) = in(nearest_in_x(out_x),nearest_in_y(out_y)) 「最近傍のy座標を取得」関数定義
  28. 例: Nearest Neighber法による画像縮小処理(Halide) #include <Halide.h> int main() { using namespace

    Halide; ImageParam in(type_of<uint8_t>(), 2); Param<uint32_t> out_width, out_height; Func nearest_in_x; Var x; nearest_in_x(x) = (x * in.width() + out_width / 2) / out_width; Func nearest_in_y; Var y; nearest_in_y(y) = (y * in.height() + out_height / 2) / out_height; Func out; Var out_x, out_y; out(out_x, out_y) = BoundaryConditions::repeat_edge(in)(nearest_in_x(out_x), nearest_in_y(out_y)); out.compile_to_static_library("nearest_neighber", {in, out_width, out_height}); return 0; } 現実の問題に対するワークアラウンドとか追加
  29. 例: Nearest Neighber法による画像縮小処理(Halide) #include <Halide.h> int main() { using namespace

    Halide; ImageParam in(type_of<uint8_t>(), 2); Param<uint32_t> out_width, out_height; Func nearest_in_x; Var x; nearest_in_x(x) = (x * in.width() + out_width / 2) / out_width; Func nearest_in_y; Var y; nearest_in_y(y) = (y * in.height() + out_height / 2) / out_height; Func out; Var out_x, out_y; out(out_x, out_y) = BoundaryConditions::repeat_edge(in)(nearest_in_x(out_x), nearest_in_y(out_y)); out.compile_to_static_library("nearest_neighber", {in, out_width, out_height}); return 0; } in画像
  30. 例: Nearest Neighber法による画像縮小処理(Halide) #include <Halide.h> int main() { using namespace

    Halide; ImageParam in(type_of<uint8_t>(), 2); Param<uint32_t> out_width, out_height; Func nearest_in_x; Var x; nearest_in_x(x) = (x * in.width() + out_width / 2) / out_width; Func nearest_in_y; Var y; nearest_in_y(y) = (y * in.height() + out_height / 2) / out_height; Func out; Var out_x, out_y; out(out_x, out_y) = BoundaryConditions::repeat_edge(in)(nearest_in_x(out_x), nearest_in_y(out_y)); out.compile_to_static_library("nearest_neighber", {in, out_width, out_height}); return 0; } in画像 out画像
  31. ベンチマーク 内容: サイズ1600x3200のグレースケール画像を320x480に様々な方式で縮小 環境: MacBook Pro (Retina, 15-inch, Mid 2015)

    Processor Name: Intel Core i7 Processor Speed: 2.2 GHz Total Number of Cores: 4 Memory: 16 GB GPU: Intel Iris Pro 1536 MB
  32. ベンチマーク結果(小さい値だとより良い) Benchmark Time CPU ------------------------------------------------------- HalideResizeNearestNeighber 2083577 ns 2081819 ns

    HalideResizeBilinear 10228914 ns 10225705 ns HalideResizeGpuBilinear 947467 ns 736395 ns FfmpegResizeFastBilinear 1418483 ns 1416548 ns FfmpegResizeBilinear 6036412 ns 6033964 ns FfmpegResizeLanczos3 18074324 ns 18070590 ns
  33. Halideの特徴(再掲) - C++の文法で数式を記述し、Halideが内部に持っているLLVMでコンパイルするこ とで画像処理用の関数を生成する - 数式自体をコンパイル、最適化をするので「縮小して輝度を上げる」など複数の画 像処理を組み合わせた際にパフォーマンスが向上する可能性がある - x86,arm等CPUのSIMD命令及びCUDA、OpenCLを使った最適化に対応してい る。

    - CPU用に書いた処理をほとんど変更せず GPUでも動かせる
  34. ベンチマーク2 内容: サイズ1600x3200のグレースケール画像をネガポジ変換をして から320x480にバイリニア法で縮小

  35. ベンチマーク結果2(小さい値だとより良い) Benchmark Time CPU ----------------------------------------------------------- HalideResizeBilinear 10228914 ns 10225705 ns

    HalideNegateResizeBilinear 10417060 ns 10413506 ns FfmpegResizeBilinear 6036412 ns 6033964 ns OpenCVNegateFfmpegResizeBilinear 9038528 ns 9036810 ns
  36. ベンチマーク結果まとめ - FFmpegのような職人芸コードに比べて速度60%くらいのコードが生成される - GPUにオフロードすることで、CPUでは実現不可能な速度で処理ができる - GPUの職人芸にどのくらい及んでいるのかはまだ未検証 - 数式DSLを内部のLLVMでコンパイルするため、複数の画像処理を組み合わせた際 には職人芸コードの組み合わせよりも高速なコードが生成される可能性がある