JPEG은 이미지를 압축 저장하는 방식 중 하나로 오랫동안 사용된 이미지 압축 포맷이다.
Joint Photographic Experts Group의 약자로 이 포맷을 만든 그룹의 이름에서 따왔다.
기본적으로 JPEG 압축 방식은 lossy compression이다.
이 말인 즉슨, 원본 데이터를 보존하지 못한다는 뜻인데 결국 이를 희생해서 압축률을 높인다고 볼 수 있겠다.
이와 관련된 몇 가지 특성들을 간단히 짚고 넘어가자.
JPEG 파일은 본래 JPEG Interchange Format (JIF) 형태로 저장되었다.
하지만 이는 요즘 거의 사용되지 않고, JIF를 확장한 여러 변종들로 많이 사용된다. (대표적으로 JFIF나 Exif가 있다.)
이러한 확장 포맷에서는 JPEG 구조 상의 APP n
marker segment를 활용하여 application-specific 데이터를 담는다.
(JFIF의 경우 APP 0
, Exif의 경우 APP 1
을 사용함)
JFIF는 JIF의 확장 버전으로, 몇 가지 제약사항을 추가하면서 포맷을 가볍게 하였으며, 다른 metadata도 함께 저장할 수 있게 만들었다.
이 포맷은 여러 플랫폼, 애플리케이션에 호환되도록 만든 minimal file format이라 할 수 있겠다.
Exif는 image capture device에서 주로 사용되는 포맷이다.
정확한 이유는 모르겠으나 카메라 산업계에서 이를 표준으로 사용하고 있고, 디지털 카메라, 스캐너 등 대부분에서 Exif 포맷으로 저장하고 있다.
(아마 Exif의 특성상 여러 tag metadata를 저장할 수 있으니 그런 것 아닌가 싶다. 사진의 정보, 썸네일 등등)
확실히 각 포맷의 사용처가 다르다.
Exif는 앞에서 말했듯 카메라와 같은 기기에서 JPEG 파일을 저장할 때 사용하고, JFIF는 반대로 image editing software에서 많이 사용한다.
(Exif에서는 color profile을 지원하지 않기 때문이라고 함)
그렇다면 이 둘은 호환이 안 되는 것일까? 엄밀히 말하면 호환되지 않는다.
보통 각 방식은 자신의 marker segment가 첫번째로 등장하도록 encoding 하는데, 실제로는 JFIF와 Exif는 이를 유연하게 조정하여 호환되도록 한다.
JFIF marker segment를 먼저 넣고, 그 뒤에 Exif header가 뒤따르게 하는 것이다.
이렇게 하면 오래된 decoder는 JFIF segment를 보고 이미지를 읽을 수 있고, 최근의 decoder는 Exif segment까지 보고 더 풍부한 정보를 제공할 수 있게 된다.
JPEG 이미지는 여러 segment로 구성되는데, 각 segment 처음에는 marker가 존재한다.
모든 marker는 0xFF로 시작하며, 그 뒤에 1 byte의 marker 정보를 담는다.
그리고 그 뒤에는 payload data의 length, payload data가 나오며, payload 없이 앞선 2 bytes만 가지고 있는 marker도 있다.
몇몇 marker들은 entropy-coded data로 뒤따르기도 한다.
아래는 흔히 사용되는 JPEG marker에 대한 테이블이다. (중요해 보이는 marker는 bold했다.)
Short name | Bytes | Payload | Full name | 설명 |
---|---|---|---|---|
SOI | 0xFF, 0xD8 | none | Start Of Image | 이미지의 시작 |
APP n | 0xFF, 0xEn | variable size | Application-specific | JFIF, Exif 등의 application-specific 데이터를 지니며, 보통 자신의 APP marker가 처음에 등장함 |
SOF0 | 0xFF, 0xC0 | variable size | Start Of Frame (baseline DCT) | Baseline DCT 기반의 JPEG임을 명시, 이미지 정보 명시 |
SOF2 | 0xFF, 0xC2 | variable size | Start Of Frame (progressive DCT) | Progressive DCT 기반의 JPEG임을 명시, 이미지 정보 명시 |
DHT | 0xFF, 0xC4 | variable size | Define Huffman Table(s) | Huffman table(s)을 지님 |
DQT | 0xFF, 0xDB | variable size | Define Quantization Table(s) | Quantization table(s)을 지님 |
DRI | 0xFF, 0xDD | 4 bytes | Define Restart Interval | RST marker 간의 간격을 명시 |
RST n | 0xFF, 0xDn (n=0..7) | none | Restart | N 개의 macroblock 마다 삽입되며, 이렇게 구분된 단위(runs of macroblocks)로 parallel decoding이 가능해짐 (N 은 DRI에서 명시된 값) |
COM | 0xFF, 0xFE | variable size | Comment | 텍스트 코멘트를 지님 |
SOS | 0xFF, 0xDA | variable size | Start Of Scan | 이미지 스캔의 시작을 명시, 이 다음에는 압축된 이미지 데이터가 이어짐 |
EOI | 0xFF, 0xD9 | none | End Of Image | 이미지의 끝 |
한 가지 주목할 점은, Restart marker를 경계로 하여 각 bitstream들은 synchronized 되어 있으며, 이는 bitstream error에 대한 복구를 가능하게 해줄 뿐더러 parallel decoding이 가능하게 한다는 점이다.
JPEG segment marker에 대해 더 자세히 알고 싶다면 글의 맨 아래에 reference된 페이지들을 참고하면 좋을 것이다.
이번엔 JPEG에 대해 알아봤다. 다음은 PNG, GIF 이미지 포맷이 될 예정이다.
JPEG의 압축 알고리즘은 위키에도 자세히 설명되어 있으니, 후에 시간될 때 encoder/decoder를 직접 구현해봐도 좋은 경험이 될 것 같다.
(그런데 아마 JPEG 압축 알고리즘에 대해서만 꽤나 공부를 해야하지 않을까... 싶다)
그리고 공부하면서 자주 등장한 DCT, TIFF, entropy coding, Huffman encoding, quantization 등에 대해서도 간단히 정리하면 좋을 것이다.