JPEG图片编码格式分析

图片展示需要BGR模式的三维向量,图片的编码是把BGR图片编码成文件能存储的格式,解码则反之。目前常见的编码为jpg、png、gif等。新兴的如webp、heic。

BMP

从简单入手,BMP是最简单的编码方式,甚至数十行代码就能完成编码和解码简单的程序。

bmp由文件头和位图信息头组成

import struct
import numpy as np
 
BITMAP_FILE_HEADER_FMT = '<2sI4xI'
BITMAP_FILE_HEADER_SIZE = struct.calcsize(BITMAP_FILE_HEADER_FMT)
BITMAP_INFO_FMT = '<I2i2H6I'
BITMAP_INFO_SIZE = struct.calcsize(BITMAP_INFO_FMT)
 
 
class BmpHeader:
    def __init__(self):
        self.bf_type = None
        self.bf_size = 0
        self.bf_off_bits = 0
        self.bi_size = 0
        self.bi_width = 0
        self.bi_height = 0
        self.bi_planes = 1 # 颜色平面数
        self.bi_bit_count = 0
        self.bi_compression = 0
        self.bi_size_image = 0
        self.bi_x_pels_per_meter = 0
        self.bi_y_pels_per_meter = 0
        self.bi_clr_used = 0
        self.bi_clr_important = 0
 
 
class BmpDecoder:
    def __init__(self, data):
        self.__header = BmpHeader()
        self.__data = data
 
    def read_header(self):
        if self.__header.bf_type is not None:
            return self.__header
        # bmp信息头
        self.__header.bf_type, self.__header.bf_size,\
            self.__header.bf_off_bits = struct.unpack_from(BITMAP_FILE_HEADER_FMT, self.__data)
        if self.__header.bf_type != b'BM':
            return None
        # 位图信息头
        self.__header.bi_size, self.__header.bi_width, self.__header.bi_height, self.__header.bi_planes,\
            self.__header.bi_bit_count, self.__header.bi_compression, self.__header.bi_size_image,\
            self.__header.bi_x_pels_per_meter, self.__header.bi_y_pels_per_meter, self.__header.bi_clr_used,\
            self.__header.bi_clr_important = struct.unpack_from(BITMAP_INFO_FMT, self.__data, BITMAP_FILE_HEADER_SIZE)
        return self.__header
 
    def read_data(self):
        header = self.read_header()
        if header is None:
            return None
        # 目前只写了解析常见的24位或32位位图
        if header.bi_bit_count != 24 and header.bi_bit_count != 32:
            return None
        # 目前只写了RGB模式
        if header.bi_compression != 0:
            return None
        offset = header.bf_off_bits
        channel = int(header.bi_bit_count / 8)
        img = np.zeros([header.bi_height, header.bi_width, channel], np.uint8)
        y_axis = range(header.bi_height - 1, -1, -1) if header.bi_height > 0 else range(0, header.bi_height)
        for y in y_axis:
            for x in range(0, header.bi_width):
                plex = np.array(struct.unpack_from('<' + str(channel) + 'B', self.__data, offset), np.int8)
                img[y][x] = plex
                offset += channel
        return img
 
 
class BmpEncoder:
    def __init__(self, img):
        self.__img = img
 
    def write_data(self):
        image_height, image_width, channel = self.__img.shape
        # 只支持RGB或者RGBA图片
        if channel != 3 and channel != 4:
            return False
        header = BmpHeader()
        header.bf_type = b'BM'
        header.bi_bit_count = channel * 8
        header.bi_width = image_width
        header.bi_height = image_height
        header.bi_size = BITMAP_INFO_SIZE
        header.bf_off_bits = header.bi_size + BITMAP_FILE_HEADER_SIZE
        header.bf_size = header.bf_off_bits + image_height * image_width * channel
        buffer = bytearray(header.bf_size)
        # bmp信息头
        struct.pack_into(BITMAP_FILE_HEADER_FMT, buffer, 0, header.bf_type, header.bf_size, header.bf_off_bits)
        # 位图信息头
        struct.pack_into(BITMAP_INFO_FMT, buffer, BITMAP_FILE_HEADER_SIZE, header.bi_size, header.bi_width, header.bi_height,
                         header.bi_planes, header.bi_bit_count, header.bi_compression, header.bi_size_image,
                         header.bi_x_pels_per_meter, header.bi_y_pels_per_meter, header.bi_clr_used,
                         header.bi_clr_important)
        # 位图,一般都是纵坐标倒序模式
        offset = header.bf_off_bits
        for y in range(header.bi_height - 1, -1, -1):
            for x in range(header.bi_width):
                struct.pack_into('<' + str(channel) + 'B', buffer, offset, *self.__img[y][x])
                offset += channel
        return buffer

