分类 默认分类 下的文章

 

JPEG文件编/解码详解

cat_ng 猫猫

JPEGJoint Photographic Experts Group)是联合图像专家小组的英文缩写。它由国际电话与电报咨询委员会CCITTThe International Telegraph and Telephone Consultative Committee)与国际标准化组织ISO1986年联合成立的一个小组,负责制定静态数字图像的编码标准。

小组一直致力于标准化工作,开发研制出连续色调、多级灰度、静止图像的数字图像压缩编码方法,即JPEG算法。JPEG算法被确定为国际通用标准,其适用范围广泛,除用于静态图像编码外,还推广到电视图像序列的帧内图像压缩。而用JPEG算法压缩出来的静态图片文件称为JPEG文件,扩展名通常为*.jpg*.jpe*.jpeg

JPEG专家组开发了两种基本的压缩算法、两种数据编码方法、四种编码模式。具体如下:

压缩算法:

l 有损的离散余弦变换(Discrete Cosine TransformDCT);

l 无损的预测技术压缩。

数据编码方法:

l 哈夫曼编码;

l 算术编码;

编码模式:

l 基于DCT顺序模式:编/解码通过一次扫描完成;

l 基于DCT递进模式:编/解码需要多次扫描完成,扫描效果从粗糙到精细,逐级递进;

l 无损模式:基于DPCM,保证解码后完全精确恢复到原图像采样值;

l 层次模式:图像在多个空间多种分辨率进行编码,可以根据需要只对低分辨率数据作解码,放弃高分辨率信息。

在实际应用中,JPEG图像使用的是离散余弦变换、哈夫曼编码、顺序模式。

 

JPEG压缩编码算法的主要计算步骤如下:

(0) 8*8分块。

(1) 正向离散余弦变换(FDCT)

(2) 量化(quantization)

(3) Z字形编码(zigzag scan)

(4) 使用差分脉冲编码调制(DPCM)对直流系数(DC)进行编码。

(5) 使用行程长度编码(RLE)对交流系数(AC)进行编码。

(6) 熵编码。

 

笔者在实践过程中查阅了大量的资料,发现大多数书籍资料和网上资料都是从编码角度分析JPEG的编/解码方式,并且都只是介绍编码过程中的主要方法。所以,本文从解码角度详细分析JPEG的编/解码过程,并且加入许多笔者实践过程中遇到的问题和解决方法,希望从另一个角度说明问题,以更好帮助读者结合其他资料解决问题。

不过,介绍解码过程之前,首先要了解JPEG文件中数据的存储格式。


一、JPEG文件格式介绍

JPEG文件使用的数据存储方式有多种。最常用的格式称为JPEG文件交换格式(JPEG File Interchange FormatJFIF)。而JPEG文件大体上可以分成两个部分:标记码(Tag)和压缩数据。

标记码由两个字节构成,其前一个字节是固定值0xFF,后一个字节则根据不同意义有不同数值。在每个标记码之前还可以添加数目不限的无意义的0xFF填充,也就说连续的多个0xFF可以被理解为一个0xFF,并表示一个标记码的开始。而在一个完整的两字节的标记码后,就是该标记码对应的压缩数据流,记录了关于文件的诸种信息。

常用的标记有SOIAPP0DQTSOF0DHTDRISOSEOI

注意,SOI等都是标记的名称。在文件中,标记码是以标记代码形式出现。例如SOI的标记代码为0xFFD8,即在JPEG文件中的如果出现数据0xFFD8,则表示此处为一个SOI标记。

本文附录列出一张完整的JPEG定义的标记表,供读者查阅。这里仅列出几个常用标记的标记代码、占用字节长度和表示的意义。

 

l         SOIStart of Image,图像开始

u  标记代码                                 2字节     固定值0xFFD8

 

l       APP0Application,应用程序保留标记0

u  标记代码                                 2字节     固定值0xFFE0

u  包含9个具体字段:
 
数据长度                         2字节     ~9个字段的总长度
                                                           
即不包括标记代码,但包括本字段
 
标识符                             5字节    固定值0x4A46494600,即字符串“JFIF0”
 
版本号                             2字节    一般是0x0102,表示JFIF的版本号
1.2
                                                           
可能会有其他数值代表其他版本

 
XY的密度单位           1字节     只有三个值可选
                                                            0
:无单位;1:点数/英寸;2:点数/厘米
 
X方向像素密度               2字节     取值范围未知
 
Y方向像素密度               2字节     取值范围未知  
 
缩略图水平像素数目        1字节     取值范围未知

 
缩略图垂直像素数目        1字节     取值范围未知
 
缩略图RGB位图             长度可能是3的倍数           缩略图RGB位图数据

 

本标记段可以包含图像的一个微缩版本,存为24位的RGB像素。如果没有微缩图像(这种情况更常见),则字段缩略图水平像素数目和字段缩略图垂直像素数目的值均为0

 

l       APPnApplication,应用程序保留标记n,其中n=115(任选)

u  标记代码                                 2字节     固定值0xFFE1~0xFFF

u  包含2个具体字段:
 
数据长度                         2字节     ~2个字段的总长度
                                                           
即不包括标记代码,但包括本字段
 
 详细信息            数据长度-2字节   内容不定
                                                    

例如,Adobe Photoshop生成的JPEG图像中就用了APP1APP13两个标记段分别存储了一幅图像的副本。

 

l       DQTDefine Quantization Table,定义量化表

u  标记代码                          2字节            固定值0xFFDB

u  包含9个具体字段:
 
数据长度                  2字节            字段①和多个字段②的总长度
                                                           
即不包括标记代码,但包括本字段
 
 量化表        数据长度-2字节

a)         精度及量化表ID   1字节            4位:精度,只有两个可选值
                                                              0
8位;116
                                               
4位:量化表ID,取值范围为03

b)        表项       (64×(精度+1))字节              例如8位精度的量化表
                                               
其表项长度为64×0+1=64字节

 

本标记段中,字段②可以重复出现,表示多个量化表,但最多只能出现4次。

 

l       SOF0Start of Frame,帧图像开始

u  标记代码                   2字节     固定值0xFFC0

u  包含9个具体字段:
 
数据长度           2字节     ~六个字段的总长度
                                             
即不包括标记代码,但包括本字段
 
精度                 1字节     每个数据样本的位数
                                             
通常是8位,一般软件都不支持 12位和16
 
图像高度           2字节     图像高度(单位:像素),如果不支持 DNL 就必须 >0
 
图像宽度           2字节     图像宽度(单位:像素),如果不支持 DNL 就必须
>0
 
颜色分量数        1字节     只有3个数值可选

                                              1
:灰度图;3YCrCbYIQ4CMYK
                                             
JFIF中使用YCrCb,故这里颜色分量数恒为
3
 
颜色分量信息      颜色分量数×3字节(通常为9字节)

a)         颜色分量ID                 1字节    

