Edited at

ImageMagick の JPEG オプション

JPEG を処理するオプションを紹介します。


-quality <Q>

いわずとしれた画質オプションです。-quality 100 が最高画質で、この値を減らすと画質を犠牲にしてファイルサイズを節約できます。

こちらのポプユニティちゃん画像で実験させて頂きます。

for q in `seq 10 10 100` ;

do convert popu-unity.png -quality $q popu-unity-${q}.jpg ;
done

popu-unity

-quality=10 ; 10395 bytes
popu-unity-10.jpg

-quality=20 ; 14867 bytes
popu-unity-20.jpg

-quality=50 ; 24191 bytes
popu-unity-50.jpg

-quality=80 ; 38503 bytes
popu-unity-80.jpg

-quality=90 ; 61733 bytes
popu-unity-90.jpg

-quality=100 ; 156988 bytes
popu-unity-100.jpg

quality 別ファイルサイズをグラフにするとこうなります。グラフは画像によって変わりますが、傾向としては大体の画像でこれと同様の形です。

image.png

個人的なお勧めとしては、大事なイラストには 90以上、カメラで撮影したものなら 70〜75。どんな画像がくるか分からない場合は 85 位が良いと思ってます。


-quality <Ql>,<Qc>

この -quality オプション自体は有名ですが、実は luma(輝度) と chroma(色味) とで値を別々に指定出来ます。

convert popu-unity.png -quality 85,10 popu-unity-85,10.jpg

convert popu-unity.png -quality 85,50 popu-unity-85,50.jpg

これで luma(輝度)を85、chroma(色味)は 10 と 50 指定です。

popu-unity

-quality 85,10 ; 34780 bytes popu-unity-85,10.jpg

-quality 85,50 ; 37397 bytes popu-unity-85,50.jpg

イラストだと色味が大事なので誤魔化しにくいですね。。あと色味を犠牲する場合は先にクロマサブサンプリングを使うでしょうから、Luma に比べてその時点でデータ量の少ない CbCr を更に頑張って減らしても、有用な状況は少なさそうです。


-sampling-factor

クロマサブサンプリングを指定出来ます。

% convert popu-unity.png -sampling-factor 1x1,1x1,1x1 popu-unity-444.jpg

% convert popu-unity.png -sampling-factor 2x1,1x1,1x1 popu-unity-422.jpg
% convert popu-unity.png -sampling-factor 2x2,1x1,1x1 popu-unity-420.jpg
%
% identify -format "%f %[jpeg:sampling-factor]\n" popu-unity-4??.jpg
popu-unity-420.jpg 2x2,1x1,1x1
popu-unity-422.jpg 2x1,1x1,1x1
popu-unity-444.jpg 1x1,1x1,1x1

クロマサブサンプリングって何?という方は、こちらはご参考に。

輝度の解像度そのままに色味の解像度だけ減らす機能です。横だけ半分にしたり、縦横半分に出来ます。

YUV422

image.png

あと、もっと直接的な記法もあります。

% convert popu-unity.png -sampling-factor 4:4:4 popu-unity-444.jpg

% convert popu-unity.png -sampling-factor 4:2:2 popu-unity-422.jpg
% convert popu-unity.png -sampling-factor 4:2:0 popu-unity-420.jpg
%
% identify -format "%f %[jpeg:sampling-factor]\n" popu-unity-4??.jpg
popu-unity-420.jpg 2x2,1x1,1x1
popu-unity-422.jpg 2x1,1x1,1x1
popu-unity-444.jpg 1x1,1x1,1x1


-interlace jpeg

大抵の状況ではデフォルトで Baseline JPEG が作られます。

(たしか MozJPEG ライブラリだとデフォルトが Progressive だったかと)

interlace オプションで Progressive JPEG を作れます。

% convert popu-unity.png -interlace JPEG popu-unity-interlace.jpg

% convert popu-unity.png -interlace None popu-unity.jpg
% ls -l popu-unity.jpg popu-unity-interlace.jpg
-rw-r--r-- 1 yoya staff 64491 12 10 12:11 popu-unity-interlace.jpg
-rw-r--r-- 1 yoya staff 66347 12 10 12:11 popu-unity.jpg

Progressive JPEG が何かはこちらを参考にして下さい。

JPEG は画像の粗い情報と細かい情報を分離する方式で、その粗い情報を先頭に、細かい情報を後ろにまとめるのが Progressive JPEG です。

ネットワークの重たい環境でもファイル読み込み途中でもはじめに粗い画像を表示して段々と細かい画像に変わっていく、大変有用な形式です。

