JPEG 인코딩 데이터에서 배열로의 디코딩 과정

np.array(pic) 함수가 JPEG 인코딩된 데이터를 배열로 변환하는 과정은 여러 단계의 복잡한 디코딩 과정을 거칩니다. 이 과정에서 PIL(Pillow)이 내부적으로 libjpeg 라이브러리를 사용하여 JPEG의 압축 알고리즘을 역순으로 실행합니다[1][2][3].

PIL과 libjpeg의 역할

PIL/Pillow는 JPEG 디코딩을 위해 libjpeg 또는 libjpeg-turbo 라이브러리에 의존합니다[2][4][5]. 이 라이브러리들은 C로 구현된 고성능 JPEG 디코더로, PIL이 JPEG 파일을 읽을 때 백그라운드에서 실제 디코딩 작업을 수행합니다[3][6][7].

JPEG 디코딩의 세부 단계

1. 파일 구조 분석 및 마커 식별

JPEG 파일의 디코딩은 먼저 파일 내의 다양한 마커들을 식별하는 것부터 시작됩니다[1][8]. 주요 마커들은 다음과 같습니다:

  • 0xFF D8: 이미지 시작 마커
  • 0xFF DB: 양자화 테이블 정의
  • 0xFF C4: 허프만 테이블 정의
  • 0xFF C0: 프레임 시작 (이미지 크기, 컴포넌트 정보)
  • 0xFF DA: 스캔 시작 (실제 이미지 데이터)
  • 0xFF D9: 이미지 종료 마커

2. 허프만 테이블 추출 및 복원

JPEG 파일에는 최대 4개의 허프만 테이블이 포함되어 있습니다[1][9][10]. 이 테이블들은 압축된 데이터를 디코딩하는 데 필요한 핵심 정보입니다:

  • DC 테이블: 루미넌스(Y)와 크로미넌스(Cb, Cr) 각각에 대한 DC 계수용
  • AC 테이블: 루미넌스(Y)와 크로미넌스(Cb, Cr) 각각에 대한 AC 계수용

허프만 디코딩은 접두사 자유(prefix-free) 특성을 가진 가변 길이 코딩 방식으로, 빈도가 높은 값에는 짧은 비트를, 빈도가 낮은 값에는 긴 비트를 할당하여 압축 효율을 높입니다[9][11][12].

3. 양자화 테이블 복원

양자화 테이블은 JPEG의 손실 압축에서 핵심적인 역할을 하는 8×8 행렬입니다[1][13][14]. 일반적으로 루미넌스용과 크로미넌스용 2개의 테이블이 있으며, 디코딩 과정에서 DCT 계수에 곱해져서 역양자화(dequantization)를 수행합니다[15][16][13].

4. 이미지 스캔 데이터 처리

실제 이미지 데이터가 포함된 스캔 영역에서는 다음과 같은 복잡한 과정이 진행됩니다[1][8]:

4.1 바이트 스터핑 제거

JPEG 인코더가 삽입한 바이트 스터핑(byte stuffing)을 제거합니다. 스캔 데이터 내에서 0xFF 다음에 오는 0x00을 제거하는 과정입니다[1][8].

4.2 비트스트림 변환 및 허프만 디코딩

압축된 데이터를 비트스트림으로 변환한 후, 앞서 추출한 허프만 테이블을 사용하여 DCT 계수를 디코딩합니다[1][9][8].

4.3 8×8 블록 단위 처리

JPEG는 이미지를 8×8 픽셀 블록(MCU, Minimum Coding Units)으로 나누어 처리합니다[1][17][13]. 각 블록에 대해 다음 과정을 수행합니다:

  • DC 계수 델타 디코딩: DC 계수는 이전 블록과의 차이값으로 인코딩되어 있어 이를 복원합니다[1][8]
  • AC 계수 런-렝스 디코딩: 연속된 0 값들을 압축한 런-렝스 인코딩을 해제합니다[1][18][8]
  • 지그재그 스캔 순서 복원: 1차원으로 배열된 계수들을 8×8 행렬로 재배열합니다[1][18][8]

5. 역양자화 및 역 이산 코사인 변환