b)        水平/垂直采样因子      1字节            4位:水平采样因子
                                                      
4位:垂直采样因子
                                                      
(曾经看到某资料把这两者调转了)

c)        量化表                         1字节            当前分量使用的量化表的ID

本标记段中,字段⑥应该重复出现,有多少个颜色分量(字段⑤),就出现多少次(一般为3次)。

 

l       DHTDifine Huffman Table,定义哈夫曼表

u  标记代码                                 2字节            固定值0xFFC4

u  包含2个具体字段:
 
数据长度                             2字节            字段①和多个字段②的总长度
                                                                  
即不包括标记代码,但包括本字段
 
 哈夫曼表              数据长度-2字节

a)       ID和表类型            1字节            4位:类型,只有两个值可选
                                                                     0
DC直流;1AC交流
                                                       
4位:哈夫曼表ID
                                                                    
注意,DC表和AC表分开编码

b)      不同位数的码字数量    16字节

c)      编码内容       16个不同位数的码字数量之和(字节)

本标记段中,字段②可以重复出现(一般4次),也可以致出现1次。例如,Adobe Photoshop 生成的JPEG图片文件中只有1DHT标记段,里边包含了4个哈夫曼表;而Macromedia Fireworks生成的JPEG图片文件则有4DHT标记段,每个DHT标记段只有一个哈夫曼表。

 

l       DRIDefine Restart Interval定义差分编码累计复位的间隔

u  标记代码                                 2字节     固定值0xFFDD

u  包含2个具体字段:
 
数据长度                             2字节     固定值0x0004~两个字段的总长度
                                                           
即不包括标记代码,但包括本字段
 
MCU块的单元中的重新开始间隔
                                              2
字节     设其值为n,则表示每nMCU块就有一个
                                                           RSTn
标记。第一个标记是RST0,第二个是
                                                            RST1
等,RST7后再从RST0重复。

 

如果没有本标记段,或间隔值为0时,就表示不存在重开始间隔和标记RST

 

l       SOSStart of Scan,扫描开始 12字节

u  标记代码                          2字节     固定值0xFFDA

u  包含2个具体字段:
 
数据长度                      2字节     ~两个字段的总长度
                                                    
即不包括标记代码,但包括本字段
 
颜色分量数                 1字节     应该和SOF中的字段⑤的值相同,即:
                                                     1
:灰度图是;3 YCrCbYIQ4CMYK

                                                         JFIF中使用YCrCb,故这里颜色分量数恒为3
   
颜色分量信息

        a)
颜色分量ID           1字节
        b)
直流/交流系数表号 1字节     4位:直流分量使用的哈夫曼树编号
                                                       
4位:交流分量使用的哈夫曼树编号

  压缩图像数据
        a)
谱选择开始                     1字节     固定值0x00
        b)
谱选择结束                     1字节     固定值
0x3F
        c)
谱选择                            1字节     在基本JPEG中总为00

 

本标记段中,字段③应该重复出现,有多少个颜色分量(字段②),就出现多少次(一般为3次)。本段结束后,紧接着就是真正的图像信息了。图像信息直至遇到一个标记代码就自动结束,一般就是以EOI标记表示结束。

 

l        EOIEnd of Image,图像结束 2字节

u  标记代码                   2字节     固定值0xFFD9

 

这里补充说明一下,由于在JPEG文件中0xFF具有标志性的意思,所以在压缩数据流(真正的图像信息)中出现0xFF,就需要作特别处理。具体方法是,在数据0xFF后添加一个没有意义的0x00。换句话说,如果在图像数据流中遇到0xFF,应该检测其紧接着的字符,如果是

10x00,则表示0xFF是图像流的组成部分,需要进行译码;

20xD9,则与0xFF组成标记EOI,则图像流结束,同时图像文件结束;

30xD0~0xD7,则组成RSTn标记,则要忽视整个RSTn标记,即不对当前0xFF和紧接的0xDn两个字节进行译码,并按RST标记的规则调整译码变量;

30xFF,则忽视当前0xFF,对后一个0xFF再作判断;

4)其他数值,则忽视当前0xFF,并保留紧接的此数值用于译码。

 

二、        JPEG解码过程详解

 

下面来详细讲述JPEG文件的解码过程。

 

1.读入文件的相关信息

按照上述的JPEG文件数据存储方式,把要解码的文件的相关信息一一读出,为接下来的解码工作做好准备。参考方法是,设计一系列的结构体对应各个标记,并存储标记内表示的信息。其中图像长宽、多个量化表和哈夫曼表、水平/垂直采样因子等多项信息比较重要。以下给出读取过程中的两个问题。

 

1)整个文件的大体结构

JFIF格式的JPEG文件(*.jpg)的一般顺序为:

SOI(0xFFD8)

APP0(0xFFE0)

[APPn(0xFFEn)]可选

DQT(0xFFDB)

SOF0(0xFFC0)

DHT(0xFFC4)

SOS(0xFFDA)

压缩数据

EOI(0xFFD9)

 

2)字的高低位问题

JPEG文件格式中,一个字(16位)的存储使用的是 Motorola 格式, 而不是 Intel 格式。也就是说, 一个字的高字节(高8位)在数据流的前面, 低字节(低8位)在数据流的后面,与平时习惯的Intel格式不一样。.

 

3)读出哈夫曼表数据

a)理论说明

在标记段DHT内,包含了一个或者多个的哈夫曼表。对于单一个哈夫曼表,应该包括了三部分:

l 哈夫曼表ID和表类型
这个字节的值为一般只有四个0x000x010x100x11
0x00
表示DC直流0号表;
0x01
表示DC直流1号表;
0x10
表示AC交流0号表;
0x11
表示AC交流1号表。

l 不同位数的码字数量

JPEG文件的哈夫曼编码只能是1~16位。这个字段的16个字节分别表示1~16位的编码码字在哈夫曼树中的个数。

l 编码内容

这个字段记录了哈夫曼树中各个叶子结点的权。所以,上一字段(不同位数的码字数量)的16个数值之和就应该是本字段的长度,也就是哈夫曼树中叶子结点个数。

 

b)举例说明

以下面一段哈夫曼表数据举例说明(数据全部以16进制表示):

11  00  02  02  00  05  01  06  01  00  00  00  00  00  00  00  00
00  01  11  02  21  03  31  41  12  51   61 71 81 91 22 13  32

红色部分(第1字节)为哈夫曼表ID和表类型,其值0x11表示此部分数据描述的是AC交流1号表。

蓝色部分2~17字节)为不同位数的码字的数量。这16个数值实际意义为:没有1位和4位的哈夫曼码字;2位和3位的码字各有2个;5位码字有5个;6位和8位码字各有1个;7位码字各有6个;没有9位或以上的码字。