Baseline と Progressive の比較イメージ

image.png

Baseline よりもファイルサイズが減る傾向がありますので、積極的に試すと良いでしょう。

ただ、Progressive は Baseline よりエンコード/デコード共にだいぶ重たい傾向があるので、リアルタイム処理なり大量に処理したい場合等で注意が必要です。


-comment

任意のコメント文字を入れられます。

(-comment 引数は入力画像ファイルより前にないとコメントが入りませんでした)

% convert -comment "This is TEST\n" input.png out.jpg

% identify -verbose out.jpg | grep -i comment
comment: This is TEST

ガラケーの頃に、

kddi_copyright=on,copy="NO"

等と入れて画像のコピーをガードといいますか、プロテクトしてたのが懐かしいです。


-profile:skip

JPEG ファイルを読み込む時にメタデータをスキップ出来ます。libjpeg の時点でスキップするので、ささやかながらメモリやCPUに優しいのが嬉しいですね。


  • -profile:skip=APP (APP2,13,14 以外をスキップ)

  • -profile:skip=ICC (APP2 のICCプロファイルをスキップ)

  • -profile:skip=IPTC (APP13 のフォトショップリソース)

の3つがあります。

ちなみに APP14 スキップは指定できません。CMYK の時によく含まれるのですが、この内容次第で色の解釈が変わってしまうので、うっかり読み飛ばすと辛い事になります。妥当だと思います。

-trim でもメタデータを消せますが、一度読み込んでから処理するのは勿体無いですし、全てを消すのは危ないので、その意味でこのオプションはお勧めです。


-define jpeg:

-define jpeg:~ で色々出来るのを一通り紹介します。

jpeglib v8 以降でないと動かないオプションが多いかもしれません。


jpeg:size

JPEG は 8x8 DCT 係数でデータを保持するので、iDCT の際に係数の読み方を変えるだけで 1/2、1/4、1/8 のサイズに画像をデコード出来ます。

主にサムネールを作る時に有用ですが、画像を縮小する際に JPEGデータから真面目に元のサイズのRGB(又はYCbCr)画像を展開して縮小するのでなく、はじめから小さ目のサイズでRGBを展開して、そこから少しだけ縮小する事で CPU にもメモリにも優しく処理が出来ます。

ただし、無条件で適用するとまずいので、詳しくはこちらは参考にして下さい。

要するに、拡大する時にこのオプションを使うと逆に処理が重たくなります。


jpeg:extent

指定したサイズを超えない範囲で、なるべく画質の良い圧縮を探ります。

以前は jpeg:extent があると -quality を無視したり -quality 100 の時だけ jpeg:extent が有効になったりしましたが、ImageMagick-6.9.2-5 からは、 jpeg:extent で指定したサイズを超えない範囲で -quality も反映します。

例えば、この画像で quality だけの場合最大 3MB まで膨らみますが。

% for q in `seq 10 10 100` ;

do convert fujisan.png -quality $q fujisan-${q}.jpg ;
done
% ls -lSr fujisan-*.jpg
-rw-r--r-- 1 yoya staff 79118 12 10 11:57 fujisan-10.jpg

-rw-r--r-- 1 yoya staff 146142 12 10 11:57 fujisan-20.jpg
-rw-r--r-- 1 yoya staff 199023 12 10 11:57 fujisan-30.jpg
-rw-r--r-- 1 yoya staff 241849 12 10 11:57 fujisan-40.jpg
-rw-r--r-- 1 yoya staff 283685 12 10 11:57 fujisan-50.jpg
-rw-r--r-- 1 yoya staff 329283 12 10 11:57 fujisan-60.jpg
-rw-r--r-- 1 yoya staff 400925 12 10 11:57 fujisan-70.jpg
-rw-r--r-- 1 yoya staff 519535 12 10 11:57 fujisan-80.jpg
-rw-r--r-- 1 yoya staff 957076 12 10 11:57 fujisan-90.jpg
-rw-r--r-- 1 yoya staff 3445570 12 10 11:57 fujisan-100.jpg

jpeg:extent で 1MB 制限をかけると。収まるように画質を調整します。

%  for q in `seq 10 10 100` ;

