2017年8月11日 星期五

自動(大量批次)尋找擷取、裁剪、模糊化、馬賽克照片中的人臉

需要從一大批相片當中偵測/擷取/裁剪出所有人臉嗎? 或是想把一大批相片當中的人臉通通打馬賽克或變模糊以保護隱私嗎? 這時你需要 facedetect。 對, 相機早就可以自動抓臉; 但是當命令列控發現一個不必寫程式就能用的命令列工具、 看到可以在 raspberry pi 上面寫 shell script 的可能性時, 這完全又是另一個等級的興奮感啊!

冰與火之歌原始海報1 原始海報1 的標記版 原始海報1 的模糊版 原始海報1 的馬賽克版
冰與火之歌原始海報2 原始海報2 的標記版 原始海報2 的模糊版 原始海報2 的馬賽克版

相較於其他機器學習/人工智慧程式, facedetect 的相依套件很簡單, 只需要 numpy 跟 opencv。 在 lubuntu 16.04 上, 因為 numpy 比較沒有版本的問題, 我偏好用 apt-get 安裝。 至於 opencv 則是人臉偵測的引擎, 所以最好裝較新的版本。 你可以 從原始碼編譯, 也可以偷懶 (以普通人,非 root 的身份) 用 pip3 安裝。 另外, facedetect 只是抓出人臉的座標; 所以你還需要圖片處理瑞士刀 ImageMagick 來真的完成裁剪/打馬賽克等等工作。 以下是偷懶版的完整安裝指令:

sudo apt-get install python3-numpy python3-pip imagemagick
pip3 install opencv-python
wget https://raw.githubusercontent.com/wavexx/facedetect/master/facedetect
chmod a+x facedetect
sudo mv facedetect /usr/bin

趕快找一張有人臉的照片來測試吧: ./facedetect people.jpg 它會印出一列或好幾列數字, 每列四個數字。 每一列代表一個矩形範圍, 意謂著抓到一張人臉。 四個數字依序是: 左側 x 座標、 上緣 y 座標、 矩形寬度、 矩形高度。

官網和 作者寫的介紹文 有一些簡單的 shell scripts 示範如何把 facedetect 的輸出餵給 ImageMagick 的 convert 或 mogrify 指令, 以達到本文最開始希望達到的效果。

注意! 有些相片存檔時採用偷懶的方式旋轉方向: 設定 (中繼資料 metadata 當中的) 一個 Orientation 標籤, 叫看圖軟體要另外自己旋轉 90 度之類的。 大部分的秀圖軟體會按照指示顯示給你看, 也就是說, 遇到這種圖片時, 表面上看起來很正常; 但在 jpg 圖檔裡面, 它存放的方向其實是旋轉 90 度之前的樣子。 我在另一個抓臉軟體裡卡關很久, 不理解很多獨照明明很清楚, 為何卻都抓不到臉。 直到改用 facedetect 看到它很厲害, 有抓到臉, 但標錯位置, 才猜到這個原因。 因為它 (底下所用的函式庫?) 並沒有理會這個標籤, 導致有時你用肉眼看相片, 明明是正常的方向, 但抓臉軟體所看到的, 其實是側躺著的人臉 (沒有用手撐頭的那種)。 你可以用 exiftool people.jpg | grep -i orient 查看。 如果沒有印任何東西, 或印出 "Horizontal (normal)" 之類的, 就表示相片沒這個問題; 若看到 "Rotate 90 CW", 有可能是手機拿直的照人像等等因素造成的, facedetect 很可能會失敗。 這時可以用 convert -auto-orient people.jpg corrected.jpg 把它轉正。 關於各主題更詳盡的說明, 請見 相片的中繼資料 metadata exiftool 的用法 方向的問題 等等連結。

獨照的成功率很高; 合照很容易會漏掉一兩張臉。 側面照比較容易被 miss 掉; 但也不是完全抓不到。 反過來說, 它偶爾也會在你異想不到的地方 -- 例如花草樹木當中 -- 找到「臉」。 少數的 寶可夢 的臉會被認出來; 多數不會, 這樣不知道算是...? 另外, 圖片大小對速度有很大的影響。 把圖片變小、 偵測完之後再把座標乘以倍數套到原圖片上, 效果相同, 速度較快。 不過遇到高解析度的大合照圖時, 則應該把圖片先裁成很多小張, 再逐張用 facedetect 去偵測, 因為 facedetect 好像很難偵測出十幾張臉以上。 預先裁切時要有一些重疊, 以免有人在每一小張都被切兩半, facedetect 認不出來。 [ 柯P上廁所大合照出處]

樹木風景照 樹木風景照的臉 少數被認出來的寶可夢
柯P 上廁所大合照 柯P 上廁所大合照, 標記版 柯P 上廁所大合照, 拆解再標記版

因為 ImageMagick 的指令太長, 每次打起來都很費腦筋, 所以就寫了一支小程式 rect2im.perl。 請抓回去、 改成可執行、 放到 /usr/bin。 把 facedetect 輸出的矩形座標餵給它吃, 並且告訴它你想做的動作 (mark 標記、 crop 裁切、 blur 模糊化、 pixelate 打馬賽克) 它就會幫你產生 ImageMagick 的指令。 不指定動作時, 預設是 mark (標記)。 為了讓它能夠批次處理大量圖片, 必須先把 facedetect 的輸出擠成一列, 前面冠上檔名, 像這樣:

echo -n kp.jpg ; facedetect kp.jpg | tr '\n' ' ' ; echo ''

在外面包一層 for 迴圈, 就可以一次處理一張或多張圖:

for f in *.jpg ; do echo -n "$f " ; facedetect $f | tr '\n' ' ' ; echo '' ; done

如果每次都需要這樣寫, 未免太囉嗦。 所以請把以下這句貼到你的 ~/.bashrc 的最後面:

function fa-de () { for f in $@ ; do echo -n "$f " ; facedetect $f | tr '\n' ' ' ; echo '' ; done }

開一個新的終端機分頁測試看看: fa-de 123.jpg 456.jpg 如果成功的話, 就可以把這結果餵給 rect2im.perl:

fa-de 123.jpg 456.jpg | rect2im.perl

rect2im.perl 只印指令, 並不執行任何動作。 它所印的指令, 預設會將圖片輸出放在 /var/run/user/數字/ 底下 (「數字」 是你的 uid), 這個目錄是在 ramdisk 裡, 裡面的檔案關機時就會跟著消失! 可以用 -d 指定別的目錄, 像這樣: rect2im.perl -d ~/marked

試著把它輸出的很長的 convert ... 指令剪貼一兩句到命令列上執行看看, 並且打開輸出的圖檔檢視。 如果沒問題, 就在上句最後面再加上 | bash, 視你圖片的張數, 等幾分鐘後就可以收割囉!

想要打馬賽克或模糊化時, 必須注意兩件事: (1) 可能會有漏網之魚需要手工處理! (2) 可能需要稍微擴大範圍, 保護效果較好。 你可以用 -x 跟 -y 指定橫向跟縱向的放大倍率 (預設都是 1.0 倍)。 另外, -s 參數可以調整馬賽克及模糊化的程度。 blur 的 -s 跟 pixelate 的 -s 意義不一樣, 但總之兩者都是 -s 值越大, 影像就越模糊。 像這樣: rect2im.perl -o blur -x 1.8 -y 1.8 -s 20 或這樣: rect2im.perl -o pixelate -x 1.8 -y 1.8 -s 30

星艦迷航記 Spock、 Kirk、 McCoy, 原始照片1 原始照片1 的標記版 原始照片1 的模糊版 原始照片1 的馬賽克版
星艦迷航記 Spock、 Kirk、 McCoy, 原始照片2 原始照片2 的標記版 原始照片2 的模糊版 原始照片2 的馬賽克版

裁剪臉部圖片時 (-o crop) 如果一張圖裡有好幾張臉, 當然就要輸出好幾個檔。 輸出的檔名會在 .jpg 或 .png 之前補上 -01 -02 等等序號。 此外, 裁剪圖片時可能會需要所有的圖片有著相同的長寬比例, 以便後續整齊排列。 這時可以用 -y 指定垂直放大的倍數、 用 -a 指定 aspect ratio, 從擷取圖片的高度計算寬度、 忽略 facedetect 算出來的寬度, 像這樣: rect2im.perl -o crop -y 2.4 -a 0.8 。 因為人臉圖片通常高大於寬, 所以 -a 的值 (「寬度是高度的多少倍」) 通常小於 1, 值越小越細長。 注意: 不能同時指定 -x 跟 -a。 可以先省略 -o crop, 也就是先標示不裁剪, 等調好 -x 跟 -y 或 -a 跟 -y 之後, 再加上 -o crop 。 出來的圖片大小不同沒關係, 可以再用 convert -resize 400x500 a.jpg b.jpg 改成統一大小。 也可以只指定寬度 -resize 400 或只指定高度 -resize x500

因為 facedetect 只截取不辨識, 所以執行速度也比一般人臉辨識的程式 (例如 撞臉偵測器所用到的 face_recognition) 快很多。 不過如果你的圖片檔很多, 而且想花很多時間調整 rect2im.perl 的參數, 那也可以先用 for ... ; done > fd-output.txt 把 facedetect 的結果存檔, 以後實驗時就不必每次重跑 facedetect, 只需要 rect2im.perl ...(各種參數設定)... fd-output.txt

facedetect 只印出座標; 它並沒有企圖要變成一個全能軟體。 這是典型的、 聰明的 unix/linux 思維: 把複雜的功能丟給專司圖片加工的 ImageMagic, 等於馬上幫它加上 ImageMagic 所有的神奇功能。 我們再次見識到 unix 哲學 / 組合的力量 / 知識相乘的效果。 我個人認為: 這種 problem decomposition 的解題方式, 是運算思維當中最重要的一個元素。 而它的前提, 就是每隻程式不藏私、 不自以為是, 要吞吐公開格式的檔案, 跟其他程式分工合作。 從這篇文章, 讀者也可以看出來為什麼在我所推薦的 「實用到二十二世紀」 的程式語言 當中, shell 指令排第二名。 這也是為什麼 linux 明明早就有 GUI 了, 可是我還是堅持教命令列。 在命令列底下生活, 每使用一次 pipe 或 command substitution, 你的 problem decomposition 經驗值就又增加了一些。 你還玩過 ImageMagick 什麼有趣的功能呢? 請分享你的好點子, 讓我把它加到 rect2im.perl 裡面去吧!

沒有留言:

張貼留言