绿色部分18~34字节)为编码内容。由蓝色部分数据知道,此哈夫曼树有0+2+2+0+5+1+6+1=17个叶子结点,即本字段应该有17个字节。这段数据表示17个叶子结点按从小到大排列,其权值依次为011122133141……

 

4)建立哈夫曼树

a)理论说明

在读出哈夫曼表的数据后,就要建立哈夫曼树。具体方法为:

1)第一个码字必定为0
如果第一个码字位数为1,则码字为0
如果第一个码字位数为2,则码字为00
如此类推。

2)从第二个码字开始,
如果它和它前面的码字位数相同,则当前码字为它前面的码字加1
如果它的位数比它前面的码字位数大,则当前码字是前面的码字加1后再在后边添若干个0,直至满足位数长度为止。

b)举例说明

继续以上边的例子说明问题。

n           由于没有1位的码字,所以第一个码字的位数为2,即码字为00

n           由于2位的码字有两个,所以第二个码字位数仍为2,即码字为00+1=01

n           第三个码字为3位,比第二个码字长1位,所以第三个码字为:01+1=10,然后再添1个“0”,得100

n           ……

如此类推,最后得到这个哈夫曼树如下:

序号

码字长度

码字

权值

1

2

00

0x00

2

2

01

0x01

3

3

100

0x11

4

3

101

0x02

5

5

11000

0x21

6

5

11001

0x03

7

5

11010

0x31

8

5

11011

0x41

9

5

11100

0x12

10

6

111010

0x51

11

7

1110110

0x61

12

7

1110111

0x71

13

7

1111000

0x81

14

7

1111001

0x91

15

7

1111010

0x22

16

7

1111011

0x13

17

8

11111000

0x32

 

特别注意的是,如果中间有某个位数的码字缺失,例如没有4位码字,则应该在3位码字加1后,添加“00”补足5位,形成下一个5位码字。

 

在准备好所有的图片信息后,就可以对图片数据进行解码了。

 

2初步了解图像数据流的结构

1)理论说明

分析图像数据流的结构,笔者准备以一个从宏观到微观的顺序为读者详细剖析,即:

 

数据流à最小编码单元à数据单元与颜色分量。

 

a)         在图片像素数据流中,信息可以被分为一段接一段的最小编码单元(Minimum Coded UnitMCU)数据流。所谓MCU,是图像中一个正方矩阵像素的数据。

矩阵的大小是这样确定的:

查阅标记SOF0,可以得到图像不同颜色分量的采样因子,即YCrCb三个分量各自的水平采样因子和垂直采样因子。大多图片的采样因子为411111。其中,411即(2*2):(1*1):(1*1));111即(1*1):(1*1):(1*1)。记三个分量中水平采样因子最大值为Hmax,垂直采样因子最大值为Vmax,那么单个MCU矩阵的宽就是Hmax*8像素,高就是Vmax*8像素。

如果,整幅图像的宽度和高度不是MCU宽度和高度的整数倍,那么编码时会用某些数值填充进去,保证解码过程中MCU的完整性(解码完成后,可直接忽视图像宽度和高度外的数据)。

在数据流中,MCU的排列方法是从左到右,从上到下。

 

b)        每个MCU又分为若干个数据单元。数据单元的大小必定为8*8,所以每个MCU的数据单元个数为Hmax*Vmax

另外JPEG的压缩方法与BMP文件有所不同,它不是把每个像素的颜色分量连续存储在一起的,而是把图片分成YCrCb三张子图,然后分别压缩。而三个颜色分量的采样密度(即采样因子)可能一样(例如111)也可能不一样(例如411)。

每个MCU内部,数据的顺序是YCrCb。如果一个颜色分量有多个数据单元,则顺序是从左到右,从上到下。

 

2)举例说明

下面通过一幅32*35的图像,对上面两个问题列出两种采样因子的具体说明。

         

1 整张完整的图像(411                            2 将图像的MCU1放大

 

1及图3中灰色部分为实际图像大小(32px*35px);粗虚线表示各个MCU的分界;细虚线表示MCU内部数据单元的分界。

 

a)    采样因子为411

此时,Hmax=max211=2Vmax=max211=2。所以,MCU的宽为Hmax*8=16像素,高为Vmax*8=16像素。图像实际的宽刚好是2MCU,但高则稍稍大于2MCU的高度,所以要补足3MCU

在数据流中,MCU的顺序是MCU1àMCU2àMCU3àMCU4àMCU5àMCU6

每个MCU又分为Hmax*Vmax=2*2=4个数据单元。由于采样因子是411,即(2*2):(1*1):(1*1),所以Y分量的水平和垂直方向都是每2个像素采样2次;Cr分量和Cb分量的水平和垂直方向都是每2个像素采样1次。因此,在一个MCU来里边,Y分量有256个采样点,即4个完整的数据单元;Cr分量和Cb分量各自只有64个采样点。

如果以MCU1说明MCU数据的次序,则依次为Y1 Y2 Y5 Y6 Cr1256 Cb1256 。图2中全部256个点均是Y的采样点,红色部分为Cr分量和Cr分量的采样点。

换句话说,对于整张图片来说,数据流的数据依次是:
[Y1
Y2 Y5 Y6 Cr1256 Cb1256] [Y3 Y4 Y7 Y8 Cr3478 Cb3478] [Y9 Y10 Y13 Y14 Cr9101314 Cb9101314 ]、……

3 整张完整的图像(111

 

b)    采样因子为111

如图3Hmax=max111=1Vmax=max111=2。所以,MCU的宽为Hmax*8=8像素,高为Vmax*8=8像素。图像实际的宽刚好是4MCU,但高则稍稍大于4MCU的高度,所以要补足5MCU

在数据流中,MCU的顺序是:
MCU1
àMCU2àMCU3àMCU4à ………… àMCU18àMCU19àMCU20

每个MCU又分为Hmax*Vmax=1*1=1个数据单元,也就是一个数据单元就是一个MCU。由于采样因子是111,即(1*1):(1*1):(1*1),所以Y分量、Cr分量和Cb分量的水平和垂直方向都是每1个像素采样1次,也就是图象的每一个像素都是采样点。因此,在一个MCU来里边,Y分量、Cr分量和Cb分量各自有64个采样点。有

因此,对于整张图片来说,数据流的数据依次是:
[Y1
Cr1Cb1] [Y2  Cr2 Cb2] [Y3 Cr3Cb3] 、………… [Y19 Cr19Cb19][Y20 Cr20Cb20]

 

3.颜色分量单元的内部解码

1)理论说明

“颜色分量单元”是笔者为说明问题而建立的概念,指的是MCU中某个颜色分量中的一个8*8数据块,例如上面提到的Y1 Cr1Cb1 都是一个颜色分量单元。

图像数据流是以位(bit)为单位存储信息的。并且内部的数据都是在编码时通过正向离散余弦变换(FDCT)进行时空域向频率域变换而得到的结果,所以对于每个颜色分量单元都应该由两部分组成:1个直流分量和63个交流分量。