do convert fujisan.png -quality $q -define jpeg:extent=1024KB fujisan-${q}.jpg ;
done
% ls -lSr fujisan-*.jpg
-rw-r--r-- 1 yoya staff 71459 12 10 12:03 fujisan-10.jpg
-rw-r--r-- 1 yoya staff 140378 12 10 12:03 fujisan-20.jpg
-rw-r--r-- 1 yoya staff 199023 12 10 12:03 fujisan-30.jpg
-rw-r--r-- 1 yoya staff 239588 12 10 12:03 fujisan-40.jpg
-rw-r--r-- 1 yoya staff 283685 12 10 12:03 fujisan-50.jpg
-rw-r--r-- 1 yoya staff 329283 12 10 12:03 fujisan-60.jpg
-rw-r--r-- 1 yoya staff 391614 12 10 12:03 fujisan-70.jpg
-rw-r--r-- 1 yoya staff 503428 12 10 12:03 fujisan-80.jpg
-rw-r--r-- 1 yoya staff 737961 12 10 12:03 fujisan-90.jpg
-rw-r--r-- 1 yoya staff 1015086 12 10 12:03 fujisan-100.jpg

内部的には、2分探索で quality を変更して条件に合うまで何度も JPEG データを作成するので、重たいかも。。。?


jpeg:dct-method


  • -define jpeg:dct-method=float


    • DCT 係数は通常整数で扱いますが、浮動小数点も対応しています。

    • default, fastest, float, ifast, islow(=default) の5種を指定できます。



% for m in default fastest float ifast islow ;

do echo $m ; time convert fujisan.png -define jpeg:dct-method=${m} fujisan-${m}.jpg ;
done
default
convert fujisan.png -define jpeg:dct-method=${m} fujisan-${m}.jpg 0.32s user 0.07s system 97% cpu 0.402 total
fastest
convert fujisan.png -define jpeg:dct-method=${m} fujisan-${m}.jpg 0.30s user 0.07s system 95% cpu 0.391 total
float
convert fujisan.png -define jpeg:dct-method=${m} fujisan-${m}.jpg 0.26s user 0.07s system 97% cpu 0.340 total
ifast
convert fujisan.png -define jpeg:dct-method=${m} fujisan-${m}.jpg 0.32s user 0.08s system 95% cpu 0.412 total
islow
convert fujisan.png -define jpeg:dct-method=${m} fujisan-${m}.jpg 0.31s user 0.08s system 96% cpu 0.398 total
% ls -l popu-unity-*
-rw-r--r-- 1 yoya staff 66510 12 10 11:19 popu-unity-default.jpg
-rw-r--r-- 1 yoya staff 66538 12 10 11:19 popu-unity-fastest.jpg
-rw-r--r-- 1 yoya staff 66347 12 10 11:19 popu-unity-float.jpg
-rw-r--r-- 1 yoya staff 66538 12 10 11:19 popu-unity-ifast.jpg
-rw-r--r-- 1 yoya staff 66510 12 10 11:19 popu-unity-islow.jpg

微妙に float 指定が速くてファイルサイズも節約できてるような。。float は一番遅いはずなのですが。。(要調査)


jpeg:q-table

周波数成分をどのくらい荒く(量子化)保存するのかを周波数別に定義する量子化テーブルを渡す事ができます。

ファイル名を指定します。 <prefix>/etc/ImageMagick-6/quantization-table.xml にサンプルがあるので、これを弄って保存したファイルを指定しましょう。

% cp /usr/local/etc/ImageMagick-7/quantization-table.xml .

% vi quantization-table.xml
% convert fujisan.png fujisan.jpg
% convert fujisan.png -define jpeg:q-table=quantization-table.xml fujisan-quantize.jpg
% ls -l fujisan.jpg fujisan-quantize.jpg
-rw-r--r-- 1 yoya staff 1149364 12 10 12:20 fujisan-quantize.jpg
-rw-r--r--@ 1 yoya staff 1071489 12 10 12:20 fujisan.jpg

このケースだと、ファイルサイズ的にデフォルトの方が良さそうですね。

ちなみにテーブルは XML 形式。

<quantization-tables>

<table slot="0" alias="luma">
<description>Luma Quantization Table</description>
<levels width="8" height="8" divisor="1">
16, 16, 16, 18, 25, 37, 56, 85,
16, 17, 20, 27, 34, 40, 53, 75,
16, 20, 24, 31, 43, 62, 91, 135,
18, 27, 31, 40, 53, 74, 106, 156,
25, 34, 43, 53, 69, 94, 131, 189,
37, 40, 62, 74, 94, 124, 169, 238,
56, 53, 91, 106, 131, 169, 226, 311,
85, 75, 135, 156, 189, 238, 311, 418
</levels>
</table>