디코딩된 각 8×8 블록에 대해 다음 과정을 수행합니다[1][19][13]:

  1. 역양자화: DCT 계수에 해당 양자화 테이블 값을 곱하여 원래 크기로 복원합니다[15][16][13]
  2. 역 이산 코사인 변환(IDCT): 주파수 영역의 DCT 계수를 공간 영역의 픽셀 값으로 변환합니다[17][19][14]

IDCT는 다음 수식으로 표현됩니다[17][14]:

$$ f(x,y) = \frac{1}{4} \sum_{u=0}^{7} \sum_{v=0}^{7} C(u)C(v) F(u,v) \cos\left(\frac{(2x+1)u\pi}{16}\right) \cos\left(\frac{(2y+1)v\pi}{16}\right) $$

여기서 $$C(u) = \frac{1}{\sqrt{2}}$$ (u=0일 때), $$C(u) = 1$$ (u≠0일 때)입니다[17][14].

6. 색공간 변환

JPEG는 일반적으로 YCbCr 색공간을 사용하므로, 최종적으로 RGB로 변환해야 합니다[1][8]:

  • Y: 루미넌스(밝기) 성분
  • Cb: 청색 크로미넌스 성분
  • Cr: 적색 크로미넌스 성분

YCbCr에서 RGB로의 변환 공식은 다음과 같습니다[1][8]:

$$ R = Y + 1.402 \times (Cr – 128) $$
$$ G = Y – 0.344 \times (Cb – 128) – 0.714 \times (Cr – 128) $$
$$ B = Y + 1.772 \times (Cb – 128) $$

7. 값 범위 정규화 및 클램핑

변환된 RGB 값들을 범위로 제한하는 클램핑(clamping) 과정을 거칩니다[1][8]. 이는 디코딩 과정에서 발생할 수 있는 범위 초과 값들을 처리하기 위함입니다.

libjpeg의 구체적인 구현

실제 PIL에서 사용하는 libjpeg의 디코딩 과정은 다음과 같은 단계로 구성됩니다[3][20][21]:

  1. JPEG 압축 해제 객체 생성: jpeg_create_decompress()
  2. 데이터 소스 지정: jpeg_stdio_src()
  3. JPEG 헤더 읽기: jpeg_read_header()
  4. 압축 해제 시작: jpeg_start_decompress()
  5. 스캔라인별 읽기: jpeg_read_scanlines()
  6. 압축 해제 완료: jpeg_finish_decompress()

NumPy 배열로의 최종 변환

모든 디코딩 과정이 완료되면, PIL Image 객체의 픽셀 데이터가 메모리에 연속적으로 저장됩니다. np.array(pic) 호출 시 NumPy는 PIL Image의 __array_interface__ 속성을 통해 이 메모리 영역에 접근하여 배열을 생성합니다.

이 과정에서 최종적으로 생성되는 NumPy 배열은 일반적으로 (height, width, channels) 형태의 3차원 배열이 되며, 각 픽셀은 0-255 범위의 uint8 타입으로 표현됩니다[1][8].

전체 과정을 통해 JPEG의 복잡한 압축 알고리즘이 완전히 역순으로 실행되어, 원본에 가까운 픽셀 데이터를 복원하게 됩니다. 이는 주파수 영역에서의 압축과 양자화로 인한 일부 정보 손실을 감안하더라도, 사람의 시각적 인지에는 거의 차이가 없는 수준의 이미지 품질을 제공합니다[17][22][19].