解码的过程其实就是哈夫曼树的查找过程。

首先查阅标记段SOS中的颜色分量信息,可以得出各个颜色分量对应使用的直流分量和交流分量使用的哈夫曼树编号。一般来说,
Y
分量:直流分量:直流0号哈夫曼树,交流分量:交流0号哈夫曼树;

Cr分量:直流分量:直流1号哈夫曼树,交流分量:交流1号哈夫曼树;

Cb分量:直流分量:直流1号哈夫曼树,交流分量:交流1号哈夫曼树。

 

颜色分量单元内部综合运用了RLE行程编码和哈夫曼编码来压缩数据。每个像素的数据流由两部分构成:编码和数值,并且两者基本以互相隔开方式出现(除非该编码的权值为零)。具体读入单个颜色分量单元的步骤如下:

a)从此颜色分量单元数据流的起点开始一位一位的读入,直到读入的编码与该分量直流哈夫曼树的某个码字(叶子结点)一致,然后用直流哈夫曼树查得该码字对应的权值。权值(共8位)表示该直流分量数值的二进制位数,也就是接下来需要读入的位数。

b)继续读入位数据,直到读入的编码与该分量交流哈夫曼树的某个码字(叶子结点)一致,然后用交流哈夫曼树查得该码字对应的权值。权值的高4位表示当前数值前面有多少个连续的零,低4位表示该交流分量数值的二进制位数,也就是接下来需要读入的位数。

c)不断重复步骤b,直到满足交流分量数据结束的条件。而结束条件有两个,只要满足其中一个即可:

当读入码字的权值为零,表示往后的交流变量全部为零;

②已经读入63个交流分量。

d)各个数值的译码是按下表进行的:

                  实际数值

编码长度

编码

0

-

-1,1

1

0,1

-3,-2,2,3 

2

00,01,10,11

-7,-6,-5,-4,4,5,6,7 

3

000,001,010,011,100,101,110,111

-15,……,-8,8,……,15

4

0000,……,0111,1000,……,1111

-31,……,-16,16,……,31

5

00000,……,01111,10000,……,11111

-63,……,-32,32,……,63

6

……

-127,……,-64,64,……,127 

7

……

-255,……,-128,128,……,255

8

……

-511,……,-256,256,……,511

9

……

-1023,……,-512,512,……,1023

10

……

-2047,……,-1024,1024,……,2047

11

……

-4095,……,-2048,2048,……,4095

12

……

-8191,……,-4096,4096,……,8191

13

……

-16383,……,-8192,8192,……,16383

14

……

-32767,……,-16384,16384,……,32767 

15

……

 

2)举例说明

下面举例说明以上几点。某个颜色分量单元数据如下:

D3 5E 6E 4D 35 f5 8A

若以二进制表示,则为:

1101 0011 0101 1110 0110 1110 0100 1101 0011 0101 1111 0101 1000 1010

假设该颜色分量单元对应以下直流哈夫曼树和交流哈夫曼树,则可将各个以位为单位的数据流拆分如下:

110 1001101 01 1 11001 101 11001 001 101 00 11010 1 1111010 11 00 01010

 

直流哈夫曼树                                                               交流哈夫曼树

序号

码字长度

码字

权值

1

2

00

0x00

2

2

01

0x01

3

2

10

0x02

4

3

110

0x07

5

4

1110

0x1e

6

5

11110

0x2e

序号

码字长度

码字

权值

1

2

00

0x00

2

2

01

0x01

3

3

100

0x11

4

3

101

0x02

5

5

11000

0x21

6

5

11001

0x03

7

5

11010

0x31

8

5

11011

0x41

9

5

11100

0x12

10

6

111010

0x51

11

7

1110110

0x61

12

7

1110111

0x71

13

7

1111000

0x81

14

7

1111001

0x91

15

7

1111010

0x22

16

7

1111011

0x13

17

8

11111000

0x32

 

详细说明一下:

读入数据流并对照直流哈夫曼树,第一个哈夫曼编码为110,其权值为6,所以往后读入6位数据“1001101”,译码成数值为77。因为每个颜色分量单元只有一个直流分量,所以下一个就是第一个交流分量了。

继续读入数据流并对照交流哈夫曼树,得哈夫曼编码为01,其权值为1,所以它的前面没有零,并往后读如1位数据“1”,译码成数值为1。如此往复,最后读到哈夫曼编码“00”,其权值为0,所以满足交流变量结束条件(最后剩余的“01010”对本颜色分量单元来说是冗余的,它可能属于下一个颜色分量单元)。

实际上,这段数据译码为:

