tdnki @ ウィキ
OptimizationNeon
最終更新:
tdnki
-
view
6. 画像処理の高速化(NEON編)
NEON(ARMのCPU拡張命令)を使用して、さらなる高速化を図る。
6-1. 準備
NEONを有効にする。
Android.mk
LOCAL_ARM_NEON := true
これだけだと、NEONはv7aでしかサポートしていない、とビルドエラーが出るので
v7a向けのビルドを明示する。
v7a向けのビルドを明示する。
Application.mk
APP_ABI := armeabi-v7a
Android.mk
ifeq ($(TARGET_ARCH_ABI), armeabi-v7a) LOCAL_ARM_NEON := true LOCAL_CFLAGS += -DENABLE_NEON endif
もし APP_ABI をデフォルトの armeabi に戻してもビルドエラーとならないよう、TARGET_ARCH_ABI を見るようにした。
LOCAL_CFLAGS に渡したのは、今後、ビルド対象によってコード側も ifdef で切り分けるための define なので、
わかりやすい名前なら何でも良い。
LOCAL_CFLAGS に渡したのは、今後、ビルド対象によってコード側も ifdef で切り分けるための define なので、
わかりやすい名前なら何でも良い。
ちなみに、これだけでかなり最適化がかかり、速くなってしまった。
40ms → 30ms
40ms → 30ms
6-2. 方針
NEONでは64bitか128bitのベクタを使用できるので、unsigned charのピクセルデータは16個を並列処理できる。
ただし、今回はSobelフィルタの途中計算で16bit精度が必要となるので、8個ずつループを回して処理していく。
ただし、今回はSobelフィルタの途中計算で16bit精度が必要となるので、8個ずつループを回して処理していく。
画像幅が8で割り切れない場合は端数処理が必要なので、水平方向のループカウンタjを外に出した。
ComicFinder.cpp
for (int i = 0; i < height; i++) { int j = 0; #ifdef ENABLE_NEON for ( ; j < width-7; j+=8) { // NEONコード } #endif for ( ; j < width; j++) { // これまでのコード } }
本来はアセンブラを直で書かなければならないが、arm_neon.hで定義されているintrinsicsを使えば、
比較的高い可読性を持ちながら、NEON命令にコンパイルされるコードを書くことができる。
使用できる関数はARM Complier Referenceを参照のこと。
比較的高い可読性を持ちながら、NEON命令にコンパイルされるコードを書くことができる。
使用できる関数はARM Complier Referenceを参照のこと。
ComicFinder.cpp
#ifdef ENABLE_NEON #include <arm_neon.h> #endif
6-3. NEONコード
ラインの冒頭でスクリーントーンのパターンを定義する。座標の偶奇によって白黒を反転させる。
uint8x8_t v_tone = vcreate_u8((i % 2) == 0 ? 0xff00ff00ff00ff00 : 0x00ff00ff00ff00ff);
三値化の置き換え。
ベクタの要素単位にif文を書くような事はできないので、vcge等の比較結果のビットマスクを返す関数を使用する。
黒、灰、白それぞれについて、各々の値を表すベクタとビットマスクの論理積を取れば、
これらの論理和が三値化後のベクタとなる。
ベクタの要素単位にif文を書くような事はできないので、vcge等の比較結果のビットマスクを返す関数を使用する。
黒、灰、白それぞれについて、各々の値を表すベクタとビットマスクの論理積を取れば、
これらの論理和が三値化後のベクタとなる。
uint8x8_t v_src = vld1_u8(p_src+i*width+j); uint8x8_t v_mask_white = vcge_u8(v_src, vdup_n_u8(threshold_gray_and_white)); uint8x8_t v_mask_gray = veor_u8(v_mask_white, vcge_u8(v_src, vdup_n_u8(threshold_black_and_gray))); uint8x8_t v_lum = vorr_u8(v_mask_white, vand_u8(v_tone, v_mask_gray));
比較が真なら全てのビットが1、偽なら全てのビットが0となるが、
これはそのまま白ピクセルおよび黒ピクセルと解釈できるので、不要な論理演算は排除した。
(例えば最後の行の v_mask_white は、正確には vand_u8(vdup_n_u8(0xff), v_mask_white) とするべきだが、
白ピクセルの値が0xffであることから、この演算の結果は v_mask_white に一致する。
同様の理由により、黒ピクセルに関してはマスクを作る必要すらない。)
これはそのまま白ピクセルおよび黒ピクセルと解釈できるので、不要な論理演算は排除した。
(例えば最後の行の v_mask_white は、正確には vand_u8(vdup_n_u8(0xff), v_mask_white) とするべきだが、
白ピクセルの値が0xffであることから、この演算の結果は v_mask_white に一致する。
同様の理由により、黒ピクセルに関してはマスクを作る必要すらない。)
Sobelフィルタの置き換え。
近傍8ピクセルをラインバッファからロードする。
この後で符号付き演算のオペランドに使うのでsignedに変換するが、wrap aroundが起きないよう16bitへの拡張も済ませておく。
近傍8ピクセルをラインバッファからロードする。
この後で符号付き演算のオペランドに使うのでsignedに変換するが、wrap aroundが起きないよう16bitへの拡張も済ませておく。
int16x8_t v_src00 = vreinterpretq_s16_u16(vmovl_u8(vld1_u8(line0+j+0))); int16x8_t v_src01 = vreinterpretq_s16_u16(vmovl_u8(vld1_u8(line0+j+1))); int16x8_t v_src02 = vreinterpretq_s16_u16(vmovl_u8(vld1_u8(line0+j+2))); int16x8_t v_src10 = vreinterpretq_s16_u16(vmovl_u8(vld1_u8(line1+j+0))); int16x8_t v_src12 = vreinterpretq_s16_u16(vmovl_u8(vld1_u8(line1+j+2))); int16x8_t v_src20 = vreinterpretq_s16_u16(vmovl_u8(vld1_u8(line2+j+0))); int16x8_t v_src21 = vreinterpretq_s16_u16(vmovl_u8(vld1_u8(line2+j+1))); int16x8_t v_src22 = vreinterpretq_s16_u16(vmovl_u8(vld1_u8(line2+j+2)));
エッジ判定。
int16x8_t v_fx = vdupq_n_s16(0); v_fx = vsubq_s16(v_fx, v_src00); v_fx = vsubq_s16(v_fx, vshlq_n_s16(v_src10, 1)); v_fx = vsubq_s16(v_fx, v_src20); v_fx = vaddq_s16(v_fx, v_src02); v_fx = vaddq_s16(v_fx, vshlq_n_s16(v_src12, 1)); v_fx = vaddq_s16(v_fx, v_src22); int16x8_t v_fy = vdupq_n_s16(0); v_fy = vsubq_s16(v_fy, v_src00); v_fy = vsubq_s16(v_fy, vshlq_n_s16(v_src01, 1)); v_fy = vsubq_s16(v_fy, v_src02); v_fy = vaddq_s16(v_fy, v_src20); v_fy = vaddq_s16(v_fy, vshlq_n_s16(v_src21, 1)); v_fy = vaddq_s16(v_fy, v_src22); uint8x8_t v_edge = vclt_u8(vqmovun_s16(vaddq_s16(vabsq_s16(v_fx), vabsq_s16(v_fy))), vdup_n_u8(threshold_edge));
三値化の結果とマージ。
v_lum = vand_u8(v_lum, v_edge);
RGBAの順にインターリーブする形でストア。
uint8x8x4_t v_dst; v_dst.val[0] = v_lum; v_dst.val[1] = v_lum; v_dst.val[2] = v_lum; v_dst.val[3] = vdup_n_u8(0xff); vst4_u8(p_dst+(i*width+j)*4, v_dst);
NEON化により、画像処理時間は 30ms → 10ms となった。
ここまでのソース
ComicFinder.zip
添付ファイル