bmp图片的纵坐标是反过来的,如下图所示:

 JPEG

JPEG是一种编码压缩方法,真正描述图片如何存储的是JFIF(JPEG File Interchange Format),但是普通交流中往往使用“JPEG文件”这种叫法。由于精力有限,只尝试了JPEG解码的步骤。

背景知识

DCT

离散余弦变换(discrete cosine transform),把信号从空域转换成频域,且具有较好的能量聚集。变换公式如下:

DCT:F(u, v) = \alpha(u) \alpha(v) \sum^{M-1}_{x=0} \sum^{N-1} f(x,y) cos(\frac{(2x+1)u\pi}{2M}) cos(\frac{(2y+1)v\pi}{2N})\,.,其中\alpha(u) = \begin{cases} \sqrt{\frac{1}{N}},\ u = 0 \\\\\sqrt{\frac{2}{N}},\ u\ne0\end{cases}

IDCT:f(x, y) = \alpha(u) \alpha(v) \sum^{M-1}_{u=0} \sum^{N-1} F(u, v) cos(\frac{(2x+1)u\pi}{2M}) cos(\frac{(2y+1)v\pi}{2N})\,.,其中\alpha(u) = \begin{cases} \sqrt{\frac{1}{N}},\ u = 0 \\\\\sqrt{\frac{2}{N}},\ u\ne0\end{cases}

可以阅读matlab的帮助文档离散余弦变换- MATLAB & Simulink- MathWorks 中国,或者一篇博客离散余弦变换(DCT)的来龙去脉_独孤呆博的博客-优快云博客_二维离散余弦变换

哈夫曼编码

根据符号出现概率,使用较短的编码更频繁出现的符号。更详细的可以阅读详细图解哈夫曼Huffman编码树_无鞋童鞋的博客-优快云博客_huffman编码树

色差信号

使用亮度和蓝色、红色的浓度偏移量描述图像信号的色彩空间,和RGB转换公式可阅读https://en.wikipedia.org/wiki/YCbCr。使用YCbCr是因为,人眼对于亮度对比的感知能力比色彩的感知能力要强,把亮度分量分离出来后,可以有针对性地使用不同的量化表、采样因子来达到不同的压缩率,且人眼感知不强。

读取JPEG文件Header

JPEG文件在制定规范时,定义文件是由marker和segment组成。marker都是以0xff开头,以非0x00结束。对应常用marker如下:

marker value description
SOI 0xFFD8 图像开始(Start Of Scan)
APP0 0xFFE0 存储图像参数
APP1 0xFFE1 EXIF
APP2 0xFFE2
APP12 0xFFEC 图片质量等信息
APP13 0xFFED phptoshop存储的信息Photoshop Tags
SOF0 0xFFC0 Start Of Frame,SOF0是baseline DCT
SOF2 0xFFC2 Start Of Frame,SOF2是progressive DCT
DHT 0xFFC4 Define Huffman Table,定义哈夫曼编码表,可以有多个,具体重建哈夫曼树方法见下
DQT 0xFFDB Define Quantization Table,定义量化表,可以有多个。量化表能影响图片的压缩质量
DRI 0xFFDD Define Restart Interval,重置DC信号的间隔(每解码指定次MCU就重置DC信号)
SOS 0xFFDA Start Of Scan
image data 如果有0xFF的数据,会使用0xFF00表示,解码的时候需要注意
EOI 0xFFD9 End Of Image

更多marker可以参考exiftool的文档JPEG Tags

APP0

field size(bytes) description
长度 2 包括这个字段为首的整个segment长度
标识符 5 图片编码方式,“JFIF\0"或者”JFXX\0“等,下面的字段均以JFIF为示例

JFIF

JFIF版本 2 第一个字节为主版本,第二个字节为次要版本(01 02表示1.02)
密度单位 1

下列像素密度字段的单位
00:无单位;width:height像素宽高比 = Xdensity:Ydensity
01:每英寸像素(2.54厘米)
02:每厘米像素