77,(01),(05),(0-6),(0-3),(51),(23

因此,把它置于18*8的矩阵中应为:

77

1

5

-6

-3

0

0

0

0

0

1

0

0

3

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

 

4.直流系数的差分编码

把所有的颜色分量单元按颜色分量(YCrCb)分类。每一种颜色分量内,相邻的两个颜色分量单元的直流变量是以差分来编码的。也就是说,通过步骤3解码出来的直流变量数值只是当前颜色分量单元的实际直流变量减去前一个颜色分量单元的实际直流变量。也就是说,当前直流变量要通过前一个颜色分量单元的实际(非解码)直流分量来校正:

DCn=DCn-1+Diff

其中Diff为差分校正变量,也就是直接解码出来的直流系数。但如果当前颜色分量单元是第一个单元,则解码出来的直流数值就是真正的直流变量。

再次提醒的是,3个颜色分量的直流变量是分开进行差分编码的。也就是说,为1张图片解码时应设置3个独立的直流校正变量。另一个问题是,当数据流中出现标记RSTn,则3个颜色分量的直流差分校正变量Diff都需要重新复位到0

 

5.反量化

不同的颜色分量使用不同的量化表,这个可以从标记段SOF中的颜色分量信息字段查得。一般是Y分量使用量化表0,而CrCb两个分量共同使用量化表1

反量化的过程比较简单。只需要对8*8的颜色分量单元的64个值逐一乘以对应的量化表内位置相同的值则可。图像内全部的颜色分量单元都要进行反量化。

 

6Zig-zag编码

如果将反量化后的每个8*8颜色分量单元的每个元素编号,如下图4,那么各反Zig-zag编码的过程就是把矩阵元素按图5重新排列。

       

4 将颜色分量单元元素编码                          5 Zig-zag编码

 

关于量化和反Zig-zag编码的先后顺序,笔者查阅的几份资料有不同的见解。经过实践试验,解码的过程中,是应该直接用文件提供的量化表反量化矩阵数据,再将其反Zig-zag编码才能正确解码。

 

7.隔行的正负纠正

这个问题比较特别,因为在笔者认真阅读的几份资料中都没有提及此问题。而是笔者通过对已知图像进行JPEG编码压缩,然后和该图的JPEG文件数据对比发现的问题。具体原因不明。

实际上,就是必须对每个颜色分量单元的奇数行(每个颜色分量单元有8行,假设把它按01、……、67编出行号),即1357行,进行取相反数操作(正的变负,负的变正)。

 

8.反离散余弦变换

之前提到,文件中的数据是在编码时通过正向离散余弦变换(FDCT)进行时空域向频率域变换而得到的结果,所以现在解码就必须将其反向离散余弦变换(IDCT),就是把颜色分量单元矩阵中的频率域数值向时空域转换。并且,原来的频率域的矩阵大小为8*8,则经过反向离散余弦变换后,时空域的矩阵仍然是8*8

设正负纠正后的频率域矩阵为F[u][v],而反向离散余弦变换后的矩阵为f[i][j],其中0u,v,i,j7。具体使用的公式如下:

,其中

Cu= (当u=0),Cu=1(当u0);

Cv= (当v=0),Cu=1(当v0);

 

另外补充一下正向离散余弦变换的公式,用于编码:

 

9YCrCbRGB转换

要在屏幕上显示图像,就必须以RGB模式表示图像的颜色。所以,解码时需要把YCrCb模式向RGB模式转换。

正如前面提到,并不是每种颜色分量的采样因子都一样,所以转换时需要注意。如果采样因子是111,则每一个像素点的3个颜色分量都被采样,所以没有问题。但411的采样因子就不一样了。由“初步了解图像数据流的结构”一节中对411的采样因子的分析,可以知道一个MCU里有4Y分量单元,而Cr分量和Cb分量各自只有1个分量单元。以图2为例,仅有的一个Cr分量单元(红色的64个采样点)应该平铺用于4Y分量单元,即左上角16个值用于Y1,右上角16个值用于Y2,左下角16个值用于Y5,右下角16个值用于Y6。换句话说,一个Cr采样点服务于4Y采样点。对于Cb分量,道理一样。

另外,由于离散余弦变化要求定义域的对称,所以在编码时把RGB的数值范围从[0255]统一减去128偏移成[-128127]。因此解码时必须为每个分量加上128。具体公式如下:

R=Y                       +1.402*Cb     +128;

G=Y-0.34414*Cr    -0.71414*Cb   +128;

B=Y                       +1.772*Cb     +128;

还有一个问题,通过变换得出的RGB值可能超出了其定义域,所以要作出检查。如果大于255,则截断为255;如果小于0,则截断为0

 

下面补充RGB模式向YCrCb模式的公式:

Y =0.299*R            +0.587*G       +0.114*B    ;

Cr=   -0.1687*R      - 0.3313*G     +0.5*B  +128;

Cb=0.5 *R              - 0.4187*G     - 0.0813*B+128;

 

至此,每个MCU的解码已经完成。而每一个MCU如何组成一幅完整的图像,请参考初步了解图像数据流的结构分析。


参考文献

[1] 李才伟,中山大学计算机系多媒体课程教学课件.

[2]     张益贞,Visual C++实现MPEG/JPEG编解码技术.北京:人民邮电出版社

[3] CCITInformation Technology-digital Compression and Conding of Continuous-ton Still Images-requirements and Guidelineshttp://www.wotsit.org/download.asp?f=itu-1150PDF (访问日期:2007-1-1

[4] 公子御风,JFIF文件格式即JPEG文件交换格式(JPEG File Interchonge Format)http://cat1226.bokee.com/4574350.html (访问日期:2006-12-29

[5] 云风,JPEG 简易文档 V2.11http://rtornados.bokee.com/2442419.html (访问日期:2006-12-30


附录:JPEG定义的标记

标记名

标记代码

说明

帧开始标记,Start of Frame,非层次哈夫曼编码

SOF0

0xFFC0

基线离散余弦变换

SOF1

0xFFC1

扩展顺序离散余弦变换

SOF2

0xFFC2

递进离散余弦变换

SOF3

0xFFC3

空间顺序无损

帧开始标记,Start of Frame,层次哈夫曼编码

SOF5

0xFFC5

差分离散余弦变换

SOF6

0xFFC6

差分层次离散余弦变换

SOF7

0xFFC7

差分空间无损

帧开始标记,Start of Frame,非层次算术编码

JPEG

0xFFC8

JPEG扩展保留

SOF9

0xFFC9

扩展顺序离散余弦变换

SOF10

0xFFCA

递进离散余弦变换

SOF11

0xFFCB

空间顺序无损

帧开始标记,Start of Frame,层次算术编码

SOF13

0xFFCD

差分离散余弦变换

SOF14

0xFFCE

差分层次离散余弦变换

SOF15

0xFFCF

差分空间无损

其他标记

DHT

0xFFC4

定义哈夫曼树表

DAC

0xFFCC

定义算术编码表

RST0

OxFFD0

差分编码累计复位,共8

……

……

RST7

OxFFD7

SOI

OxFFD8

图像开始

EOI

OxFFD9

图像结束

SOS

0xFFDA

开始扫描,图像数据开始

DQT

0xFFDB

定义量化表

DNL

0xFFDC

定义线数

DRI

0xFFDD

定义差分编码累计复位的间隔

DHP

0xFFDE

定义层次级数

EXP

0xFFDF

展开参考图像

APP0

0xFFE0

为应用程序保留,共15

……

……

APP15

0xFFEE

JPG0

0xFFF0

JPEG扩展保留,共14

……

……

JPG13

0xFFFD

COM

0xFFFE

注释

TEM

0xFF01

算术编码中作临时之用

RES

0xFF02

保留,共189

……

……

RES

0xFFBF

 

 

 

从显示列表中移除 obj.parent && obj.parent.removeChild(obj)
查看有多少个子容器 obj.numChildren

添加子容器 obj.addChild(child)
添加子容器到指定zIndex obj.addChildAt(child, childZIndex) 起始值是0

从显示队列删除子容器 obj.removeChild(child)
从显示队列删除子容器 obj.removeChildAt(zIndex)

删除所有子容器 obj.removeChildren() 或者下面这样写
const numChild: number = sprcon.numChildren;
for (const t: number = 0; t < numChild; t++) {
    sprcon.removeChildAt(0);
}

同一个zIndex只能有一个DisplayObject

不同zIndex交换顺序
容器.swapChildren( 显示对象, 显示对象 )
容器.swapChildrenAt( 深度值, 深度值 )

指定zIndex
容器.setChildIndex( 显示对象, 新的深度值 );
obj.zIndex = 3; 十倍性能优化 
要给一个对象使用 zIndex,包含此显示对象的 DisplayObjectContainer 对象一定要设置 sortableChildren = true,开启排序功能,否则设置 zIndex 是无效的

let container = new egret.Sprite();
container.sortableChildren = true; //注意,一定要设置为true
this.addChild(container);

let texture: egret.Texture = RES.getRes("bird_png");
let b1 = new egret.Bitmap();
b1.texture = texture;
b1.x = 100;
this.addChild(b1);

let b2 = new egret.Bitmap();
b2.texture = texture;
b2.x = 270;
this.addChild(b2);

let b3 = new egret.Bitmap();
b3.texture = texture;
b3.x = 440;
this.addChild(b3);

b2.zIndex = 3; //将第二个图片设置到顶部


查询现实对象
容器.getChildAt( 深度值 );
容器.getChildByName( 显示对象 )
const _spr: egret.DisplayObject = sprcon.getChildAt(1);
_spr.alpha = 0.5;

通过graphics对象绘制图形

class GraphicsTest extends egret.DisplayObjectContainer {
    public constructor() {
        super();
        this.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
    }
    private onAddToStage(event: egret.Event) {
        const shp: egret.Shape = new egret.Shape();
        shp.graphics.beginFill(0xff0000, 1);
        shp.graphics.drawRect(0, 0, 100, 200);
        shp.graphics.endFill();
        this.addChild(shp);
    }
}

shp.graphics.lineStyle(10, 0x00ff00); 提前声明画笔: 线条宽度, 颜色
shp.graphics.beginFill(0xff0000, 1); 红色, 1不透明, 开始填充
绘制形状
shp.graphics.endFill(); 结束填充

矩形
 shp.graphics.drawRect(0, 0, 100, 200);
 相对于shp的锚点(0,0), 宽度x=100,高度y=200

圆形
 drawCircle(x: number, y: number, radius: number): void;
 相对于shap的锚点, 指定圆心坐标(x,y),半径大小

直线
    moveTo(x: number, y: number): void; 起点坐标
    lineTo(x: number, y: number): void; 终点坐标(允许多条调用)

直线: 不需要开始填充, 只要结束填充

class GraphicsTest extends egret.DisplayObjectContainer {
    public constructor() {
        super();
        this.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
    }
    private onAddToStage(event: egret.Event) {
        const shp: egret.Shape = new egret.Shape();
        shp.graphics.lineStyle(2, 0x00ff00);
        shp.graphics.moveTo(10, 10);
        shp.graphics.lineTo(167, 76);
        shp.graphics.lineTo(221, 118);
        shp.graphics.lineTo(290, 162);
        shp.graphics.lineTo(297, 228);
        shp.graphics.lineTo(412, 250);
        shp.graphics.lineTo(443, 174);
        shp.graphics.endFill();
        this.addChild(shp);
    }
}

二次贝赛尔曲线
moveTo(x: number, y: number): void; P0起点坐标
curveTo(x1: number, y1: number, x2: number, y2: number): void; P1控制点,P2结束点

class GraphicsTest extends egret.DisplayObjectContainer {
    public constructor() {
        super();
        this.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
    }
    private onAddToStage(event: egret.Event) {
        const shp: egret.Shape = new egret.Shape();
        shp.graphics.lineStyle(2, 0x00ff00);
        shp.graphics.moveTo(50, 50);
        shp.graphics.curveTo(100, 100, 200, 50);
        shp.graphics.endFill();
        this.addChild(shp);
    }
}


圆弧
drawArc(x: number, y: number, radius: number, startAngle: number, endAngle: number, anticlockwise: boolean): void;
圆弧路径的圆心位置(x,y)
圆弧半径radius
圆弧起点的角度startAngle (从x 轴方向开始计算,以弧度为单位)
圆弧终点的角度endAngle
绘制方向anticlockwise (true,逆时针绘制圆弧, false,顺时针绘制, 通常false)

一个从 0 到 π 的圆弧(上半圆,扇形)
class GraphicsTest extends egret.DisplayObjectContainer {
    public constructor() {
        super();
        this.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
    }
    private onAddToStage(event: egret.Event) {
        const shp: egret.Shape = new egret.Shape();
        shp.graphics.beginFill(0x1122cc);
        shp.graphics.drawArc(200, 200, 100, 0, Math.PI, true);
        shp.graphics.endFill();
        this.addChild(shp);
    }
}


线条型 弧线, 不填充颜色
const shape: egret.Shape = new egret.Shape();
shape.graphics.lineStyle(2, 0xffff00);
shape.graphics.drawArc(50, 50, 50, 0, Math.PI / 180 * 30, false);
shape.graphics.endFill();

拱形
画拱和画弧的区别就是:画拱需要填充图形,画弧不需要填充图形
const shape: egret.Shape = new egret.Shape();
shape.graphics.beginFill(0xff0000);
shape.graphics.drawArc(50, 50, 50, 0, Math.PI / 180 * 60, false);
shape.graphics.endFill();

扇形
const r: number = 50;
const shape: egret.Shape = new egret.Shape();
shape.graphics.beginFill(0xff0000);
shape.graphics.moveTo(r, r); //绘制点移动(r, r)点
shape.graphics.lineTo(r * 2, r); //画线到弧的起始点
shape.graphics.drawArc(50, 50, 50, 0, 260 * Math.PI / 180, false); //从起始点顺时针画弧到终点
shape.graphics.lineTo(r, r); //从终点画线到圆形。到此扇形的封闭区域形成
shape.graphics.endFill();

弧形进度条
class GraphicsText {
    private getArcProgress(): egret.Shape {
        const shape: egret.Shape = new egret.Shape();
        let angle: number = 0;
        egret.startTick(function (timeStamp: number) {
            angle += 1;
            changeGraphics(angle);
            angle = angle % 360;
            return true;
        }, this);
        function changeGraphics(angle) {
            shape.graphics.clear();
            shape.graphics.lineStyle(2, 0x0000ff, 1);
            shape.graphics.drawArc(50, 50, 50, 0, angle * Math.PI / 180, false);
            shape.graphics.endFill();
        }
    }
}

扇形进度条
class GraphicsText {
    private getSectorProgress(): egret.Shape {
        const shape: egret.Shape = new egret.Shape();
        let angle: number = 0;
        egret.startTick(function (timeStamp: number): boolean {
            angle += 1;
            changeGraphics(angle);
            angle = angle % 360;
            return true;
        }, this);
        return shape;
        function changeGraphics(angle) {
            shape.graphics.clear();
            shape.graphics.beginFill(0xff0000);
            shape.graphics.moveTo(50, 50);
            shape.graphics.lineTo(100, 50);
            shape.graphics.drawArc(50, 50, 50, 0, angle * Math.PI / 180, false);
            shape.graphics.lineTo(50, 50);
            shape.graphics.endFill();
        }
    }
}

不规则边框进度条
class GraphicsText {
    private getSectorProgress(): egret.DisplayObjectContainer {
        const container: egret.DisplayObjectContainer = new egret.DisplayObjectContainer();
        const w: number = 100;
        const h: number = 100;
        const r: number = Math.max(w, h) / 2 * 1.5;
        const bitmap = new egret.Bitmap(RES.getRes(key));
        container.addChild(bitmap);
        bitmap.width = w;
        bitmap.height = h;
        const shape: egret.Shape = new egret.Shape();
        shape.x = bitmap.width / 2;
        shape.y = bitmap.height / 2;
        bitmap.mask = shape;
        container.addChild(shape);
        let angle = 0;
        egret.startTick(function (timeStamp: number): boolean {
            angle += 1;
            changeGraphics(angle);
            angle = angle % 360;
            return true;
        }, this);
        return container;
        function changeGraphics(angle) {
            shape.graphics.clear();
            shape.graphics.beginFill(0x00ffff, 1);
            shape.graphics.lineTo(r, 0);
            shape.graphics.drawArc(0, 0, r, 0, angle * Math.PI / 180, true);
            shape.graphics.lineTo(0, 0);
            shape.graphics.endFill();
        }
    }
}

多个形状绘制, 互相是独立的,每一次绘制填充,都必须以 endFill() 结束,才能开始下一次绘制
 this.graphics.beginFill(0x0000ff);
this.graphics.drawRect(0, 0, 50, 50);
this.graphics.endFill();
this.graphics.beginFill(0x0000ff);
this.graphics.drawRect(50, 50, 50, 50);
this.graphics.endFill();
this.graphics.beginFill(0xff0000);
this.graphics.drawRect(50, 0, 50, 50);
this.graphics.endFill();
this.graphics.beginFill(0xff0000);
this.graphics.drawRect(0, 50, 50, 50);
this.graphics.endFill();

遮罩层
遮罩的作用是指定一个显示对象的可见区域,所有显示对象都具备遮罩功能。

1.矩形遮罩
矩形遮罩,即显示对象的可见区域是方形显示区域而非不规则显示区域。
用法为:将一个矩形对象赋值给显示对象的 mask 属性。
shp.mask = new egret.Rectangle(20, 20, 30, 50);

2.显示对象遮罩
显示对象遮罩,即显示对象的可见区域由另一个显示对象确定,可实现不规则遮罩。
用法为:将被遮罩显示对象的 mask 属性设置为遮罩对象:

mySprite.mask = maskSprite; //将maskSprite设置为mySprite的遮罩
mySprite.mask = null; // 删除遮罩

//画一个红色的正方形
const square: egret.Shape = new egret.Shape();
square.graphics.beginFill(0xff0000);
square.graphics.drawRect(0, 0, 100, 100);
square.graphics.endFill();
this.addChild(square);
//画一个蓝色的圆形
const circle: egret.Shape = new egret.Shape();
circle.graphics.beginFill(0x0000ff);
circle.graphics.drawCircle(25, 25, 25);
circle.graphics.endFill();
this.addChild(circle);
square.mask = circle; // 正方形的遮罩是圆形, 最终可见到红色的圆形

用作遮罩的显示对象可设置动画、动态调整大小。
遮罩显示对象不一定需要添加到显示列表中。
但是,如果希望在缩放舞台时也缩放遮罩对象,或者如果希望支持用户与遮罩对象的交互(如调整大小),则必须将遮罩对象添加到显示列表中。

清空绘图
清空绘图操作是将已经绘制的图像全部清空,可以执行 Graphics 中的 clear() 方法

shp.graphics.clear();

文本

1.普通文本

const label: egret.TextField = new egret.TextField();
label.text = "This is a text!";
this.addChild(label);

2.输入文本
把egret.TextField.type 改为 egret.TextFieldType.INPUT 即可输入

const txInput: egret.TextField = new egret.TextField();
txInput.type = egret.TextFieldType.INPUT;
txInput.width = 282;
txInput.height = 43;
txInput.x = 134;
txInput.y = 592;
txInput.textColor = 0x000000;
/// 注意_container是事先建立好的一个显示容器,即 egret.Sprite,并且已经添加到显示列表中
this._container.addChild(txInput);

聚焦 textField.setFocus();

const textIput: egret.TextField = new egret.TextField();
textIput.type = egret.TextFieldType.INPUT;
this.addChild(textIput);

const button: egret.Shape = new egret.Shape();
button.graphics.beginFill(0x00cc00);
button.graphics.drawRect(0, 0, 100, 40);
button.graphics.endFill();
button.y = 50;
this.addChild(button);
button.touchEnabled = true;
button.addEventListener(egret.TouchEvent.TOUCH_BEGIN, (e) => {
    textIput.setFocus();
}, this);

设置输入文本的样式为 text,密码(默认输入英文字母),电话(数字输入框)

text.inputType = egret.TextFieldInputType.TEXT;
egret.TextFieldInputType.PASSWORD
egret.TextFieldInputType.TEL

3.位图文本 egret.BitmapText

位图文本是借助位图字体渲染的文本类型。egret.BitmapText 类表示位图文本类型。

加载位图字体文件, 将加载后的字体对象赋值给 egret.BitmapText 的 font 属性。

class Main extends egret.DisplayObjectContainer {
    public constructor() {
        super();
        this.once(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
    }
    private onAddToStage(evt: egret.Event) {
        RES.getResByUrl("resource/cartoon-font.fnt", this.onLoadComplete, this, RES.ResourceItem.TYPE_FONT);
    }
    private _bitmapText: egret.BitmapText;
    private onLoadComplete(font: egret.BitmapFont): void {
        this._bitmapText = new egret.BitmapText();
        this._bitmapText.font = font;
        this._bitmapText.x = 50;
        this._bitmapText.y = 300;
        this.addChild(this._bitmapText);
    }
}

对齐方式

label.textAlign = egret.HorizontalAlign.CENTER;  水平
label.verticalAlign = egret.VerticalAlign.MIDDLE; 居中

字体

label.fontFamily = "Impact";

字号

label.size = 20

egret.TextField.default_size 属性可设置全局的默认文本字号大小。
egret.TextField 的对象自身尺寸会根据首次设置的文本内容自动计算

字体颜色, 默认白色

label.textColor = 0xff0000;

文字描边

label.strokeColor = 0x0000ff;
label.stroke = 2; 描边粗度

加粗

label.bold = true

斜体

label.italic =true

文字流, 不同字符, 不同的颜色样式

const tx: egret.TextField = new egret.TextField;
tx.width = 400;
tx.x = 10;
tx.y = 10;
tx.textColor = 0;
tx.size = 20;
tx.fontFamily = "微软雅黑";
tx.textAlign = egret.HorizontalAlign.CENTER;
tx.textFlow = <Array<egret.ITextElement>>[
    { text: "Text in ", style: { "size": 20 } },
    { text: "Egret", style: { "textColor": 0x336699, "size": 60, "strokeColor": 0x6699cc, "stroke": 2 } },
    { text: " can ", style: { "fontFamily": "Impact" } },
    { text: "be set ", style: { "fontFamily": "Times New Roman" } },
    { text: "to a ", style: { "textColor": 0xff0000 } },
    { text: "\n" },
    { text: "variety ", style: { "textColor": 0x00ff00 } },
    { text: "of ", style: { "textColor": 0xf000f0 } },
    { text: "styles ", style: { "textColor": 0x00ffff } },
    { text: "with", style: { "size": 56 } },
    { text: "different ", style: { "size": 16 } },
    { text: "colors, ", style: { "size": 26 } },
    { text: "\n" },
    { text: "fonts ", style: { "italic": true, "textColor": 0x00ff00 } },
    { text: "and ", style: { "size": 26, "textColor": 0xf000f0, fontfamily = "Quaver" } },
    { text: "sizes", style: { "italic": true, "textColor": 0xf06f00 } }
];
this.addChild(tx);

文字超链接, 必须用textFlow, 且可触摸

tx.textFlow = new Array<egret.ITextElement>(
{ text: "这段文字有链接", style: { "href": "http://www.egret.com/" } },
{ text: "\n这段文字没链接", style: {} }
);
tx.touchEnabled = true;

fdisk -l
mkfs.ext4 /dev/vdb
mount /dev/vdb /data
vi /etc/fstab
/dev/vdb /data ext4 defaults 0 0

交换分区是

/var/swapfile swap swap defaults 0 0

http://www.ruanyifeng.com/blog/2016/04/cors.html

如果需要跨域的预先检查,则响应options请求;
之后只要声明Origin和ExposeHeader即可;

    location / {
       if ($request_method = 'OPTIONS') {
            add_header 'access-control-allow-origin' '*' always; 
            add_header 'access-control-allow-methods' 'GET, POST, PATCH, PUT, DELETE, OPTIONS' always;                 
            add_header 'access-control-allow-credentials' 'false' always;# origin不能为*
            add_header 'access-control-allow-headers' 'iv, key, authorization, content-type, if-match, if-modified-since, if-none-match, if-unmodified-since, x-csrf-token, x-requested-with' always;                
            add_header 'access-control-max-age' '2592000' always;
            add_header 'content-type' 'text/plain';
            add_header 'content-length' 0;
            return 204;
        }           
        add_header 'access-control-allow-origin' '*' always;
        add_header 'access-control-expose-headers' 'date, logid' always;
        add_header 'logid' $request_id always;
    }

常用
netstat -nutlp
netstat -natp
netstat -r

netstat命令各个参数

-a   或–all                             显示所有连线中的Socket。
-A                                       <网络类型>或–<网络类型> 列出该网络类型连线中的相关地址。
-c   或–continuous               持续列出网络状态。
-C 或–cache                       显示路由器配置的快取信息。
-e  或–extend                     显示网络其他相关信息。
-F  或 –fib                          显示FIB。
-g  或–groups                     显示多重广播功能群组组员名单。
-h  或–help                        在线帮助。
-i   或–interfaces                 显示网络界面信息表单。
-l  或–listening                    显示监控中的服务器的Socket。
-M   或–masquerade           显示伪装的网络连线。
-n  或–numeric                   直接使用IP地址,而不通过域名服务器。
-N   或–netlink或–symbolic  显示网络硬件外围设备的符号连接名称。
-o  或–timers                      显示计时器。
-p   或–programs                显示正在使用Socket的程序识别码和程序名称。
-r  或–route                        显示 Routing Table。
-s  或–statistice 显示网络工作信息统计表。
-t  或–tcp 显示TCP 传输协议的连线状况。
-u或–udp 显示UDP传输协议的连线状况。
-v或–verbose 显示指令执行过程。
-V 或–version 显示版本信息。
-w或–raw 显示RAW传输协议的连线状况。
-x或–unix 此参数的效果和指定”-A unix”参数相同。
–ip或–inet 此参数的效果和指定”-A inet”参数相同。

状态state枚举值

LISTEN:(Listening for a connection.)侦听来自远方的TCP端口的连接请求
SYN-SENT:(Active; sent SYN. Waiting for a matching connection request after having sent a connection request.)再发送连接请求后等待匹配的连接请求
SYN-RECEIVED:(Sent and received SYN. Waiting for a confirming connection request acknowledgment after having both received and sent connection requests.)再收到和发送一个连接请求后等待对方对连接请求的确认
ESTABLISHED:(Connection established.)代表一个打开的连接
FIN-WAIT-1:(Closed; sent FIN.)等待远程TCP连接中断请求,或先前的连接中断请求的确认
FIN-WAIT-2:(Closed; FIN is acknowledged; awaiting FIN.)从远程TCP等待连接中断请求
CLOSE-WAIT:(Received FIN; waiting to receive CLOSE.)等待从本地用户发来的连接中断请求
CLOSING:(Closed; exchanged FIN; waiting for FIN.)等待远程TCP对连接中断的确认
LAST-ACK:(Received FIN and CLOSE; waiting for FIN ACK.)等待原来的发向远程TCP的连接中断请求的确认
TIME-WAIT:(In 2 MSL (twice the maximum segment length) quiet wait after close. )等待足够的时间以确保远程TCP接收到连接中断请求的确认
CLOSED:(Connection is closed.)没有任何连接状态

网上常见的

netstat -ano来显示协议统计信息和TCP/IP网络连接

netstat -t/-u/-l/-r/-n【显示网络相关信息,-t:TCP协议,-u:UDP协议,-l:监听,-r:路由,-n:显示IP地址和端口号】

netstat -tlun【查看本机监听的端口】

netstat -an【查看本机所有的网络】

netstat -rn【查看本机路由表】

列出所有端口:netstat -a 

列出所有的TCP端口:netstat -at 

列出所有的UDP端口:netstat -au 

列出所有处于监听状态的socket:netstat -l 

列出所有监听TCP端口的socket:netstat -lt 

列出所有监听UDP端口的socket:netstat -lu 

找出程序运行的端口:netstat -ap | grep ssh 

找出运行在指定端口的进程:netstat -an | grep ':80'

1.查找请求数前20个IP(常用于查找攻来源):
netstat -anlp|grep 80|grep tcp|awk '{print $5}'|awk -F: '{print $1}'|sort|uniq -c|sort -nr|head -n20
 
netstat -ant |awk '/:80/{split($5,ip,”:”);++A[ip[1]]}END{for(i in A) print A[i],i}' |sort -rn|head -n20
 
2.用tcpdump嗅探80端口的访问看看谁最高
tcpdump -i eth0 -tnn dst port 80 -c 1000 | awk -F”.” '{print $1″.”$2″.”$3″.”$4}' | sort | uniq -c | sort -nr |head -20
 
3.查找较多time_wait连接
netstat -n|grep TIME_WAIT|awk '{print $5}'|sort|uniq -c|sort -rn|head -n20
 
4.找查较多的SYN连接
netstat -an | grep SYN | awk '{print $5}' | awk -F: '{print $1}' | sort | uniq -c | sort -nr | more
 
5.根据端口列进程
netstat -ntlp | grep 80 | awk '{print $7}' | cut -d/ -f1