출처
[1] Understanding and Decoding a JPEG Image using Python https://yasoob.me/posts/understanding-and-writing-jpeg-decoder-in-python/
[2] Image file formats – Pillow (PIL Fork) 11.2.1 documentation https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
[3] [c++] libjpeg-turbo 소스코드 분석 https://colinch4.github.io/2023-12-20/09-30-29-523949-libjpeg-turbo-%EC%86%8C%EC%8A%A4%EC%BD%94%EB%93%9C-%EB%B6%84%EC%84%9D/
[4] Python Image Library fails with message “decoder JPEG not available” https://stackoverflow.com/questions/8915296/python-image-library-fails-with-message-decoder-jpeg-not-available-pil
[5] pydicom/pylibjpeg: A Python framework for decoding JPEG … – GitHub https://github.com/pydicom/pylibjpeg
[6] pylibjpeg-libjpeg/README.md at main – GitHub https://github.com/pydicom/pylibjpeg-libjpeg/blob/master/README.md
[7] pylibjpeg-libjpeg – PyPI https://pypi.org/project/pylibjpeg-libjpeg/
[8] Pillow/src/libImaging/JpegDecode.c at main – GitHub https://github.com/python-pillow/Pillow/blob/main/src/libImaging/JpegDecode.c
[9] JPEG Series, Part II: Huffman Coding – Alex Dowad Computes https://alexdowad.github.io/huffman-coding/
[10] JPEG – Idea and Practice/The Huffman coding – Wikibooks https://en.wikibooks.org/wiki/JPEG_-_Idea_and_Practice/The_Huffman_coding
[11] huffman table in jpeg decoding – Stack Overflow https://stackoverflow.com/questions/77391457/huffman-table-in-jpeg-decoding
[12] Huffman Encoding: The Magic behind JPEG https://www.youtube.com/watch?v=Au8PZbn6B8M
[13] Lecture_6 https://cseweb.ucsd.edu/classes/sp02/cse126/Lecture_6.html
[14] IDCTN – N-D inverse discrete cosine transform. – BioméCardio https://www.biomecardio.com/matlab/idctn_doc.html
[15] [PDF] EEL4783 Project 2 – JPEG Huffman Decoder – ucf ece https://www.ece.ucf.edu/~mingjie/EEL4783_2012/PA2.pdf
[16] JPEG Algorithm Adjustment for Different Quality Metrics http://wseas.us/e-library/conferences/2012/Sliema/SENVIS/SENVIS-39.pdf
[17] [컴퓨터비전] Image Transformation2 https://velog.io/@nakyeongg/%EC%BB%B4%ED%93%A8%ED%84%B0%EB%B9%84%EC%A0%84-Image-Transformation2
[18] JPEG Compression http://simia.net/codenetproblems/p00593.html
[19] A Machine Learning Approach to Optimal Inverse https://arxiv.org/pdf/2102.00502v1.pdf
[20] PIL and JPEG library on Windows https://stackoverflow.com/questions/10453858/pil-and-jpeg-library-on-windows/10454174
[21] Windows에서 jpeglib 사용하기 – decoding routine (Visual Studio C++) https://blog.naver.com/cyberyanne/40099947301
[22] Joint optimization of run-length coding, Huffman coding, and quantization table with complete baseline JPEG decoder compatibility – PubMed https://pubmed.ncbi.nlm.nih.gov/19095519/
[23] decoded jpeg images are different using different versions of Pillow https://github.com/python-pillow/Pillow/issues/2316
[24] Python Imaging Library – Wikipedia https://en.wikipedia.org/wiki/Python_Imaging_Library
[25] JPEG https://python.developpez.com/cours/pilhandbook/php/format-jpeg.php
[26] PIL /JPEG Library: “decoder jpeg not available” – Stack Overflow https://stackoverflow.com/questions/4632261/pil-jpeg-library-decoder-jpeg-not-available
[27] pylibjpeg on Pypi https://libraries.io/pypi/pylibjpeg
[28] A JPEG decoder made in C – GitHub https://github.com/cefqrn/jpeg-decoder
[29] NanoJPEG: a compact JPEG decoder – KeyJ’s Blog – emphy.de https://keyj.emphy.de/nanojpeg/
[30] ImageFile Module https://pillow.readthedocs.io/en/stable/reference/ImageFile.html
[31] Read/write JPEG image with libjpeg https://gist.github.com/mts0629/f64e7646f1db8335c80f131be8359044
[32] Need help in reading JPEG file using libjpeg https://stackoverflow.com/questions/5616216/need-help-in-reading-jpeg-file-using-libjpeg
[33] 18 2013-D-CT-D-1-029(1003-1008).hwp https://koreascience.kr/article/JAKO201320361633057.pdf
[34] [PDF] arXiv:2008.00605v1 [eess.IV] 3 Aug 2020 https://arxiv.org/pdf/2008.00605.pdf
[35] Zig zag run length coding for image compression https://irp.cdn-website.com/873f2dce/files/uploaded/fovefuvoxovepaleziso.pdf
[36] PIL(Python Image Library) 설치하기 – ASH84 https://ash84.io/2014/02/13/python-pilpython-image-library/
[37] [PDF] JPEG Quantization Table Optimization by Guided Fireworks Algorithm https://mail.ipb.ac.rs/~rakaj/home/fajpg.pdf

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다