contents
Problem
背景
影像處理中,給定一張圖,準確地找到點、線、邊、角都是相當困難的,由於圖片會受到干擾、顏色屬性的差異,使得擷取特徵相當困難。
問題描述
對於 $N \times M$ 的像素圖片,方便起見只由黑白影像構成,0 表示暗、1 表示亮,對於每一個像素位置判斷是否可能是角點。
在角點偵測的算法中,有一個由 Rosten and Drummond 提出的 FAST (Features from Accelerated Segment Test) 方法。概念由一個 $7 \times 7$ 的遮罩,待測點 $p$ 位於遮罩中心,由遮罩內圈上的 16 個像素的灰階判斷 $p$ 是否為角點。遮罩樣子如下所示:
|
|
只要這個圈上出現連續大於等於 12 個相同的暗像素或者是亮像素,則 $p$ 就被視為一個角點。
不幸地,這會造成在一個角上出現很多角點,通常會根據掃描的順序找到角點,當找到一個角點後,會抑制鄰近區域不可以是角點。此題不考慮抑制情況,對於每一個角點必須在 16 個像素在圖片上才進行判斷,圖片邊界不進行偵測。
輸出一個 $N \times M$ 的矩陣,按照原圖片位置,若該點是角點則為 1,反之為 0。
Sample Input
|
|
Sample Output
|
|
Solution
這一題的技巧不是演算法,而是實作的加速細節。
同時,來實驗老師上課說的優化策略的用處,由於要連續 12 個,根據鴿籠原理的方式,挑選位置 1, 5, 9, 13 出來,若連續三個狀態相同再進行 $O(16)$ 的判斷。然而挑出這四個位置,可以加速 50%,相較於只有 $O(16)$ 的判斷效能,可以參考下方的樸素解。
樸素寫法並不是最快的,因為 branch 太多,導致速度至少為 800ms,去處理一張 $1920 \times 1080$ 的影像,更好的方案是使用 bitmask,預處理在 16bits 下,連續 9 個相同狀態的位元情況,搭配 loop unrolling 的方式去撰寫,直接 $O(16)$ 判斷,為了減少代碼量,採用巨集的前處理展開。請參考 bitmask 版本。速度來到 140ms,加速幾乎 8 倍。
單純的 bitmask 還不是最快,直接建表 $O(2^{16} \times 16)$ 得到 16bits 是否是角點,建表消耗時間,但單一判斷變成 $O(1)$。請參考 bitmask2 版本。速度來到 76ms,直接翻了快兩倍。
最終 bitmask3 版本 56ms,採用以下的方案:
- 減少型別轉換 movz 的出現,用補數來抽換判斷。
意即(a&mask) == 0 || (a&mask) == mask)
將只會有(a&mask) == mask
,需要(a&mask) == 0
的判斷,則先a = ~a
再進行(a&mask) == mask
。 - 利用編譯的常數展開,減少二維陣列取址時的一次乘法。
意即g[x][y]
取址使用時,會動用到一次乘法和一次加法,對於每一個角點偵測,動用到 16 次的乘法運算。若矩陣大小事先已知,那麼對於某一行的角點,g[x]
可以用一次乘法計算,接著該行所有角點偵測,只會剩下 16 次的加法。 - 建表太慢,用
__builtin_popcount()
提供剪枝。
當然,以上變態至極的作法,倒不如樸素解直接開 g++ -O3
或者是 g++ -Ofast
來的省事,速度慢一點也是沒問題的對吧。
|
|
樸素解
|
|
bitmask
|
|
bitmask2
|
|
bitmask3
|
|