x方向密度 2 水平像素密度。不得为零。
y方向密度 2 垂直像素密度。不得为零。
缩略图宽度 1 嵌入的RGB缩略图的水平像素数。可以为零。
缩略图高度 1 嵌入的RGB缩略图的垂直像素数。可以为零。
<think>好的,我现在需要帮助用户了解如何解析JPEG图片格式的文件结构和解码流程。根据用户的问题,他们希望了解解析的方法或原理,可能需要从文件结构入手,然后逐步讲解解码步骤。同时,用户提供的引用材料中提到了一些关键点,比如熵编解码、存储结构、DCT和Huffman编码,以及image、frame、scan的概念区别。我需要将这些信息整合到回答中,确保符合系统级指令的要求,比如正确使用LaTeX格式,结构清晰,并生成相关问题。 首先,我需要确定JPEG文件的基本结构。根据引用[2],JPEG的存储结构包括多个标记段,比如SOI、APPn、DQT、SOF0、DHT、SOS和EOI。这些标记段是解析的关键,必须逐一解释每个部分的作用。例如,SOI表示文件开始,EOI结束,中间的部分包含量化表、霍夫曼表等必要信息。 接下来是解码流程。引用[1]提到熵编解码的流程,这可能涉及到霍夫曼解码。引用[3]指出现在主流的JPEG使用DCT和Huffman编码,因此解码过程应该包括反量化、反离散余弦变换(IDCT)等步骤。此外,颜色空间转换和采样处理也是重要环节,特别是YCbCr到RGB的转换,以及处理色度分量的降采样。 需要注意的是,用户提供的引用[4]解释了image、frame、scan的区别,特别是顺序模式和渐进式编码的不同。在解析时,如果是顺序模式,一个image对应一个frame,而分层模式可能有多个frame。这可能影响解码时的处理流程,尤其是在处理不同扫描次数时的数据重组。 在组织回答时,应该按照文件结构解析和解码流程分步骤说明。每个步骤需要简明扼要,同时引用相关的引用材料来支持说明。例如,在讲解霍夫曼解码时,可以引用[2]提到的码表存储问题,以及[3]中提到的Huffman编码的应用。 另外,要确保数学公式的正确使用。例如,DCT和IDCT的公式需要用$$...$$格式独立成段。例如,二维DCT公式应写成: $$F(u,v) = \frac{2}{N} C(u)C(v) \sum_{x=0}^{N-1} \sum_{y=0}^{N-1} f(x,y) \cos\left(\frac{(2x+1)u\pi}{2N}\right) \cos\left(\frac{(2y+1)v\pi}{2N}\right)$$ 同时,反量化步骤中的公式也需要正确呈现。 生成的相关问题需要与JPEG解析相关,可能涉及优化解码、错误处理、颜色空间转换等。例如,如何优化解码速度,如何处理损坏的JPEG文件,或者YCbCr和RGB转换的具体数学方法。 最后,要检查是否符合所有系统级指令,包括中文回答、LaTeX格式正确、引用标识自然添加,以及生成的相关问题是否合理。需要确保回答结构清晰,分步骤解析,同时引用提供的资料来增强可信度。</think>### JPEG图片格式解析方法及原理 #### 一、文件结构解析 JPEG文件由**标记段**和**压缩数据**组成,遵循`0xFF`开头的标记系统[^2]。核心结构如下: 1. **SOI(Start of Image)**:文件起始标记`0xFFD8` 2. **APPn(应用保留段)**:存储元数据(如相机参数) 3. **DQT(量化表)**:定义离散余弦变换(DCT)系数的量化步长,公式为: $$Q_{u,v} = \text{量化表}[u][v] \quad (0 \leq u,v < 8)$$ 4. **SOF0(Start of Frame)**:图像基本信息(如尺寸、颜色分量) 5. **DHT(霍夫曼表)**:存储熵编码的码表[^2] 6. **SOS(Start of Scan)**:压缩数据起始标记,包含扫描分量信息 7. **EOI(End of Image)**:文件结束标记`0xFFD9` #### 二、解码流程 1. **熵解码(Huffman/算术解码)** 根据DHT段中的霍夫曼码表,将压缩数据还原为量化后的DCT系数矩阵[^1]。 2. **反量化** 使用DQT段的量化表恢复DCT系数: $$F_{u,v} = \text{压缩数据}[u][v] \times Q_{u,v}$$ 3. **逆离散余弦变换(IDCT)** 将频域系数转换为空域像素值,公式为: $$f(x,y) = \frac{2}{N} \sum_{u=0}^{N-1} \sum_{v=0}^{N-1} C(u)C(v) F(u,v) \cos\left(\frac{(2x+1)u\pi}{2N}\right) \cos\left(\frac{(2y+1)v\pi}{2N}\right)$$ 其中$N=8$,$C(u)=1/\sqrt{2}$(当$u=0$时)否则$C(u)=1$[^3]。 4. **颜色空间转换与采样处理** - 将YCbCr转换为RGB: $$R = Y + 1.402(Cr-128) \\ G = Y - 0.344136(Cb-128) - 0.714136(Cr-128) \\ B = Y + 1.772(Cb-128)$$ - 处理色度分量的降采样(如4:2:0模式需插值恢复) 5. **图像重组** 按MCU(最小编码单元)将8x8块拼接为完整图像[^4]。
评论 6
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值