2017年9月1日 星期五

從相片的 exif 資料撈描述字串、 用 imagemagick 在相片上寫字 (批次處理相片檔的命令列入門練習)

可愛狗狗照片集錦 需要批次大量修圖時, ImageMagick 超好用。 命令列麻瓜/新手/小學生請跟著我做, 勇敢踏出滑鼠選單便利但局促的保護傘, 感受一下 「用電腦」 跟 「被電腦用」 的差別。

我們從 這六張可愛狗狗圖片 出發。 現實生活中, 當你面對 60 張或 600 張圖片時, 今天學的這幾招會更有感覺; 不過在教室裡面, 老師們總是習慣拿小小數量的資料來解說 :-) 我們的目標是要把這六張圖片都變成大小相同的圖片, 並從 exif 後設資料 (meta data) 當中撈字串出來, 把它寫到相片上。請先用 geeqie 檢視相片。 等一下每次產生新圖片時, geeqie 會自動更新、 可以點新圖片來看, 不必重新執行。

一、 基本練習

  1. 安裝套件: apt-get install imagemagick 這裡面包含本文要用到的 identify、 convert、 montage 三個指令。
  2. 先學一個很簡單的指令, 就是在螢幕上列印而已: echo hello
  3. 再學迴圈跟變數: for x in earth mars venus ; do echo "hello people from $x !" ; done 注意: 單引號跟雙引號大部分時候效果一樣; 但這裡就不一樣。 這裡必須用雙引號, 裡面的變數才會代換。
  4. identify 指令可以查看圖片解析度: identify 3.jpg
  5. convert 指令的 -crop 功能可以剪裁圖片: convert -crop 960x800+240+0 3.jpg test.jpg 位置及大小的寫法是 「寬度x高度+左緣+上緣」, 原點在左上角。
  6. convert 指令的 -resize 功能可以改變圖片解析度: convert -resize 480 3.jpg test.jpgconvert -resize x400 3.jpg test.jpg 。 第一句指定寬度, 它自動算高度; 第二句指定高度, 它自動算寬度。 我都不會同時指定寬度與高度; 讓 ImageMagick 根據原圖的寬高比例 (aspect ratio) 來自動幫我計算另一個值, 比較簡單。
  7. 可以把好幾個動作前後串起來, 例如先裁剪、 再縮小一半: convert -crop 960x800+240+0 -resize 480 3.jpg test.jpg

二、 統一規格

先把相片變成同樣大小。 因為 ImageMagick 有些特效的範圍是用 pixel 數指定的, 如果圖片大小不一的話, 在大圖上, 特效可能很不明顯, 而在小圖上, 效果可能太誇張。

但是統一規格不能直接 resize -- 如剛剛所說, 有些圖片可能會變太胖, 有些變太瘦。 我們先印出所有圖片的解析度: identify ?.jpg 出現類似這樣:

1.jpg JPEG 448x298 448x298+0+0 8-bit sRGB 101KB 0.010u 0:00.000
2.jpg[1] JPEG 500x375 500x375+0+0 8-bit sRGB 33.6KB 0.000u 0:00.000
3.jpg[2] JPEG 1920x1200 1920x1200+0+0 8-bit sRGB 436KB 0.000u 0:00.000
4.jpg[3] JPEG 400x267 400x267+0+0 8-bit sRGB 14.9KB 0.000u 0:00.010
5.jpg[4] JPEG 1440x900 1440x900+0+0 8-bit sRGB 111KB 0.000u 0:00.000
6.jpg[5] JPEG 736x552 736x552+0+0 8-bit sRGB 82.3KB 0.010u 0:00.009

regexp 把圖片名稱、 圖片寬度、 圖片高度印出來: identify ?.jpg | perl -ne 'print "$1 $2 $3\n" if /^(.*\.jpg).*? (\d+)x(\d+)/' 再改印檔名跟 aspect ratio: identify ?.jpg | perl -ne 'printf "$1 %0.2f\n",$2/$3 if /^(.*\.jpg).*? (\d+)x(\d+)/' 最後按照 aspect ratio 大小排序: identify ?.jpg | perl -ne 'printf "$1 %0.2f\n",$2/$3 if /^(.*\.jpg).*? (\d+)x(\d+)/' | sort -n -k 2 得到:

2.jpg 0.99
6.jpg 1.33
1.jpg 1.50
4.jpg 1.50
3.jpg 1.60
5.jpg 1.60

好, 現在知道 2.jpg 最窄高; 3.jpg 跟 5.jpg 最矮胖。 目標是把所有圖片的 aspect ratio 調成 4:3 。 調整 aspect ratio 很難批次處理, 最多只能分成幾大類各類處理。

第一步先把 2.jpg 的上下各砍掉一點: convert -crop 500x375+0+64 2.jpg test.jpg 如果從 geeqie 裡面看起來 ok, 再 mv test.jpg 2.jpg 。 這件事當然也可以用 gimp 做; 不過我覺得下指令還是比較快一點。

第二步把所有圖片的高度都變成 600: mkdir ht ; for f in ?.jpg ; do convert -resize x600 $f ht/$f ; done

再來, 1.jpg 3.jpg 4.jpg 5.jpg 分別要保留中間、 左邊、 左邊、 中間偏右; 另兩張直接拷貝:

cd ht
mkdir ../crop
convert -crop 800x600+50+0 1.jpg ../crop/1.jpg
convert -crop 800x600+0+0 3.jpg ../crop/3.jpg
convert -crop 800x600+0+0 4.jpg ../crop/4.jpg
convert -crop 800x600+120+0 5.jpg ../crop/5.jpg
cp 2.jpg 6.jpg ../crop