この Levels ずばりの値が反映されない事があります。特定の係数をがっつり荒い量子化をしたいみたいな大雑把な操作が出来る程度に考えた方が良さそうです。なお、0 指定するとなぜか 1 になります。せめて極端に大きな値として解釈して欲しい。。


jpeg:optimize-coding

デフォルトだと、最適化されたハフマン符号テーブルを作成しますが、


  • -define jpeg:optimize-coding=false

を指定すると、決め打ちのハフマン符号表を用います。

少しだけ処理がはやく終わりますが、サイズが膨らみます。

% convert popu-unity.png popu-unity.jpg

% convert popu-unity.png -define jpeg:optimize-coding=false popu-unity-h.jpg
% ls -l popu-unity.png popu-unity.jpg popu-unity-h.jpg
-rw-r--r-- 1 yoya staff 73362 12 10 11:06 popu-unity-h.jpg
-rw-r--r-- 1 yoya staff 66347 12 10 11:06 popu-unity.jpg

% time convert fujisan.png fujisan.jpg
0.27s user 0.08s system 97% cpu 0.351 total
% time convert fujisan.png -define jpeg:optimize-coding=false fujisan.jpg
0.23s user 0.06s system 96% cpu 0.300 total


jpeg:fancy-upsampling

fancy-upsampling は libjpeg のオプションです。

クロマサブサンプリングで Y に比べて CbCr のサンプル数が 1/2 又は 1/4 になるまで間引きされる事があり、表示する時には Y と同じサンプル数に戻す必要があるので、その際の補間を頑張るか指示します。

なお、libjpeg v6 と v7 以降で true の時の挙動が変わります。turbo-jpeg や mozjpeg は v6 と同じです。


libjpeg-v6, turbo-jpeg-2, mozjpeg-3


  • false: Nearest Neighbor で補間する (偽の色が出やすい)

  • true: Bi-Linear で補間 (色が淡くなり易い)

* jpeg-6b/jdsample.c

METHODDEF(void)
h2v2_fancy_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr,
JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr)
{
(略)
*outptr++ = (JSAMPLE) ((thiscolsum * 3 + lastcolsum + 8) >> 4);
*outptr++ = (JSAMPLE) ((thiscolsum * 3 + nextcolsum + 7) >> 4);

隣接する CbCr を 3:1 で混ぜます。(横方向で考えると、左隣が lastcolsum で右隣が nextcolsum です)

image.png

https://www.itu.int/rec/T-REC-T.871-201105-I page.5

この図の配置の通りに復元しているものと思われます。


libjpeg-v7,8,9

true 時の挙動が変わっています。


  • true: iDCT scaling デコード機能で補間。ImageMagick の JPEG size hinting と同じ。

JPEG は YCbCr のデータ列を DCT で周波数成分にして保存されています。元の YCbCr 値に戻す時は、周波数成分から iDCT にかけるので、周波数サンプリングが半分でも引き伸ばして復元できます。

* jpeg-7/jdmaster.c

/* In selecting the actual DCT scaling for each component, we try to
* scale up the chroma components via IDCT scaling rather than upsampling.
* This saves time if the upsampler gets to use 1:1 scaling.
* Note this code adapts subsampling ratios which are powers of 2.
*/
for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
ci++, compptr++) {
int ssize = 1;
while (cinfo->min_DCT_h_scaled_size * ssize <=
(cinfo->do_fancy_upsampling ? DCTSIZE : DCTSIZE / 2) &&
(cinfo->max_h_samp_factor % (compptr->h_samp_factor * ssize * 2)) == 0) {
ssize = ssize * 2;
}
compptr->DCT_h_scaled_size = cinfo->min_DCT_h_scaled_size * ssize;
ssize = 1;
while (cinfo->min_DCT_v_scaled_size * ssize <=
(cinfo->do_fancy_upsampling ? DCTSIZE : DCTSIZE / 2) &&
(cinfo->max_v_samp_factor % (compptr->v_samp_factor * ssize * 2)) == 0) {
ssize = ssize * 2;
}
compptr->DCT_v_scaled_size = cinfo->min_DCT_v_scaled_size * ssize;

iDCT scaling decode の設定をしてます。


その他

記事を書くのに疲れてきたので、以下のパラメータの解説は割愛します。


  • jpeg:colors


    • libjpeg に減色を依頼するそうです



  • jpeg:block-smoothing


    • ブロックノイズを目立たなくするフィルタ



  • jpeg:sampling-factor


    • -sampling-factor ときっと同じ




参考URL