【画像処理】ヘッダ情報の確認とバイナリエディタからの画像編集(JPEG編)


ヘッダ情報の確認とバイナリエディタからの画像編集(JPEG編)
前回の記事→「[画像ビューア(ペイント・GIMP)でみる画像ファイル形式の違い](https://qiita.com/spc_ehara/private/45925bb567fd2258abf6)」

前回までの講座でファイル形式の違いについてある程度理解できたと思います。そこで今回は、画像ビューアがファイルそのものからどのような情報を読み込んでいるのかを確認するために、jpg構造の理解とバイナリエディタを使ってjpgファイル形式の画像内部をみていきます。

# ファイル形式ごとのフォーマット

そもそも画像ファイルには、画像ビューアで画面に表示されているデータ以外にも、画像の幅・高さ、画像そのもの以外のデータサイズ、圧縮方式など様々な情報が含まれています。それらのフォーマットはファイル形式によって異なり、jpgフォーマットの内容を解説した後にバイナリエディタを使って具体的に中身を見ていきます。


## jpgへの変換手順

jpgファイルの構造を理解するためには、まずjpgへの変換手順を理解する必要があります。手順は大きく分けて以下の4つです。

1. RGB-YCbCr変換
2. DCT変換(離散コサイン変換)
3. 量子化
4. エントロピー符号化(ランレングス・ハフマン圧縮)

jpg圧縮の基本的な概念は「人間の眼にわからないように情報量を減らす」ことです。そこで人間の眼の性質に着目し、2種類のデータ削減方法を組み合わせて行っています。
輝度の変化には敏感で、色の変化には鈍感という性質を利用した、`明るさを変えずに色の変化情報のみを減らす方法`と図柄の緩やかな変化には敏感で、図柄の細かく急激な変化には鈍感という性質を利用した、`低周波成分を変えずに高周波成分のみを減らす方法`の2つです。

上記4つの手順では、これらの性質を利用した圧縮が行われています。
それでは、具体的に見ていきます。

#### 1. RGB-YCbCr変換

ここでは「明るさを変えずに色の変化情報のみを減らす方法」が実装されています。
画像は基本的にRGB(Red, Green, Blue)の色空間で表されますが、そこから輝度と色の変化情報を取り出すために、以下の式を使ってYCbCr値に変換します。

|色空間|式|
|:-:|:-:|
|Y|(0.2990 * R) + (0.5870 * G) + (0.1140 * B)|
|Cb|-(0.1687 * R) - (0.3313 * G) + (0.5000 * B) + 128|
|Cr|(0.5000 * R) - (0.4187 * G) - (0.0813 * B) + 128 |

Yは輝度、Cbは輝度と青レベルの差、Crは輝度と赤レベルの差です。

次に、Y(輝度)はそのままで、Cb(輝度と青レベルの差)とCr(輝度と赤レベルの差)だけを間引く、クロマサブサンプリングとよばれる方法で、色の変化情報のみを間引いていきます。この間引き方にも様々な種類がありますが、ここでは代表的なものを紹介します。各サンプリング比は、Y成分を基準として、CbCr成分の比率を表しています。ちなみに、Y成分のことをルミナンスといい、CbCr成分のことをクロマといいます。色成分のみを間引きして、サンプリングするので、クロマサブサンプリングと呼ばれています。
例えば、「1.1 入力画像(16×32)」の場合、YCbCr変換後に各サンプリング比でサンプリングすると、以下のようになります。

|1.1 入力画像(16×32)|
|:-:|
|![](https://user-images.githubusercontent.com/74283288/112803032-d7407600-90ad-11eb-810f-7fc0140281d1.png)|

|サンプリング比|Y(輝度)|サンプリング後CbCr(色成分)|サンプリング後YCbCr|
|:-:|:-:|:-:|:-:|
|4:4:4|![](https://user-images.githubusercontent.com/74283288/112803054-da3b6680-90ad-11eb-94d6-355a9104391f.png)|![](https://user-images.githubusercontent.com/74283288/112803032-d7407600-90ad-11eb-810f-7fc0140281d1.png)|![](https://user-images.githubusercontent.com/74283288/112803053-d9a2d000-90ad-11eb-86fd-de73b7838513.png)|
|4:2:2|![](https://user-images.githubusercontent.com/74283288/112803054-da3b6680-90ad-11eb-94d6-355a9104391f.png)|![](https://user-images.githubusercontent.com/74283288/112803048-d90a3980-90ad-11eb-9fa3-1c51739b1c5c.png)|![](https://user-images.githubusercontent.com/74283288/112803050-d9a2d000-90ad-11eb-9979-0670fcdafe75.png)|
|4:2:0|![](https://user-images.githubusercontent.com/74283288/112803054-da3b6680-90ad-11eb-94d6-355a9104391f.png)|![](https://user-images.githubusercontent.com/74283288/112803043-d871a300-90ad-11eb-8f07-75e2f0444869.png)|![](https://user-images.githubusercontent.com/74283288/112803046-d90a3980-90ad-11eb-9691-03ed5be48046.png)|
|4:1:1|![](https://user-images.githubusercontent.com/74283288/112803054-da3b6680-90ad-11eb-94d6-355a9104391f.png)|![](https://user-images.githubusercontent.com/74283288/112803039-d7d90c80-90ad-11eb-90a9-42214ce3dc57.png)|![](https://user-images.githubusercontent.com/74283288/112803041-d871a300-90ad-11eb-8c2e-89f911803f70.png)|

上記図のように、サンプリング比によって色の変化が間引かれているのがわかります。このようにして明るさを変えずに色の変化情報のみを減らしています。
また、この時点で今後のデータ処理に使われるMCU(Minimum Coded Unit)という単位に分割されます。MCUは基本的に8*8ピクセル単位の画像データをYCbCr成分に分けて保存したもので、この時に上記の色成分に対するサンプリングが行われることでMCUに保存する画像データを減らすことができています。
例えば、上記の表のサンプリング比4:4:4の場合、8×8ピクセルの画像データはY(輝度)が8色、サンプリング後CbCr(色成分)が8色あります。よって、Y(8) + Cb(8) + Cr(8) = 24ブロックできることになります。

|サンプリング比|Y(輝度)色数|Cb(青レベル)色数|Cr(赤レベル)色数|合計ブロック数|
|:-:|:-:|:-:|:-:|:-:|
|4:4:4|8|8|8|24|
|4:2:0|8|2|2|12|

しかし、サンプリング比4:2:0の場合、8×8ピクセルの画像データはY(輝度)が8色、サンプリング後CbCr(色成分)が2色あります。つまり、Y(8) + Cb(2) + Cr(2) = 12ブロックできることになり、結果として12ブロック分のデータを減らすことができるのです。


#### 2. DCT変換(離散コサイン変換)

続いて、MCU内の8×8ピクセルの画像データ(ブロック)にフォーカスしていきます。
ここでは、各ブロックに対してDCT変換[^1]を行い、DCT変換後画像例のように64段階の周波数成分に分解して左上が低周波成分、右下が高周波成分になるように並べていきます。

|DCT変換後画像例|
|:-:|
|![](https://user-images.githubusercontent.com/74283288/113809019-36834200-97a2-11eb-8693-70fcfb7889bb.png)|

[^1]: DCTはデータ列をcos関数の和で表現する形式に変換する処理です。フーリエ変換の仲間で、周期関数に三角関数を用いるところはフーリエ変換と同じですが、cos関数のみを用いるところが異なります。


低周波成分は画像内変化の小さいところが集まり、高周波成分は画像内変化の大きいところが集まります。
この時に左上端の部分はコサイン関数が絡まないので、とくに「直流(DC)成分」と呼び、その他の部分を「交流(AC)成分」と呼びます。
そうして並べ替えられたデータは、次の工程である量子化、そして符号化によって圧縮されていきます。
つまり、DCT変換時点ではデータの圧縮は行われずに単に変換のみが行われているため、ここでのデータ量に変化はないものの、続く量子化・符号化工程において効率的な圧縮を実現するための重要な工程となります。


#### 3. 量子化

ここでは、DCT変換された各ブロックの高周波成分を間引きすることで、次の工程の符号化で効率的な圧縮ができるようにすることが目的です。端的に言えば、DCT変換されたブロックを同じ大きさの量子化テーブルで割るだけです。

|量子化テーブル(Y成分用)|量子化テーブル(CbCr成分用)|
|:-:|:-:|
|![](https://user-images.githubusercontent.com/74283288/113816091-3d648180-97af-11eb-9c2a-b7fbccbcaf85.PNG)|![](https://user-images.githubusercontent.com/74283288/113816088-3c335480-97af-11eb-9cff-ec812558a709.PNG)|

「DCT変換後画像例」のように、DCT変換されたブロックは左上端に低周波成分、右下端に高周波成分が集まっています。そのため量子化の工程では、高周波成分の間引きを考え、左上端に小さい値、右下端に大きい値が来るように量子化テーブルが構成されています。
この量子化テーブルを利用してDCT変換されたブロックを割ることでブロック全体の値を小さくします。この時に出た余りは切り捨てられるため、とくに高周波成分の値に多く0が出ます。
次の工程ではデータ値の登場頻度をもとに符号を決めるため、ここで0の部分が多く出るほどそれだけ情報を間引くことができます。また、ここで切り捨てた余りが大きいほどデコード時の画像劣化が大きくなるために、jpgの品質調整が量子化テーブルの値調整によって行われていることがわかります。
また、上記2つの量子化テーブルのようにYCbCrのY成分には小さい値を、CbCr成分には大きい値を割り当てて輝度値の劣化を抑えるなど、各成分ごとにも品質調整がなされています。

#### 4. エントロピー符号化(ランレングス・ハフマン圧縮)

最後の工程がこの符号化になります。
ここでは、ハフマン符号化とランレングス符号化を組み合わせて、圧縮を行っています。また直流成分と交流成分で記録する順番が異なりますが、基本的な処理方法は同じです。

まず、直流成分は隣接するブロック間(MCUの前後ブロック間)の平均輝度値は似たような値をとるので、1つ前のブロックの直流成分との差分を抽出してその値に対してハフマン符号化を行っていきます。
この時、DCT変換後に量子化されたブロックには幅広い範囲の値が記録されているため、このままハフマン符号化を適用するのは効率的ではありません。そのため、値の取りうる範囲を16区間に分割し、その区間番号とその区間の何番目の値に対応するのかを記録することで効率的なハフマン符号化を行います。

次に、交流成分はここまでの工程で左上端に低周波成分、右下端に高周波成分が集まっています。さらに、高周波成分には0が多く並んでいる状態です。これを活用して最初にランレングス符号化を行います。ランレングス符号化は、同じ数字が連続しているほど小さく圧縮できるため、データを見ていく順番も一工夫されていて、そのスキャン方法をジグザグスキャンといい、左上端から右下端に向かってジグザグに進んでいき、できる限り0を並べてスキャンできるようにしています。

|ジグザグスキャン|
|:-:|
|![](https://user-images.githubusercontent.com/74283288/113818425-b5807680-97b2-11eb-920b-9f7373d0defa.png)|

その後、直流成分と同じように各値に区間番号とその区間の何番目の値に対応するのかを記録してハフマン符号化を行います。
また、この時にどのデータがどのハフマン符号と対応しているかを記載したハフマンテーブルというものが作られ、デコード時にこのテーブルをもとに復号化されます。

以上、4つの手順によってjpg画像が作られています。
ここまででjpgの変換手順について説明しました。つづいて、そのファイル構造について説明していきます。

## jpgのファイル構造

jpgの全体的なファイル構造は次のようになっています。

|jpgの構造|
|:-:|
|![](https://user-images.githubusercontent.com/74283288/113822714-be744680-97b8-11eb-9acc-3f6f45f732a1.png)|

先頭にSOIマーカ(`FFD8`)、最後にEOIマーカ(`FFD9`)というファイルの始まりと終わりを示すものがあり、その間に画像そのもののデータとそれ以外のデータが挟まれています。[^2]
[^2]: 厳密には、EOIマーカ以降は何が記録してあっても無視するので、必ずしもEOIマーカがファイルの最終位置にあるとは限りません。また、多くの画像ビューアでは画像データが破損している場合に備え、EOIマーカが無くても画像を表示出来るようになっています。ただし、SOIマーカは画像を表示するために必ず必要となります。

ここにある複数のセグメントは、画像そのもの以外のデータを格納している部分で、その構造は次のようになっています。

|セグメント構造|
|:-:|
|![](https://user-images.githubusercontent.com/74283288/113822717-bfa57380-97b8-11eb-807d-7095688649ad.png)|

セグメントの先頭にデータ(パラメータ)の種類を表す`マーカ`と呼ばれるものが必ずあり、その後ろにセグメントの長さ、パラメータが格納されています。

### 必須セグメント

jpgには様々なセグメントが格納されていますが、その中でも画像の表示に必ず必要となるセグメントが4つあります。
DQTセグメント、DHTセグメント、SOFセグメント、SOSセグメントです。
これらはどのjpgファイルにも必ずあるセグメントで、それぞれ重要な役割を持っているので、1つずつ見ていきます。

#### DQT(Define Quantization Table) -- 量子化テーブル定義
DQTはjpgファイルの画質に関するデータが記録されているセグメントで、ひとつのセグメントで複数の量子化テーブルを定義することが出来ます。具体的にはjpg変換手順の量子化で用いた量子化係数を保存したテーブルが定義してあり、そのマーカは`FFDB`です。また、輝度成分ブロックと色成分ブロックで別々の量子化テーブルを使うことが多いので、ここでは複数のテーブルとなることが多いです。
テーブルは固定長でテーブル番号が4種類（0～3）あります。また、DQTは後述するSOFセグメントから参照されます。

##### DQTの構造

|名称|サイズ|内容|
|:-:|:-:|:-:|
|Lq|2バイト|セグメント長|
|Pq|4ビット(上位)|量子化テーブル精度 (8bit or 16bit)|
|Tq|4ビット(下位)|量子化テーブル番号 (0~3)|
|Q-0~63|Pq=8bit 64バイト　Pq=16bit 128バイト|64個の量子化係数|

#### DHT(Define Huffman Table) -- ハフマンテーブル定義
DHTはjpgファイルのハフマン圧縮、復元時に使用するデータが記録されているセグメントで、ひとつのセグメントで複数のハフマンテーブルを定義出来ます。具体的にはjpg変換手順のハフマン符号化で用いたハフマン符号との対応を保存したテーブルが定義してあり、そのマーカは`FFC4`です。また、輝度成分ブロックと色成分ブロックごとに直流成分・交流成分があり、別々のハフマンテーブルを使うことが多いので、ここではDQTと同じように複数のテーブルとなることが多いです。
テーブルは可変長でテーブル番号が4種類（0～3）、テーブルクラス（DCとAC）が2種類、計8種類あります。また、DHTは後述するSOSセグメントから参照されます。


##### DHTの構造

|名称|サイズ|内容|
|:-:|:-:|:-:|
|Lh|2バイト|セグメント長|
|Tcn|4ビット(上位)|ハフマンテーブルクラス (DC成分 or AC成分)|
|Thn|4ビット(下位)|ハフマンテーブル番号 (0~3)|
|L1~L16|16バイト|ハフマンテーブルビット配分数|
|V1~V16|L1~L16の合計ｘ1バイト|ハフマンテーブル定義データ<br>DC成分：データビット数<br>AC成分：上位4ビット ランレングス数<br>  　　　　 下位4ビット データビット数|


#### SOF(Start Of Frame) -- フレームヘッダ
SOFはjpgファイルの種類や画像サイズなど重要なパラメータが記録されているセグメントで、ひとつのjpgファイルやjpg構造に必ず一つ記録されます。
SOFは圧縮形式や画像形式によってマーカが異なり、全部で13種類のマーカが有ります。jpgは様々な圧縮方式があり、その中でもベースライン方式とプログレッシブ方式が一般的に使われています。ベースライン方式なら`FFC0`、プログレッシブ方式なら`FFC2`がマーカに利用されます。各色成分ごとに使用している量子化テーブルの対応付けをここでしているため、ここのセグメントからDQTセグメントを参照しています。



##### SOFの構造

|名称|サイズ|内容|
|:-:|:-:|:-:|
|Lf|2バイト|セグメント長|
|P|1バイト|成分数|
|Y|2バイト|画像縦サイズ|
|X|2バイト|画像横サイズ|
|Nf|1バイト|成分数|
|Cn|1バイト|成分ID|
|Hn|4ビット(上位)|水平サンプリング値|
|Vn|4ビット(下位)|垂直サンプリング値|
|Tqn|1バイト|対応量子化テーブル番号|


#### SOS(Start Of Scan) -- スキャンヘッダ
SOSはイメージデータの先頭に記録されるセグメントで、後に続くイメージデータの成分などの内容が記録されています。マーカは`FFDA`です。
各色成分ごとに使用しているハフマンテーブルの対応付けをここでしているため、ここのセグメントからDHTセグメントを参照しています。


##### SOSの構造

|名称|サイズ|内容|
|:-:|:-:|:-:|
|Ls|2バイト|セグメント長|
|Ns|1バイト|成分数|
|Csn|1バイト|成分ID|
|Tdn|4ビット(上位)|DC成分ハフマンテーブル番号|
|Tan|4ビット(下位)|AC成分ハフマンテーブル番号|
|Ss|1バイト|量子化係数開始番号|
|Se|1バイト|量子化係数終了番号|
|Ah|4ビット(上位)|前回のスキャンの係数値分割シフト量(最初のスキャンは0)|
|Ai|4ビット(下位)|係数値分割シフト量(最後のスキャンは0)|



### その他セグメント

jpgは上記で説明した4つのセグメント以外にも、通常格納されているセグメントがあります。カメラで撮影した時の状況・情報を記録したExif情報やデータの欠落時にその影響を最小限にするためのセグメントなどがあります。

#### COM(comment) -- コメントセグメント
画像ファイルに埋め込むことができるテキストデータを定義するセグメントで、SOIマーカ・EOIマーカ間であれば自由にテキストデータを埋め込むことができます。マーカは`FFFE`です。
基本的には加工を行ったソフトの名前・バージョンやコピーライトが書き込まれています。

##### COMの構造

|名称|サイズ|内容|
|:-:|:-:|:-:|
|Lc|2バイト|セグメント長|
|Cmn|1バイト×文字数|コメント(ASCIIテキスト)|


#### APP0~15(Application) -- アプリケーションセグメント
アプリケーション側で自由に規定できるセグメントで、ここには各OSごとの差異を吸収するためのJFIF情報や撮影時の情報が記録されたExif情報などがありますが、ここではAPP0(JFIFフォーマット)とAPP1(Exifフォーマット)のみを取り上げます。

#### APP0(JFIFフォーマット)セグメント
APP0は各プラットフォーム(OS等)との互換を取るための情報が記録されているので、大半のjpgファイルに含まれています。ここで重要なのがASCII文字で書かれている"JFIF"という文字です。ビューアは、この文字を見てjpg画像であるかを判断しています。マーカは`FFE0`です。

##### APP0の構造

|名称|サイズ|内容|
|:-:|:-:|:-:|
|La|2バイト|セグメント長|
|Id|5バイト|ASCII文字で"JFIF"+NULL|
|Ver|2バイト|JFIFのバージョン|
|U|1バイト|解像度単位(0:単位なし 1:dpi 2:dpcm)|
|Xd|2バイト|横解像度|
|Yd|2バイト|縦解像度|
|Xt|1バイト|サムネイル横サイズ|
|Yt|1バイト|サムネイル縦サイズ|
|RGB|3バイト|1ピクセルのRGBデータが全ピクセル数分|


#### APP1(Exifフォーマット)セグメント
APP1はデジタルカメラの撮影情報が記録されているセグメントで、大半のカメラの出力ファイルに含まれています。ここには撮影条件や撮影場所などの情報が記録されていますが、カメラの機種やメーカーによって異なることがあります。
マーカは`FFE1`です。


##### APP1の構造

|名称|サイズ|内容|
|:-:|:-:|:-:|
|La|2バイト|セグメント長|
|Id|6バイト|ASCII文字で"Exif"+NULL|
|Th|2バイト|Tiffヘッダ|
|Tid|2バイト|Tiff識別コード|
|Io|4バイト|0th IFDへのポインタ|


#### APP2(カラープロファイル)セグメント
PhotoshopやGIMPなどのソフトでカラープロファイルを記録する項目を選んだ場合、このセグメントが記録され、撮影や編集時の色を忠実に再現させるためのデータが入っています。このセグメントのデータを利用するには、それに対応したソフトや機器が必要ですが、プロ向けの規格なので一般的には使うことはないと思われます。マーカは`FFE2`です。

##### APP2の構造
構造はその規格ごとに異なり、そのアーキテクチャを知らなければ理解が難しいです。ISO 15076-1で標準規格化されているものを紹介します。
以下のサイトで必要な参照色空間とデータ構造（タグ）の説明がなされています。気になる方はどうぞ。

[国際標準化団体(International Color Consortium)](https://www.color.org/index.xalter)
[ISO 15076-1](https://www.iso.org/standard/54754.html)


#### DRI(Define Restart Interval) -- リスタートインタバル定義
画像データの部分的な破損が全体に波及するのを防ぐために、jpgファイル作成時に一定の間隔でRST(restart)マーカが埋め込まれる場合があります。RSTマーカはMCUの間に一定間隔で置かれていて、その区間でデータの破損があった場合は次のRSTマーカまで読み飛ばします。
DRIはそのRSTマーカがどのくらいの間隔で置かれているかを定義しているセグメントで、マーカは`FFDD`です。

##### DRIの構造

|名称|サイズ|内容|
|:-:|:-:|:-:|
|Lr|2バイト|セグメント長|
|Ri|2バイト|リスタートインタバル間隔|


ここまででひと通りjpgの構造を説明しました。次はそれらの情報を活用して本当に説明通りの構造になっているかを確認します。


# バイナリエディタからの画像編集
ここではバイナリエディタを使って、実際に画像の中身を確認していきます。
今回は、GIMPを使用して16×16の黒で塗りつぶした画像を使用します。

|1.1 サンプル画像|
|:-:|
|![](https://user-images.githubusercontent.com/74283288/113553784-3ca6e080-9633-11eb-9ffb-78ec235a86a5.png)|

上記の画像をバイナリエディタを使って見てみると、以下のようになります。



```makefile:1.1サンプル画像
  Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 	
00000000: FF D8 FF E0 00 10 4A 46 49 46 00 01 01 01 01 2C    .X.`..JFIF.....,
00000010: 01 2C 00 00 FF FE 00 15 49 20 6D 61 64 65 20 74    .,...~..I.made.t
00000020: 68 69 73 20 62 79 20 47 49 4D 50 FF E2 02 B0 49    his.by.GIMP.b.0I
00000030: 43 43 5F 50 52 4F 46 49 4C 45 00 01 01 00 00 02    CC_PROFILE......
00000040: A0 6C 63 6D 73 04 30 00 00 6D 6E 74 72 52 47 42    .lcms.0..mntrRGB
00000050: 20 58 59 5A 20 07 E5 00 04 00 02 00 00 00 1E 00    .XYZ..e.........
00000060: 28 61 63 73 70 4D 53 46 54 00 00 00 00 00 00 00    (acspMSFT.......
00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
00000080: 00 00 00 F6 D6 00 01 00 00 00 00 D3 2D 6C 63 6D    ...vV......S-lcm
00000090: 73 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    s...............
000000a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
000000b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
000000c0: 0D 64 65 73 63 00 00 01 20 00 00 00 40 63 70 72    .desc.......@cpr
000000d0: 74 00 00 01 60 00 00 00 36 77 74 70 74 00 00 01    t...`...6wtpt...
000000e0: 98 00 00 00 14 63 68 61 64 00 00 01 AC 00 00 00    .....chad...,...
000000f0: 2C 72 58 59 5A 00 00 01 D8 00 00 00 14 62 58 59    ,rXYZ...X....bXY
00000100: 5A 00 00 01 EC 00 00 00 14 67 58 59 5A 00 00 02    Z...l....gXYZ...
00000110: 00 00 00 00 14 72 54 52 43 00 00 02 14 00 00 00    .....rTRC.......
00000120: 20 67 54 52 43 00 00 02 14 00 00 00 20 62 54 52    .gTRC........bTR
00000130: 43 00 00 02 14 00 00 00 20 63 68 72 6D 00 00 02    C........chrm...
00000140: 34 00 00 00 24 64 6D 6E 64 00 00 02 58 00 00 00    4...$dmnd...X...
00000150: 24 64 6D 64 64 00 00 02 7C 00 00 00 24 6D 6C 75    $dmdd...|...$mlu
00000160: 63 00 00 00 00 00 00 00 01 00 00 00 0C 65 6E 55    c............enU
00000170: 53 00 00 00 24 00 00 00 1C 00 47 00 49 00 4D 00    S...$.....G.I.M.
00000180: 50 00 20 00 62 00 75 00 69 00 6C 00 74 00 2D 00    P...b.u.i.l.t.-.
00000190: 69 00 6E 00 20 00 73 00 52 00 47 00 42 6D 6C 75    i.n...s.R.G.Bmlu
000001a0: 63 00 00 00 00 00 00 00 01 00 00 00 0C 65 6E 55    c............enU
000001b0: 53 00 00 00 1A 00 00 00 1C 00 50 00 75 00 62 00    S.........P.u.b.
000001c0: 6C 00 69 00 63 00 20 00 44 00 6F 00 6D 00 61 00    l.i.c...D.o.m.a.
000001d0: 69 00 6E 00 00 58 59 5A 20 00 00 00 00 00 00 F6    i.n..XYZ.......v
000001e0: D6 00 01 00 00 00 00 D3 2D 73 66 33 32 00 00 00    V......S-sf32...
000001f0: 00 00 01 0C 42 00 00 05 DE FF FF F3 25 00 00 07    ....B...^..s%...
00000200: 93 00 00 FD 90 FF FF FB A1 FF FF FD A2 00 00 03    ...}...{!..}"...
00000210: DC 00 00 C0 6E 58 59 5A 20 00 00 00 00 00 00 6F    \..@nXYZ.......o
00000220: A0 00 00 38 F5 00 00 03 90 58 59 5A 20 00 00 00    ...8u....XYZ....
00000230: 00 00 00 24 9F 00 00 0F 84 00 00 B6 C4 58 59 5A    ...$.......6DXYZ
00000240: 20 00 00 00 00 00 00 62 97 00 00 B7 87 00 00 18    .......b...7....
00000250: D9 70 61 72 61 00 00 00 00 00 03 00 00 00 02 66    Ypara..........f
00000260: 66 00 00 F2 A7 00 00 0D 59 00 00 13 D0 00 00 0A    f..r'...Y...P...
00000270: 5B 63 68 72 6D 00 00 00 00 00 03 00 00 00 00 A3    [chrm..........#
00000280: D7 00 00 54 7C 00 00 4C CD 00 00 99 9A 00 00 26    W..T|..LM......&
00000290: 67 00 00 0F 5C 6D 6C 75 63 00 00 00 00 00 00 00    g...\mluc.......
000002a0: 01 00 00 00 0C 65 6E 55 53 00 00 00 08 00 00 00    .....enUS.......
000002b0: 1C 00 47 00 49 00 4D 00 50 6D 6C 75 63 00 00 00    ..G.I.M.Pmluc...
000002c0: 00 00 00 00 01 00 00 00 0C 65 6E 55 53 00 00 00    .........enUS...
000002d0: 08 00 00 00 1C 00 73 00 52 00 47 00 42 FF DB 00    ......s.R.G.B.[.
000002e0: 43 00 03 02 02 03 02 02 03 03 03 03 04 03 03 04    C...............
000002f0: 05 08 05 05 04 04 05 0A 07 07 06 08 0C 0A 0C 0C    ................
00000300: 0B 0A 0B 0B 0D 0E 12 10 0D 0E 11 0E 0B 0B 10 16    ................
00000310: 10 11 13 14 15 15 15 0C 0F 17 18 16 14 18 12 14    ................
00000320: 15 14 FF DB 00 43 01 03 04 04 05 04 05 09 05 05    ...[.C..........
00000330: 09 14 0D 0B 0D 14 14 14 14 14 14 14 14 14 14 14    ................
00000340: 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14    ................
00000350: 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14    ................
00000360: 14 14 14 14 14 14 14 FF C2 00 11 08 00 10 00 10    ........B.......
00000370: 03 01 11 00 02 11 01 03 11 01 FF C4 00 15 00 01    ...........D....
00000380: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
00000390: 08 FF C4 00 14 01 01 00 00 00 00 00 00 00 00 00    ..D.............
000003a0: 00 00 00 00 00 00 00 FF DA 00 0C 03 01 00 02 10    ........Z.......
000003b0: 03 10 00 00 01 95 00 07 FF C4 00 14 10 01 00 00    .........D......
000003c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 20 FF DA    ...............Z
000003d0: 00 08 01 01 00 01 05 02 1F FF C4 00 14 11 01 00    ..........D.....
000003e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20 FF    ................
000003f0: DA 00 08 01 03 01 01 3F 01 1F FF C4 00 14 11 01    Z......?...D....
00000400: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20    ................
00000410: FF DA 00 08 01 02 01 01 3F 01 1F FF C4 00 14 10    .Z......?...D...
00000420: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
00000430: 20 FF DA 00 08 01 01 00 06 3F 02 1F FF C4 00 14    ..Z......?...D..
00000440: 10 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
00000450: 00 20 FF DA 00 08 01 01 00 01 3F 21 1F FF DA 00    ...Z......?!..Z.
00000460: 0C 03 01 00 02 00 03 00 00 00 10 92 4F FF C4 00    ............O.D.
00000470: 14 11 01 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
00000480: 00 00 20 FF DA 00 08 01 03 01 01 3F 10 1F FF C4    ....Z......?...D
00000490: 00 14 11 01 00 00 00 00 00 00 00 00 00 00 00 00    ................
000004a0: 00 00 00 20 FF DA 00 08 01 02 01 01 3F 10 1F FF    .....Z......?...
000004b0: C4 00 14 10 01 00 00 00 00 00 00 00 00 00 00 00    D...............
000004c0: 00 00 00 00 20 FF DA 00 08 01 01 00 01 3F 10 1F    ......Z......?..
000004d0: FF D9                                              .Y


```

## jpg構造の確認

### SOIマーカ・EOIマーカ

|行数|マーカ|内容|
|:-:|:-:|:-:|
|00000000|FFD8|SOIマーカ|
|000004d0|FFD9|EOIマーカ|

先頭行を見てみると、「`FFD8`」が最初に来ています。さらに、最終行は「`FFD9`」で終わっています。つまり、このファイルの先頭にSOIマーカ、最後にEOIマーカがそれぞれあることがわかります。

### APP0(JFIFフォーマット)

|行数|マーカ|内容|
|:-:|:-:|:-:|
|00000000|FFE0|APP0(JFIFフォーマット)マーカ|
|000004d0|FFD9|EOIマーカ|

SOIマーカの隣にAPP0(JFIFフォーマット)のマーカ「`FFE0`」があります。その直後2バイトに「`0010`」が来ていますので、このセグメント長は16バイトだとわかります。次にその後ろ5バイトに「`4A 46 49 46 00`」が来て、ASCII文字に直してみると「`J F I F □`」となり、上記で説明したAPP0のフォーマットに一致していることから、このファイルがjpgであることがわかります。[^3]
[^3]: 厳密にはJFIFフォーマット=jpgではありませんが、現在は大半のjpgがJFIFフォーマットを採用しているため、記事内ではJFIFフォーマット=jpgとします。

### COM(comment)

|行数|マーカ|内容|
|:-:|:-:|:-:|
|00000010|FFFE|COM(コメント)マーカ|

2行目にはCOMマーカがあり、画像作成時に設定した「I made this by GIMP」というコメントが記録されています。


### APP2

|行数|マーカ|内容|
|:-:|:-:|:-:|
|00000020|FFE2|APP2(カラープロファイル)マーカ|

3行目にAPP2(カラープロファイル)マーカの「`FFE2`」があり、そこから46行目の「000002d0」までが1つのセグメントになっています。
構造はわかりませんが、「ICC_PROFILE」というASCII文字で始まっていることからその直後にカラーフォーマットのプロファイル形式やデータ構造が定義されていると考えられます。


### 必須セグメントの確認
必須セグメントは、DQTセグメント、DHTセグメント、SOFセグメント、SOSセグメントの4つがありましたが、これらが実際にあるか確認してみます。

### DQTセグメント

|行数|マーカ|内容|
|:-:|:-:|:-:|
|000002d0|FFDB|DQTマーカ|
|00000320|FFDB|DQTマーカ|

DQTのマーカは「`FFDB`」です。「000002d0:」の行、「00000320:」行を見てみると「`FFDB`」マーカがあり、その後ろの2バイトはどちらとも「`0043`」でセグメント長が67バイトであることがわかります。続いて、その後ろには量子化テーブル精度と番号が4ビットで記載されていて、どちらも8bit精度で前者のテーブル番号が0、後者のテーブル番号が1であることがわかります。さらに、その後ろに64個の量子化係数が並んでいます。セグメント長が67バイト、セグメント長表現に2バイト、量子化テーブルで1バイト使っていることからもセグメント最後方の64ビットが量子化係数を表しているとわかります。(67バイト - (2バイト + 1バイト))


### DHTセグメント

|行数|マーカ|内容|
|:-:|:-:|:-:|
|00000370|FFC4|DHTマーカ|
|00000390|FFC4|DHTマーカ|
|000003b0|FFC4|DHTマーカ|
|000003d0|FFC4|DHTマーカ|
|000003f0|FFC4|DHTマーカ|
|00000410|FFC4|DHTマーカ|
|00000430|FFC4|DHTマーカ|
|00000460|FFC4|DHTマーカ|
|00000480|FFC4|DHTマーカ|

DHTのマーカは「`FFC4`」です。「`FFC4`」マーカの後ろの2バイトはセグメント長を表しています。その後ろにはDC成分またはAC成分を表す4ビットとハフマンテーブル番号を表す4ビットがあり、続いて量子化されたデータに配分するビット数定義があります。

### SOFセグメント

|行数|マーカ|内容|
|:-:|:-:|:-:|
|00000360|FFC2|SOFマーカ(ハフマン符号化プログレッシブ方式)|

SOFのマーカはDCT変換の方式によって異なりますが、今回はハフマン符号化プログレッシブ方式の「`FFC2`」が、「00000360」の行にあります。セグメント長が「`0011`」で17バイトです。YCbCr成分数が8で、画像縦、横ともに「`0010`」で16×16です。
ここで特に注目したいところは、最後の10バイト「03 01 11 00 02 11 01 03 11 01」です。成分数(YCbCr=3)、成分ID、水平サンプリング値・垂直サンプリング値、対応量子化テーブル番号を表しています。例えば、「01 11 00」は成分ID=1, 1:1サンプリング、量子化テーブル番号=00となります。
そこで、参照している量子化テーブル番号を見に行くと、テーブル番号00は小さい値(上位4ビットが0)が多く続いていますが、テーブル番号1は多くの部分を14が占めています。
このことから、Y成分には緩やかな量子化、CbCr成分にはある程度粗い量子化が行われていることがわかると思います。


### SOSセグメント

|行数|マーカ|内容|
|:-:|:-:|:-:|
|000003a0|FFDA|SOSマーカ|
|000003c0|FFDA|SOSマーカ|
|00000410|FFDA|SOSマーカ|
|00000430|FFDA|SOSマーカ|
|00000450|FFDA|SOSマーカ|
|00000450|FFDA|SOSマーカ|
|00000480|FFDA|SOSマーカ|
|000004a0|FFDA|SOSマーカ|
|000004c0|FFDA|SOSマーカ|


SOSのマーカは「`FFDA`」です。「`FFDA`」マーカの後ろの2バイトはセグメント長を表しています。
その後ろには成分数やDC・AC各成分に対応するハフマンテーブル番号が続いています。

これですべての必須セグメントが記録されていることがわかりました。続いて、バイナリエディタを使って画像を編集してみたいと思います。

## jpg構造の編集
jpgではイメージデータが圧縮されているため、今回は表示に関係ないセグメントを消して、表示が変わらないように画像データサイズを小さくしてみようと思います。
具体的にはCOMセグメントとAPP2セグメントを削除して、ファイルの容量を減らしつつ、画像はそのまま維持できるようにしてみます。

```makefile:1.2サンプル画像

  Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 	
00000000: FF D8 FF E0 00 10 4A 46 49 46 00 01 01 01 01 2C    .X.`..JFIF.....,
00000010: 01 2C 00 00 FF DB 00 43 00 03 02 02 03 02 02 03    .,...[.C........
00000020: 03 03 03 04 03 03 04 05 08 05 05 04 04 05 0A 07    ................
00000030: 07 06 08 0C 0A 0C 0C 0B 0A 0B 0B 0D 0E 12 10 0D    ................
00000040: 0E 11 0E 0B 0B 10 16 10 11 13 14 15 15 15 0C 0F    ................
00000050: 17 18 16 14 18 12 14 15 14 FF DB 00 43 01 03 04    ..........[.C...
00000060: 04 05 04 05 09 05 05 09 14 0D 0B 0D 14 14 14 14    ................
00000070: 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14    ................
00000080: 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14    ................
00000090: 14 14 14 14 14 14 14 14 14 14 14 14 14 14 FF C2    ...............B
000000a0: 00 11 08 00 10 00 10 03 01 11 00 02 11 01 03 11    ................
000000b0: 01 FF C4 00 15 00 01 01 00 00 00 00 00 00 00 00    ..D.............
000000c0: 00 00 00 00 00 00 00 08 FF C4 00 14 01 01 00 00    .........D......
000000d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF DA    ...............Z
000000e0: 00 0C 03 01 00 02 10 03 10 00 00 01 95 00 07 FF    ................
000000f0: C4 00 14 10 01 00 00 00 00 00 00 00 00 00 00 00    D...............
00000100: 00 00 00 00 20 FF DA 00 08 01 01 00 01 05 02 1F    ......Z.........
00000110: FF C4 00 14 11 01 00 00 00 00 00 00 00 00 00 00    .D..............
00000120: 00 00 00 00 00 20 FF DA 00 08 01 03 01 01 3F 01    .......Z......?.
00000130: 1F FF C4 00 14 11 01 00 00 00 00 00 00 00 00 00    ..D.............
00000140: 00 00 00 00 00 00 20 FF DA 00 08 01 02 01 01 3F    ........Z......?
00000150: 01 1F FF C4 00 14 10 01 00 00 00 00 00 00 00 00    ...D............
00000160: 00 00 00 00 00 00 00 20 FF DA 00 08 01 01 00 06    .........Z......
00000170: 3F 02 1F FF C4 00 14 10 01 00 00 00 00 00 00 00    ?...D...........
00000180: 00 00 00 00 00 00 00 00 20 FF DA 00 08 01 01 00    ..........Z.....
00000190: 01 3F 21 1F FF DA 00 0C 03 01 00 02 00 03 00 00    .?!..Z..........
000001a0: 00 10 92 4F FF C4 00 14 11 01 00 00 00 00 00 00    ...O.D..........
000001b0: 00 00 00 00 00 00 00 00 00 20 FF DA 00 08 01 03    ...........Z....
000001c0: 01 01 3F 10 1F FF C4 00 14 11 01 00 00 00 00 00    ..?...D.........
000001d0: 00 00 00 00 00 00 00 00 00 00 20 FF DA 00 08 01    ............Z...
000001e0: 02 01 01 3F 10 1F FF C4 00 14 10 01 00 00 00 00    ...?...D........
000001f0: 00 00 00 00 00 00 00 00 00 00 00 20 FF DA 00 08    .............Z..
00000200: 01 01 00 01 3F 10 1F FF D9                         ....?...Y

```

|1.2 サンプル画像|
|:-:|
|![](https://user-images.githubusercontent.com/74283288/113553784-3ca6e080-9633-11eb-9ffb-78ec235a86a5.png)|

COMセグメント(`FFFE`)とAPP2セグメント(`FFE2`)のセグメントを削除した結果が上の状態です。APP2セグメントで使用していた部分がイメージデータに比較して大きかったためファイルサイズを大幅に削減することができました。また、GIMPやその他の画像ビューアで正常に画像が表示できることを確認しました。

|画像名|ファイルサイズ|
|:-:|:-:|
|1.1 サンプル画像|1,234バイト|
|1.2 サンプル画像|521バイト|



# さいごに
今回は、「ヘッダ情報の確認とバイナリエディタからの画像編集(JPEG編)」について解説しました。jpg画像の構造を知ることができたと思います。

それでは引き続きよろしくお願いいたします。

目次は以下の記事からご覧になれます。

https://qiita.com/spc_ehara/items/e425b6dcc0398299c40d#%E7%9B%AE%E6%AC%A1