三、 貼字

先手動測試在圖片上貼字: convert -font AR-PL-UKai-TW -pointsize 24 -stroke '#0008' -undercolor '#ffc8' -gravity South -annotate +0+5 '馬麻什麼時候回來啊?' 1.jpg test.jpg 這麼長的指令當然不是我自己一口氣寫出來的, 而是上網 爬文、 一點一點蓋出來的。 簡單說明:

  1. -font AR-PL-UKai-TW 指定字型
  2. -pointsize 24 指定字型大小
  3. -stroke '#0008'指定文字顏色 (含透明度)
  4. -undercolor '#ffc8' 指定文字背景顏色 (含透明度)
  5. -gravity South 指定字串座落於圖片的那一側
  6. -annotate +0+5 '馬麻什麼時候回來啊?' 要畫上去的字串, 以及位置

字型的部分, 因為要寫中文, 所以不能用預設值, 一定要指定。 根據 這個問答, 可先用 convert -list font 查詢有哪些字型可用。 我在 lubuntu 上面有安裝 fonts-arphic-ukai 跟 fonts-arphic-uming 兩個套件, 所以有 AR-PL-UKai-TW 跟 AR-PL-UMing-TW 可用。

絞盡腦汁想台詞這種工作, 也是要逐張用工人智慧手工打造的。 不過一貼到圖裡面去, 再要還原或修改, 那就更複雜了。 所以較理想的方式是用 digikam 相片整理軟體 或是用 exiftool 命令列工具 把文字存在 jpg 檔裡面的 IPTC/IIF/XMP/EXIF 欄位, 然後再用指令產生如上的貼字指令。 這就是 「保留原始碼好做事」 的概念。

因為那個過程太囉嗦, 所以我已事先將所有文字存入 ?.jpg 當中。 請在最新的 crop/ 子目錄裡面這樣查看: exiftool -p '$Directory/$FileName $codedcharacterset $Headline | $Subject | $Keywords' ?.jpg (這些文字資訊從最開始的解壓縮檔裡面就存在, 一路上一直留著, 不會因修圖而消失。) 其中最重要的是 codedcharacterset 欄位 -- 如果沒有設成 UTF8, 大部分欄位都不能存中文。 另外還有 Description 欄位等等, 也許更適合儲存較長的文字; 不過我這裡就簡單採用 Subject 欄位好了。

exiftool 的 -p 選項可以直接想寫什麼就寫什麼; 不過更多時候, 我會先用各種蒐集資訊的指令印出一個對照表, 再用 regexp 把對照表轉成想下的指令。 以下在最新的 crop/ 子目錄下執行:

exiftool -p '$FileName $Subject' ?.jpg > list.txt
perl -ne 'print qq(convert -font AR-PL-UKai-TW -pointsize 24 -stroke "#0008" -undercolor "#ffc8" -gravity South -annotate +0+5 "$2" $1 ../cap/$1\n) if /(\S+\.jpg)\s+(.*)/' list.txt

原先單句實驗時, 顏色字串外面用單引號, 像這樣: '#0008' 但是現在 perl -ne '...' 也要用單引號, 所以只好把顏色字串改成雙引號。 那 perl 本身的 print 呢? 還好 perl 可以用 qq(...) 代表雙引號。

如果印出來的指令看來正確, 就先建一個空目錄 mkdir ../cap 然後上箭頭叫出上面那一長句, 最後面再加上 | bash 真的執行下去。 再補一句 montage -tile 2x -geometry 400 ?.jpg dogs-montaged.jpg 合併所有圖片產生本文最上面的成果。 這個 montage 指令也是 ImageMagick 套件的一部分。 上面的指令以每列 2 張的方式合併, 每張小圖寬度 400。

四、 心得及結語

ImageMagick 還有超多特效可用, 請見 官網 這個網站 拿 IM 組合出很多驚豔的效果, 可惜他們的作品並不是自由軟體。

以下是一些不錯的 IM 中文教學:

  1. 用 ImageMagick 將 PDF 轉成高品質的預覽圖檔
  2. 關於 ImageMagick 的工作筆記
  3. 圖像神器 ImageMagick

在命令列上要批次處理大量的檔案, 通常有兩個方法: 如果只是單純一堆檔名, 可以用 for 迴圈; 如果每個檔名還會搭配不同的參數, 那可能用 regexp 產生命令會比較方便。

下指令就像蓋房子一樣, 第一眼直接看到完整的指令, 可能會覺得很雄偉很可敬; 但其實就只是一點一點搭起來的而已。 指令並不可怕, 也不要覺得剪貼指令很不上進。 我就經常剪貼。 只要剪貼時勇敢地這裡改一點、 那裡改一點, 就會有收穫、 有感覺。 當資料量很大時, 下指令 vs 滑鼠選單的差別就很明顯了。 命令列控們堅持我們要用電腦, 不要被電腦用。

各種文字格式的檔案, 永遠是命令列控最好的朋友。 下指令時, regexp 很少是主角, 但經常是很重要的配角。 各種文字檔案處理工具也是。 學命令列就像玩積木一樣, 重點在於發揮組合的力量, 而不在於誰擁有哪一塊獨特、 稀有、 昂貴的積木。

沒有留言:

張貼留言

因為垃圾留言太多,現在改為審核後才發佈,請耐心等候一兩天。