精华内容
下载资源
问答
  • JPEG编码

    2019-06-02 21:41:10
    JPEG 压缩简介 色彩模型 JPEG 的图片使用的是 YCrCb 颜色模型, 而不是计算机上最常用的 RGB. 关于色 彩模型, 这里不多阐述. 只是说明, YCrCb 模型更适合图形压缩. 因为人眼对图片上 的亮度 Y 的变化远比色度...

    JPEG 压缩简介

    原文:https://bbs.csdn.net/topics/190980

    1. 色彩模型

      JPEG 的图片使用的是 YCrCb 颜色模型, 而不是计算机上最常用的 RGB. 关于色
      彩模型, 这里不多阐述. 只是说明, YCrCb 模型更适合图形压缩. 因为人眼对图片上
      的亮度 Y 的变化远比色度 C 的变化敏感. 我们完全可以每个点保存一个 8bit 的亮
      度值, 每 2x2 个点保存一个 Cr Cb 值, 而图象在肉眼中的感觉不会起太大的变化.
      所以, 原来用 RGB 模型, 4 个点需要 4x3=12 字节. 而现在仅需要 4+2=6 字节; 平
      均每个点占 12bit. 当然 JPEG 格式里允许每个点的 C 值都记录下来; 不过 MPEG 里
      都是按 12bit 一个点来存放的, 我们简写为 YUV12.

    [R G B] -> [Y Cb Cr] 转换

    (R,G,B 都是 8bit unsigned)

        | Y  |     |  0.299       0.587       0.114 |   | R |     | 0 |
        | Cb |  =  |- 0.1687    - 0.3313      0.5   | * | G |   + |128|
        | Cr |     |  0.5       - 0.4187    - 0.0813|   | B |     |128|
    

    Y = 0.299R + 0.587G + 0.114B (亮度)
    Cb = - 0.1687
    R - 0.3313*G + 0.5 B + 128
    Cr = 0.5 R - 0.4187G - 0.0813
    B + 128

    [Y,Cb,Cr] -> [R,G,B] 转换

    R = Y + 1.402 (Cr-128)
    G = Y - 0.34414
    (Cb-128) - 0.71414*(Cr-128)
    B = Y + 1.772 *(Cb-128)

    一般, C 值 (包括 Cb Cr) 应该是一个有符号的数字, 但这里被处理过了, 方法
    

    是加上了 128. JPEG 里的数据都是无符号 8bit 的.

    1. DCT (离散余弦变换)

      JPEG 里, 要对数据压缩, 先要做一次 DCT 变换. DCT 变换的原理, 涉及到数学
      知识, 这里我们不必深究. 反正和傅立叶变换(学过高数的都知道) 是差不多了. 经过
      这个变换, 就把图片里点和点间的规律呈现出来了, 更方便压缩.JPEG 里是对每 8x8
      个点为一个单位处理的. 所以如果原始图片的长宽不是 8 的倍数, 都需要先补成 8
      的倍数, 好一块块的处理. 另外, 记得刚才我说的 Cr Cb 都是 2x2 记录一次吗? 所
      以大多数情况, 是要补成 16x16 的整数块.按从左到右, 从上到下的次序排列 (和我
      们写字的次序一样). JPEG 里是对 Y Cr Cb 分别做 DCT 变换的. 这里进行 DCT 变换
      的 Y, Cr, Cb 值的范围都是 -128~127. (Y 被减去 128)

      JPEG 编码时使用的是 Forward DCT (FDCT) 解码时使用的 Inverse DCT (IDCT)
      下面给出公式:

    FDCT:
    7 7 2x+1 2y+1
    F(u,v) = alpha(u)alpha(v) sum sum f(x,y) * cos (------- uPI)* cos (------ vPI)
    x=0 y=0 16 16

    u,v = 0,1,…,7

           { 1/sqrt(8)  (u==0)
    

    alpha(u) = {
    { 1/2 (u!=0)

    IDCT:
    7 7 2x+1 2y+1
    f(x,y) = sum sum alpha(u)*alpha(v)*F(u,v)cos (------- uPI) cos (------ vPI)
    u=0 v=0 16 16

    x,y=0,1…7

    这个步骤很花时间, 另外有种 AA&N 优化算法, 大家可以去 inet 自己找一下. 
    

    在 Intel 主页上可以找到 AA&N IDCT 的 MMX 优化代码. ( Intel 主页上的代码,
    输入数据为 12.4 的定点数, 输入矩阵需要转置 90 度)

    1. 重排列 DCT 结果
      DCT 将一个 8x8 的数组变换成另一个 8x8 的数组. 但是内存里所有数据都是线
      形存放的, 如果我们一行行的存放这 64 个数字, 每行的结尾的点和下行开始的点就
      没有什么关系, 所以 JPEG 规定按如下次序整理 64 个数字.

                0, 1, 5, 6,14,15,27,28,
                2, 4, 7,13,16,26,29,42,
                3, 8,12,17,25,30,41,43,
                9,11,18,24,31,40,44,53,
               10,19,23,32,39,45,52,54,
               20,22,33,38,46,51,55,60,
               21,34,37,47,50,56,59,61,
               35,36,48,49,57,58,62,63
      

      这样数列里的相邻点在图片上也是相邻的了.

    2. 量化
      对于前面得到的 64 个空间频率振幅值, 我们将对它们作幅度分层量化操作.方
      法就是分别除以量化表里对应值并四舍五入.

    for (i = 0 ; i<=63; i++ )
    vector[i] = (int) (vector[i] / quantization_table[i] + 0.5)

    下面有张 JPEG 标准量化表. (按上面同样的弯曲次序排列)
    
    16 11 10 16 24  40  51  61
    12 12 14 19 26  58  60  55
    14 13 16 24 40  57  69  56
    14 17 22 29 51  87  80  62
    18 22 37 56 68  109 103 77
    24 35 55 64 81  104 113 92
    49 64 78 87 103 121 120 101
    72 92 95 98 112 100 103 99
    
    这张表依据心理视觉阀制作, 对 8bit 的亮度和色度的图象的处理效果不错.
    

    当然我们可以使用任意的量化表. 量化表是定义在 jpeg 的 DQT 标记后. 一般
    为 Y 值定义一个, 为 C 值定义一个.
    量化表是控制 JPEG 压缩比的关键. 这个步骤除掉了一些高频量, 损失了很高
    细节. 但事实上人眼对高空间频率远没有低频敏感.所以处理后的视觉损失很小.
    另一个重要原因是所有的图片的点与点之间会有一个色彩过渡的过程. 大量的图象
    信息被包含在低空间频率中. 经过量化处理后, 在高空间频率段, 将出现大量连续
    的零.
    注意, 量化后的数据有可能超过 2 byte 有符号整数的处理范围.

    1. 0 RLE 编码
      现在我们矢量中有许多连续的 0. 我们可以使用 RLE 来压缩掉这些 0. 这里我们
      将跳过第一个矢量 (后面将解释为什么) 因为它的编码比较特别. 假设有一组矢量
      (64 个的后 63 个) 是
      57,45,0,0,0,0,23,0,-30,-16,0,0,1,0,0,0, 0 , 0 ,0 , 0,…,0
      经过 RLC 压缩后就是
      (0,57) ; (0,45) ; (4,23) ; (1,-30) ; (0,-16) ; (2,1) ; EOB
      EOB 是一个结束标记, 表示后面都是 0 了. 实际上, 我们用 (0,0) 表示 EOB
      但是, 如果这组数字不以 0 结束, 那么就不需要 EOB.
      由于后面 huffman 编码的要求, 每组数字前一个表示 0 的数量的必须是 4 bit,
      就是说, 只能是 0~15, 所以我们实际这样编码:
      (0,57) ; (15,0) (2,3) ; (4,2) ; (15,0) (15,0) (1,895) , (0,0)
      注意 (15,0) 表示了 16 个连续的 0.

    2. huffman 编码
      为了提高储存效率, JPEG 里并不直接保存数值, 而是将数值按位数分成 16 组:

              数值                 组              实际保存值
               0                   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 .

    还是来看前面的例子:
    (0,57) ; (0,45) ; (4,23) ; (1,-30) ; (0,-8) ; (2,1) ; (0,0)
    只处理每对数右边的那个:
    57 是第 6 组的, 实际保存值为 111001 , 所以被编码为 (6,111001)
    45 , 同样的操作, 编码为 (6,101101)
    23 -> (5,10111)
    -30 -> (5,00001)
    -8 -> (4,0111)
    1 -> (1,1)

    前面的那串数字就变成了:
    (0,6), 111001 ; (0,6), 101101 ; (4,5), 10111; (1,5), 00001; (0,4) , 0111 ;
    (2,1), 1 ; (0,0)

    括号里的数值正好合成一个字节. 后面被编码的数字表示范围是 -32767…32767.
    合成的字节里, 高 4 位是前续 0 的个数, 低 4 位描述了后面数字的位数.

    继续刚才的例子, 如果 06 的 huffman 编码为 111000
    69 = (4,5) — 1111111110011001
    21 = (1,5) — 11111110110
    4 = (0,4) — 1011
    33 = (2,1) — 11011
    0 = EOB = (0,0) — 1010

    那么最后对于前面的例子表示的 63 个系数 (记得我们将第一个跳过了吗?) 按位流
    写入 JPG 文件中就是这样的:
    111000 111001 111000 101101 1111111110011001 10111 11111110110 00001
    1011 0111 11011 1 1010

    DC 的编码

    记得刚才我们跳过了每组 64 个数据的第一个吧, DC 就是指的这个数字 (后面 63
    个简称 AC) 代入前面的 FDCT 公式可以得到
    c(0,0) 7 7
    DC = F(0,0) = --------- * sum sum f(x,y) * cos 0 * cos 0 其中 c(0,0) = 1/2
    4 x=0 y=0

       1     7   7         
    

    = — * sum sum f(x,y)
    8 x=0 y=0

    即一块图象样本的平均值. 就是说, 它包含了原始 8x8 图象块里的很多能量. (通常
    会得到一个很大的数值)

    JPEG 的作者指出连续块的 DC 率之间有很紧密的联系, 因此他们决定对 8x8 块的
    DC 值的差别进行编码. (Y, Cb, Cr 分别有自己的 DC)

    Diff = DC(i) - DC(i-1)

    所以这一块的 DC(i) 就是: DC(i) = DC(i-1) + Diff

    JPG 从 0 开始对 DC 编码, 所以 DC(0)=0. 然后再将当前 Diff 值加在上一个值上得
    到当前值.

    下面再来看看上面那个例子: (记住我们保存的 DC 是和上一块 DC 的差值 Diff)

    例如上面例子中, Diff 是 -511, 就编码成

                    (9, 000000000)
    

    如果 9 的 Huffman 编码是 1111110 (在 JPG 文件中, 一般有两个 Huffman 表, 一
    个是 DC 用, 一个是 AC 用) 那么在 JPG 文件中, DC 的 2 进制表示为

               1111110 000000000
    

    它将放在 63 个 AC 的前面, 上面上个例子的最终 BIT 流如下:

    1111110 000000000 111000 111001 111000 101101 1111111110011001 10111
    11111110110 00001 1011 0111 11011 1 1010

    下面简单叙述一下针对一个数据单元的图片 Y 的解码

    在整个图片解码的开始, 你需要先初始化 DC 值为 0.

    1. 先解码 DC:
      a) 取得一个 Huffman 码 (使用 Huffman DC 表)
      b) Huffman解码, 看看后面的数据位数 N
      c) 取得 N 位, 计算 Diff 值
      d) DC + = Diff
      e) 写入 DC 值: " vector[0]=DC "

    2. 解码 63 个 AC:

    ------- 循环处理每个 AC 直到 EOB 或者处理到 64 个 AC

       a) 取得一个 Huffman 码 (使用 Huffman AC 表)
       b) Huffman 解码, 得到 (前面 0 数量, 组号)
    

    [记住: 如果是(0,0) 就是 EOB 了]

       c) 取得 N 位(组号) 计算 AC
       d) 写入相应数量的 0
       e) 接下来写入 AC
    

    下一步的解码

    上一步我们得到了 64 个矢量. 下面我们还需要做一些解码工作:

    1. 反量化 64 个矢量 : “for (i=0;i<=63;i++) vector[i]*=quant[i]” (注意防止溢出)
    2. 重排列 64 个矢量到 8x8 的块中
    3. 对 8x8 的块作 IDCT

    对 8x8 块的 (Y,Cb,Cr) 重复上面的操作 [Huffman 解码, 步骤 1), 2), 3)]

    1. 将所有的 8bit 数加上 128
    2. 转换 YCbCr 到 RGB

    JPG 文件(Byte 级)里怎样组织图片信息

    注意 JPEG/JFIF 文件格式使用 Motorola 格式, 而不是 Intel 格式, 就是说, 如果
    是一个字的话, 高字节在前, 低字节在后.

    JPG 文件是由一个个段 (segments) 构成的. 每个段长度 <=65535. 每个段从一个标
    记字开始. 标记字都是 0xff 打头的, 以非 0 字节和 0xFF 结束. 例如 ‘FFDA’ ,
    ‘FFC4’, ‘FFC0’. 每个标记有它特定意义, 这是由第2字节指明的. 例如, SOS (Start
    Of Scan = ‘FFDA’) 指明了你应该开始解码. 另一个标记 DQT (Define Quantization
    Table = 0xFFDB) 就是说它后面有 64 字节的 quantization 表

    在处理 JPG 文件时, 如果你碰到一个 0xFF, 而它后面的字节不是 0, 并且这个字节
    没有意义. 那么你遇到的 0xFF 字节必须被忽略. (一些 JPG 里, 常用用 0xFF 做某
    些填充用途) 如果你在做 huffman 编码时碰巧产生了一个 0xFF, 那么就用 0xFF
    0x00 代替. 就是说在 jpeg 图形解码时碰到 FF00 就把它当作 FF 处理.

    另外在 huffman 编码区域结束时, 碰到几个 bit 没有用的时候, 应该用 1 去填充.
    然后后面跟 FF.

    下面是几个重要的标记

    SOI = Start Of Image = ‘FFD8’
    这个标记只在文件开始出现一次
    EOI = End Of Image = ‘FFD9’
    JPG 文件都以 FFD9 结束

    RSTi = FFDi ( i = 0…7) [ RST0 = FFD0, RST7=FFD7]
    = 复位标记
    通常穿插在数据流里, 我想是担心 JPG 解码出问题吧(应该配合 DRI 使用). RST 将
    Huffman 的解码数据流复位. DC 也重新从 0 开始计

    (SOS — RST0 — RST1 – RST2 --…
    …-- RST6 — RST7 – RST0 --…)


    标记

    下面是必须处理的标记

    SOF0 = Start Of Frame 0 = FFC0
    SOS = Start Of Scan = FFDA
    APP0 = it’s the marker used to identify a JPG file which uses the JFIF
    specification = FFE0
    COM = Comment = FFFE
    DNL = Define Number of Lines = FFDC
    DRI = Define Restart Interval = FFDD
    DQT = Define Quantization Table = FFDB
    DHT = Define Huffman Table = FFC4

    JPG 文件中 Haffman 表的储存

    JPEG 里定义了一张表来描述 Haffman 树. 定义在 DHT 标记后面. 注意: Haffman
    代码的长度限制在 16bit 内.

    一般一个 JPG 文件里会有 2 类 Haffman 表: 一个用于 DC 一个用于 AC (实际有 4
    个表, 亮度的 DC,AC 两个, 色度的 DC,AC 两个)

    这张表是这样保存的:

    1. 16 字节:
      第 i 字节表示了 i 位长的 Huffman 代码的个数 (i= 1 到 16)

    2. 这表的长度 (字节数) = 这 16 个数字之和
      现在你可以想象这张表怎么存放的吧? 对应字节就是对应 Haffman 代码等价数字. 我
      不多解释, 这需要你先了解 Haffman 算法. 这里只举一个例子:

    Haffman 表的表头是 0,2,3,1,1,1,0,1,0,0,0,0,0,0,0,0
    就是说长度为 1 的代码没有
    长度为 2 的代码为 00
    01
    长度为 3 的代码是 100
    101
    110
    长度为 4 的代码是 1110
    长度为 5 的代码是 11110
    长度为 6 的代码是 111110
    长度为 7 的代码没有 (如果有一个的话应该是 1111110)
    长度为 8 的代码是 11111100

    后面都没有了.

    如果表下面的数据是
    45 57 29 17 23 25 34 28

    就是说
    45 = 00
    57 = 01
    29 = 100
    17 = 101
    23 = 110
    等等…

    如果你懂 Haffman 编码, 这些不难理解

    采样系数

    下面讲解的都是真彩 JPG 的解码, 灰度 JPG 的解码很简单, 因为图形中只有亮度信
    息. 而彩色图形由 (Y, Cr, Cb) 构成, 前面提到过, Y 通常是每点采样一次, 而 Cr,
    Cb 一般是 2x2 点采样一次, 当然也有的 JPG 是逐点采样, 或者每两点采样 (横向
    两点, 纵向一点) 采样系数均被定义成对比最高采样系数的相对值.

    一般情况 (即: Y 逐点采样, Cr Cb 每 2x2 点一次) 下: Y 有最高的采样率, 横向采
    样系数HY=2 纵向采样系数 VY=2; Cb 的横向采样系数 HCb=1, 纵向采样系数 VCb=1;
    同样 HCr=1, VCr=1

    在 Jpeg 里, 8x8 个原始数据, 经过 RLC, Huffman 编码后的一串数据流称为一个
    Data Unit (DU) JPG 里按 DU 为单位的编码次序如下:

     1)      for  (counter_y=1;counter_y<=VY;counter_y++)
                  for (counter_x=1;counter_x<=HY;counter_x++)
                     {  对 Y 的 Data Unit 编码 }
    
     2)      for  (counter_y=1;counter_y<=VCb ;counter_y++)
                  for (counter_x=1;counter_x<=HCb;counter_x++)
                     {  对 Cb 的 Data Unit 编码 }
    
     3)      for  (counter_y=1;counter_y<=VCr;counter_y++)
                  for (counter_x=1;counter_x<=HCr;counter_x++)
                     {  对 Cr 的 Data Unit 编码 }
    

    按我上面的例子: (HY=2, VY=2 ; HCb=VCb =1, HCr,VCr=1) 就是这样一个次序
    YDU,YDU,YDU,YDU,CbDU,CrDU
    这些就描述了一块 16x16 的图形. 16x16 = (Hmax8 x Vmax8) 这里 Hmax=HY=2
    Vmax=VY=2

    一个 (Hmax8,Vmax8) 的块被称作 MCU (Minimun Coded Unix) 前面例子中一个
    MCU = YDU,YDU,YDU,YDU,CbDU,CrDU

    如果 HY =1, VY=1
    HCb=1, VCb=1
    HCr=1, VCr=1
    这样 (Hmax=1,Vmax=1), MCU 只有 8x8 大, MCU = YDU,CbDU,CrDU

    对于灰度 JPG, MCU 只有一个 DU (MCU = YDU)

    JPG 文件里, 图象的每个组成部分的采样系数定义在 SOF0 (FFC0) 标记后

    简单说一下 JPG 文件的解码

    解码程序先从 JPG 文件中读出采样系数, 这样就知道了 MCU 的大小, 算出整个图象
    有几个 MCU. 解码程序再循环逐个对 MCU 解码, 一直到检查到 EOI 标记. 对于每个
    MCU, 按正规的次序解出每个 DU, 然后组合, 转换成 (R,G,B) 就 OK 了

    附:JPEG 文件格式

    
      - 文件头 (2 bytes):  $ff, $d8 (SOI) (JPEG 文件标识)
      - 任意数量的段 , 见后面
      - 文件结束 (2 bytes): $ff, $d9 (EOI)
    
    段的格式:
    ~~~~~~~~~
    
      - header (4 bytes):
           $ff     段标识
            n      段的类型 (1 byte)
           sh, sl  该段长度, 包括这两个字节, 但是不包括前面的 $ff 和 n.
                   注意: 长度不是 intel 次序, 而是 Motorola 的, 高字节在前,
           低字节在后!
      - 该段的内容, 最多 65533 字节
    
     注意:
      - 有一些无参数的段 (下面那些前面注明星号的)
        这些段没有长度描述 (而且没有内容), 只有 $ff 和类型字节.
      - 段之间无论有多少 $ff 都是合法的, 必须被忽略掉.
    
    段的类型:
    ~~~~~~~~~
    
       *TEM   = $01   可以忽略掉
    
        SOF0  = $c0   帧开始 (baseline JPEG), 细节附后
        SOF1  = $c1   dito
        SOF2  = $c2   通常不支持
        SOF3  = $c3   通常不支持
    
        SOF5  = $c5   通常不支持
        SOF6  = $c6   通常不支持
        SOF7  = $c7   通常不支持
    
        SOF9  = $c9   arithmetic 编码(Huffman 的一种扩展算法), 通常不支持
        SOF10 = $ca   通常不支持
        SOF11 = $cb   通常不支持
    
        SOF13 = $cd   通常不支持
        SOF14 = $ce   通常不支持
        SOF14 = $ce   通常不支持
        SOF15 = $cf   通常不支持
    
        DHT   = $c4   定义 Huffman Table,  细节附后
        JPG   = $c8   未定义/保留 (引起解码错误)
        DAC   = $cc   定义 Arithmetic Table, 通常不支持
    
       *RST0  = $d0   RSTn 用于 resync, 通常被忽略
       *RST1  = $d1
       *RST2  = $d2
       *RST3  = $d3
       *RST4  = $d4
       *RST5  = $d5
       *RST6  = $d6
       *RST7  = $d7
    
        SOI   = $d8   图片开始
        EOI   = $d9   图片结束
        SOS   = $da   扫描行开始, 细节附后
        DQT   = $db   定义 Quantization Table, 细节附后
        DNL   = $dc   通常不支持, 忽略
        DRI   = $dd   定义重新开始间隔, 细节附后
        DHP   = $de   忽略 (跳过)
        EXP   = $df   忽略 (跳过)
    
        APP0  = $e0   JFIF APP0 segment marker (细节略)
        APP15 = $ef   忽略
    
        JPG0  = $f0   忽略 (跳过)
        JPG13 = $fd   忽略 (跳过)
        COM   = $fe   注释, 细节附后
    
     其它的段类型都保留必须跳过
    
    SOF0: Start Of Frame 0:
    
    • $ff, $c0 (SOF0)
    • 长度 (高字节, 低字节), 8+components*3
    • 数据精度 (1 byte) 每个样本位数, 通常是 8 (大多数软件不支持 12 和 16)
    • 图片高度 (高字节, 低字节), 如果不支持 DNL 就必须 >0
    • 图片宽度 (高字节, 低字节), 如果不支持 DNL 就必须 >0
    • components 数量(1 byte), 灰度图是 1, YCbCr/YIQ 彩色图是 3, CMYK 彩色图
      是 4
    • 每个 component: 3 bytes
      • component id (1 = Y, 2 = Cb, 3 = Cr, 4 = I, 5 = Q)
      • 采样系数 (bit 0-3 vert., 4-7 hor.)
      • quantization table 号

    DRI: Define Restart Interval:

    
      - $ff, $dd (DRI)
      - 长度 (高字节, 低字节), 必须是 4
      - MCU 块的单元中的重新开始间隔 (高字节, 低字节),
        意思是说, 每 n 个 MCU 块就有一个 RSTn 标记.
        第一个标记是 RST0, 然后是 RST1 等, RST7 后再从 RST0 重复 
    
    DQT: Define Quantization Table:
    
    • $ff, $db (DQT)
    • 长度 (高字节, 低字节)
    • QT 信息 (1 byte):
      bit 0…3: QT 号(0…3, 否则错误)
      bit 4…7: QT 精度, 0 = 8 bit, 否则 16 bit
    • n 字节的 QT, n = 64*(精度+1)

    评论:

    • 一个单独的 DQT 段可以包含多个 QT, 每个都有自己的信息字节
    • 当精度=1 (16 bit), 每个字都是高位在前低位在后

    DAC: Define Arithmetic Table:

     法律原因, 现在的软件不支持 arithmetic 编码.
     不能生产使用 arithmetic 编码的 JPEG 文件
    
    DHT: Define Huffman Table:
    ~~~~~~~~~~~~~~~~~~~~~~~~~~
    
      - $ff, $c4 (DHT)
      - 长度 (高字节, 低字节)
      - HT 信息 (1 byte):
         bit 0..3: HT 号 (0..3, 否则错误)
         bit 4   : HT 类型, 0 = DC table, 1 = AC table
         bit 5..7: 必须是 0
      - 16 bytes: 长度是 1..16 代码的符号数. 这 16 个数的和应该 <=256
      - n bytes: 一个包含了按递增次序代码长度排列的符号表
        (n = 代码总数)
    
     评论:
      - 一个单独的 DHT 段可以包含多个 HT, 每个都有自己的信息字节
    
    COM: 注释:
    ~~~~~~~~~~
    
      - $ff, $fe (COM)
      - 注释长度 (高字节, 低字节) = L+2
      - 注释为长度为 L 的字符流
    
    SOS: Start Of Scan:
    ~~~~~~~~~~~~~~~~~~~
    
      - $ff, $da (SOS)
      - 长度 (高字节, 低字节), 必须是 6+2*(扫描行内组件的数量)
      - 扫描行内组件的数量 (1 byte), 必须 >= 1 , <=4 (否则是错的) 通常是 3
      - 每个组件: 2 bytes
         - component id (1 = Y, 2 = Cb, 3 = Cr, 4 = I, 5 = Q), 见 SOF0
         - 使用的 Huffman 表:
    - bit 0..3: AC table (0..3)
    - bit 4..7: DC table (0..3)
      - 忽略 3 bytes (???)
    
     评论:
      - 图片数据 (一个个扫描行) 紧接着 SOS 段.
    
    展开全文
  • JPEG 编码

    2013-11-11 16:13:00
    WIN8. DNJXJ-7XBW8-2378T-X22TX-BKG7J 模板:类的宏,泛型,甜饼切割机 类模板:泛型类; 函数模板:泛型函数 STL standard template Library ...大家都知道面向对象程序设计有三个特点:封装、继承、...

     

    WIN8.

    DNJXJ-7XBW8-2378T-X22TX-BKG7J

     

     

     

    模板:类的宏,泛型,甜饼切割机

    类模板:泛型类;

    函数模板:泛型函数

    STL standard template Library

    容器: vector set map multimap deque

    vector 基本数组模板

     

     

    大家都知道面向对象程序设计有三个特点:封装、继承、多态。多态在面向对象程序设计中起着举足轻重的作用。

    上述的多态是如何实现的呢?通常是有一个基类,它包含了一些特定的接口,而该类的子类重载了这些接口;使用基类的指针或者引用指向子类的对象,那么就可以实现调用子类对应的函数的功能;此种现象被称为多态。

    上述多态有哪些特点呢?

    1.它是绑定的。即有一个基类,其中存在一些接口,子类必须重载这些接口,这就是绑定的。

    2.它是动态的。这些函数调用机制是执行期才能进行确定,所以是动态的。

    具有上述两种特性的多态我们可以给它个新的名称,动多态。那么是否存在一种多态叫静多态?确实有静多态。

    我想大家肯定已经猜到了静多态的特点了:

    1.它是非绑定的:因为采用模板机制,所以没有所谓的基类,所以就不需要绑定。

    2.它是静态的:因为不采用虚函数机制,所以所有调用在编译期就可确定,所以是静态的。

    静多态如何实现呢?

     

    此时您可能认为这根本不是什么多态,只不过是模板函数罢了。那么好吧,我再举一个例子。

    我们知道在桥模式(参见《设计模式》一书)中所说桥模式一般采用如下的方式实现:

    577x315

     

    而如果采用模板的方式那就会很简单:

    Template Class Interface {

    Private:

      T body;

    Public:

    … }

    只需要传递不同的参数implementation A或者implementation B就可以了。

    我好像听到你说“但是…”,是的采用这种方式实现桥模式之后有它的好处也有它的缺点。不过通过这个例子使我们多了一种选择,那就是静多态。

    经过上面的说明和举例,我想大家已经基本明白什么是静多态了。我再一次强调把上面那种类似我们通常理解的多态的实现方法称为静多态

    静多态有以下优点:

    1.类型安全的。因为会在编译期进行代码的检查,所以比动多态要安全。

    2.效率要高。因为不需要执行期的决议,所以效率比较高

    静多态有以下缺点:

    1.不能够处理异类的集合。

    2.没有动多态灵活(在上面桥静多态实现的桥模式中不能够动态改变实现)。

    我们可以结合动多态和静多态来获得一个很好的效率和灵活性。


     

    查看文章

     

     

     

    c++的静态多态和动态多态(笔记)

    2010-01-03 21:16

     

    多态(polymorphism)一词最初来源于希腊语polumorphos,含义是具有多种形式或形态的情形。在程序设计领域,一个广泛认可的定义是“一种将不同的特殊行为和单个泛化记号相关联的能力”。和纯粹的面向对象程序设计语言不同,C++中的多态有着更广泛的含义。除了常见的通过类继承和虚函数机制生效于运行期的动态多态(dynamic   polymorphism)外,模板也允许将不同的特殊行为和单个泛化记号相关联,由于这种关联处理于编译期而非运行期,因此被称为      静态多态(static polymorphism)。
          事实上,带变量的宏和函数重载机制也允许将不同的特殊行为和单个泛化记号相关联。然而,习惯上我们并不将它们展现出来的行为称为多态(或静态多态)。今天,当我们谈及多态时,如果没有明确所指,默认就是动态多态,而静态多态则是指基于模板的多态。不过,在这篇以C++各种多态技术为主题的文章中,我们首先还是回顾一下C++社群争论已久的另一种“多态”:函数多态(function   polymorphism),以及更不常提的“宏多态(macro   polymorphism)”。

    C++支持多种风格的编程模式 称之为编程范型 C++支持的编程范型包括面向过程的 基于对象的 面向对象的和泛型编程

    通过指针和引用来支持多态 是面向对象的编程范型区  别于基于对象的编程范型的本质所在  所谓多态  是指通过  单一的标识支持不同的特定行为的能力 C++支持多种形式  的多态 从绑定时间来看 可以分成静态多态和动态多态  也称为编译期多态和运行期多态 从表现的形式来看 有虚  函数 模板 重载和转换[1]  由于静态多态在时间和空间上都比动态多态表现得好  因此在其他的条件相同的情况下 应该更多地使用静态多态
     
      函数多态
     
      也就是我们常说的函数重载(function overloading)。基于不同的参数列表,同一个函数名字可以指向不同的函数定义:
     
      // overload_poly.cpp
     
      #include <iostream>
      #include <string>
     
      // 定义两个重载函数
     
      int my_add(int a, int b)
      {
          return a + b;
      }
     
      int my_add(int a, std::string b)
      {
          return a + atoi(b.c_str());
      }
     
      int main()
      {
          int i = my_add(1,   2);                  // 两个整数相加
          int s = my_add(1,   "2");                // 一个整数和一个字符串相加
          std::cout << "i = " << i <<   "\n";
          std::cout << "s = " << s <<   "\n";
      }
     
      根据参数列表的不同(类型、个数或兼而有之),my_add(1, 2)和my_add(1, "2")被分别编译为对my_add(int,   int)和my_add(int, std::string)的调用。实现原理在于编译器根据不同的参数列表对同名函数进行名字重整,而后这些同名函数就变成了彼此不同的函数。比方说,也许某个编译器会将my_add()函数名字分别重整为my_add_int_int()和my_add_int_str()。
     
      宏多态
     
      带变量的宏可以实现一种初级形式的静态多态:
      // macro_poly.cpp
     
      #include <iostream>
      #include <string>
     
      // 定义泛化记号:宏ADD
      #define ADD(A, B) (A) + (B);
     
      int main()
      {
          int i1(1), i2(2);
          std::string s1("Hello, "),   s2("world!");
          int i = ADD(i1,   i2);                          // 两个整数相加
          std::string s = ADD(s1, s2);                  // 两个字符串“相加”
          std::cout << "i = " << i <<   "\n";
          std::cout << "s = " << s <<   "\n";
      }
      当程序被编译时,表达式ADD(i1, i2)和ADD(s1,   s2)分别被替换为两个整数相加和两个字符串相加的具体表达式。整数相加体现为求和,而字符串相加则体现为连接(注:string.h库已经重载了“+”)。程序的输出结果符合直觉:
      1 + 2 = 3
      Hello, + world! = Hello, world!
     
      动态多态
     
      这就是众所周知的的多态。现代面向对象语言对这个概念的定义是一致的。其技术基础在于继承机制和虚函数。例如,我们可以定义一个抽象基类Vehicle和两个派生于Vehicle的具体类Car和Airplane:
     
      // dynamic_poly.h
     
      #include <iostream>
     
      // 公共抽象基类Vehicle
      class Vehicle
      {
      public:
          virtual void run() const = 0;
      };
     
      // 派生于Vehicle的具体类Car
      class Car: public Vehicle
      {
      public:
          virtual void run() const
          {
              std::cout << "run a   car\n";
          }
      };
     
      // 派生于Vehicle的具体类Airplane
      class Airplane: public Vehicle
      {
      public:
          virtual void run() const
          {
              std::cout << "run a airplane\n";
          }
      };
      客户程序可以通过指向基类Vehicle的指针(或引用)(注意:此处应该是指向派生类的指针(或引用)来操纵具体对象。通过指向基类对象的指针(或引用)(注意:此处应该是指向派生类的指针(或引用)来调用一个虚函数,会导致对被指向的具体对象之相应成员的调用:
     
      // dynamic_poly_1.cpp
     
      #include <iostream>
      #include <vector>
      #include "dynamic_poly.h"
     
      // 通过指针run任何vehicle
      void run_vehicle(const Vehicle* vehicle)
      {
            vehicle->run();              // 根据vehicle的具体类型调用对应的run()
      }
     
      int main()
      {
          Car car;
          Airplane airplane;
            run_vehicle(&car);         // 调用Car::run()
          run_vehicle(&airplane);    // 调用Airplane::run()
      }
     
      此例中,关键的多态接口元素为虚函数run()。由于run_vehicle()的参数为指向基类Vehicle的指针,因而无法在编译期决定使用哪一个版本的run()。在运行期,为了分派函数调用,虚函数被调用的那个对象的完整动态类型将被访问。这样一来,对一个Car对象调用run_vehicle(),实际上将调用Car::run(),而对于Airplane对象而言将调用Airplane::run()。
      或许动态多态最吸引人之处在于处理异质对象集合的能力:
     
      // dynamic_poly_2.cpp
     
      #include <iostream>
      #include <vector>
      #include "dynamic_poly.h"
     
      // run异质vehicles集合
      void run_vehicles(const std::vector<Vehicle*>& vehicles)
      {
          for (unsigned int i = 0; i < vehicles.size(); ++i)
          {
                vehicles[i]->run();     // 根据具体vehicle的类型调用对应的run()
          }
      }
     
      int main()
      {
          Car car;
          Airplane airplane;
          std::vector<Vehicle*> v;    // 异质vehicles集合
          v.push_back(&car);
          v.push_back(&airplane);
            run_vehicles(v);              // run不同类型的vehicles
      }
      在run_vehicles()中,vehicles[i]->run()依据正被迭代的元素的类型而调用不同的成员函数。这从一个侧面体现了面向对象编程风格的优雅。
     
      静态多态
     
      如果说动态多态是通过虚函数来表达共同接口的话,那么静态多态则是通过“彼此单独定义但支持共同操作的具体类”来表达共同性,换句话说,必须存在必需的同名成员函数。
      我们可以采用静态多态机制重写上一节的例子。这一次,我们不再定义vehicles类层次结构,相反,我们编写彼此无关的具体类Car和Airplane(它们都有一个run()成员函数):
     
      // static_poly.h
     
      #include <iostream>
     
      //具体类Car
      class Car
      {
      public:
          void run() const
          {
              std::cout << "run a   car\n";
          }
      };
     
      //具体类Airplane
      class Airplane
      {
      public:
          void run() const
          {
              std::cout << "run a   airplane\n";
          }
      };
     
      run_vehicle()应用程序被改写如下:
     
      // static_poly_1.cpp
     
      #include <iostream>
      #include <vector>
      #include "static_poly.h"
     
      // 通过引用而run任何vehicle
      template <typename Vehicle>
      void run_vehicle(const Vehicle& vehicle)
      {
            vehicle.run();              // 根据vehicle的具体类型调用对应的run()
      }
     
      int main()
      {
          Car car;
          Airplane airplane;
            run_vehicle(car);         // 调用Car::run()
          run_vehicle(airplane);    // 调用Airplane::run()
      }
      现在Vehicle用作模板参数而非公共基类对象(事实上,这里的Vehicle只是一个符合直觉的记号而已,此外别无它意)。经过编译器处理后,我们最终会得到run_vehicle<Car>()和   run_vehicle<Airplane>()两个不同的函数。这和动态多态不同,动态多态凭借虚函数分派机制在运行期只有一个run_vehicle()函数。
      我们无法再透明地处理异质对象集合了,因为所有类型都必须在编译期予以决定。不过,为不同的vehicles引入不同的集合只是举手之劳。由于无需再将集合元素局限于指针或引用,我们现在可以从执行性能和类型安全两方面获得好处:
     
      // static_poly_2.cpp
     
      #include <iostream>
      #include <vector>
      #include "static_poly.h"
     
      // run同质vehicles集合
      template <typename Vehicle>
      void run_vehicles(const std::vector<Vehicle>& vehicles)
      {
          for (unsigned int i = 0; i < vehicles.size(); ++i)
          {
              vehicles[i].run();              // 根据vehicle的具体类型调用相应的run()
          }
      }
     
      int main()
      {
          Car car1, car2;
          Airplane airplane1, airplane2;
     
          std::vector<Car>   vc;                // 同质cars集合
          vc.push_back(car1);
          vc.push_back(car2);
            //vc.push_back(airplane1);        // 错误:类型不匹配
            run_vehicles(vc);                   // run cars
     
          std::vector<Airplane>   vs;         // 同质airplanes集合
          vs.push_back(airplane1);
          vs.push_back(airplane2);
            //vs.push_back(car1);               // 错误:类型不匹配
            run_vehicles(vs);                   // run airplanes
      }
     
      两种多态机制的结合使用
     
      在一些高级C++应用中,我们可能需要结合使用动态多态和静态多态两种机制,以期达到对象操作的优雅、安全和高效。例如,我们既希望一致而优雅地处理vehicles的run问题,又希望“安全而高效”地完成给飞行器(飞机、飞艇等)进行“空中加油”这样的高难度动作。为此,我们首先将上面的vehicles类层次结构改写如下:
     
      // dscombine_poly.h
     
      #include <iostream>
      #include <vector>
     
      // 公共抽象基类Vehicle
      class Vehicle
      {
          public:
          virtual void run() const = 0;
      };
     
      // 派生于Vehicle的具体类Car
      class Car: public Vehicle
      {
      public:
          virtual void run() const
          {
              std::cout << "run a   car\n";
          }
      };
     
      // 派生于Vehicle的具体类Airplane
      class Airplane: public Vehicle
      {
      public:
          virtual void run() const
          {
              std::cout << "run a   airplane\n";
          }
     
          void add_oil() const
          {
              std::cout << "add oil   to airplane\n";
          }
      };
     
      // 派生于Vehicle的具体类Airship
      class Airship: public Vehicle
      {
      public:
          virtual void run() const
          {
              std::cout << "run a   airship\n";
          }
        
          void add_oil() const
          {
              std::cout << "add oil   to airship\n";
          }
      };
     
      我们理想中的应用程序可以编写如下:
     
      // dscombine_poly.cpp
     
      #include <iostream>
      #include <vector>
      #include "dscombine_poly.h"
     
      // run异质vehicles集合
      void run_vehicles(const std::vector<Vehicle*>& vehicles)
      {
          for (unsigned int i = 0; i < vehicles.size(); ++i)
          {
                vehicles[i]->run();                   // 根据具体的vehicle类型调用对应的run()
          }
      }
     
      // 为某种特定的aircrafts同质对象集合进行“空中加油”
      template <typename Aircraft>
      void add_oil_to_aircrafts_in_the_sky(const std::vector<Aircraft>&   aircrafts)
      {
          for (unsigned int i = 0; i < aircrafts.size(); ++i)
          {
              aircrafts[i].add_oil();
          }
      }
     
      int main()
      {
          Car car1, car2;
          Airplane airplane1, airplane2;
     
          Airship airship1, airship2;
          std::vector<Vehicle*>   v;                  // 异质vehicles集合
          v.push_back(&car1);
          v.push_back(&airplane1);
          v.push_back(&airship1);
            run_vehicles(v);                          // run不同种类的vehicles
     
          std::vector<Airplane>   vp;                 // 同质airplanes集合
          vp.push_back(airplane1);
          vp.push_back(airplane2);
          add_oil_to_aircrafts_in_the_sky(vp);    // 为airplanes进行“空中加油”
     
          std::vector<Airship>   vs;                  // 同质airships集合
          vs.push_back(airship1);
          vs.push_back(airship2);
          add_oil_to_aircrafts_in_the_sky(vs);    // 为airships进行“空中加油”
      }
     
      我们保留了类层次结构,目的是为了能够利用run_vehicles()一致而优雅地处理异质对象集合vehicles的run问题。同时,利用函数模板add_oil_to_aircrafts_in_the_sky<Aircraft>(),我们仍然可以处理特定种类的vehicles — aircrafts(包括airplanes和airships)的“空中加油”问题。其中,我们避开使用指针,从而在执行性能和类型安全两方面达到了预期目标。
     
      结语
     
      长期以来,C++社群对于多态的内涵和外延一直争论不休。在comp.object这样的网络论坛上,此类话题争论至今仍随处可见。曾经有人将动态多态(dynamic polymorphism)称为inclusion   polymorphism,而将静态多态(static polymorphism)称为parametric polymorphism或parameterized   polymorphism。
     
      我注意到2003年斯坦福大学公开的一份C++ and   Object-Oriented Programming教案中明确提到了函数多态概念:Function   overloading is also referred to as function polymorphism as it involves one   function having many forms。文后的“参考文献”单元给出了这个网页链接。
     
      可能你是第一次看到宏多态(macro polymorphism)这个术语。不必讶异 — 也许我就是造出这个术语的“第一人”。显然,带变量的宏(或类似于函数的宏或伪函数宏)的替换机制除了免除小型函数的调用开销之外,也表现出了类似的多态性。在我们上面的例子中,字符串相加所表现出来的符合直觉的连接操作,事实上是由底部运算符重载机制(operator overloading)支持的。值得指出的是,C++社群中有人将运算符重载所表现出来的多态称为ad hoc polymorphism。
     
      David Vandevoorde和Nicolai M. Josuttis在他们的著作C++ Templates: The Complete Guide一书中系统地阐述了静态多态和动态多态技术。因为认为“和其他语言机制关系不大”,这本书没有提及“宏多态”(以及“函数多态”)。(需要说明的是,笔者本人是这本书的繁体中文版译者之一,本文正是基于这本书的第14章The Polymorphic Power of Templates编写而成)
     
      动态多态只需要一个多态函数,生成的可执行代码尺寸较小,静态多态必须针对不同的类型产生不同的模板实体,尺寸会大一些,但生成的代码会更快,因为无需通过指针进行间接操作。

    静态多态比动态多态更加类型安全,因为全部绑定都被检查于编译期。正如前面例子所示,你不可将一个错误的类型的对象插入到从一个模板实例化而来的容器之中。此外,正如你已经看到的那样,动态多态可以优雅地处理异质对象集合,而静态多态可以用来实现安全、高效的同质对象集合操作。
     
      静态多态为C++带来了泛型编程(generic   programming)的概念。泛型编程可以认为是“组件功能基于框架整体而设计”的模板编程。STL就是泛型编程的一个典范。STL是一个框架,它提供了大量的算法、容器和迭代器,全部以模板技术实现。从理论上讲,STL的功能当然可以使用动态多态来实现,不过这样一来其性能必将大打折扣。
     
      静态多态还为C++社群带来了泛型模式(generic   patterns)的概念。理论上,每一个需要通过虚函数和类继承而支持的设计模式都可以利用基于模板的静态多态技术(甚至可以结合使用动态多态和静态多态两种技术)而实现。正如你看到的那样,Andrei Alexandrescu的天才作品Modern C++ Design:   Generic Programming and Design Patterns Applied(Addison-Wesley)和Loki程序库已经走在了我们的前面。

     

    转载于:https://www.cnblogs.com/iamgoodman/p/3418091.html

    展开全文
  • jpeg:jpeg编码和解码
  • JPEG编码总结文档

    2018-09-05 17:01:51
    Jpeg编码总结文档,快速了解jpeg编码原理,详细介绍jpeg
  • JPEG 编码采样

    2020-07-24 16:28:26
    JPEG 编码采样 ImageSharp源码详解之JPEG编码原理(2)采样

    JPEG 编码采样

    概念

    component:通道,比如YUV, rgb
    MCU: 最小编码单元,如果压缩图像数据是非交错的,MCU就是DU, 如果压缩图像是交错的,MCU由来自不同通道的一个或多个DU组成。
    DU: 数据单元 8 * 8 像素的data block
    在这里插入图片描述
    Image:一副压缩图像只有一个Image,一个Image 在顺序编码模式中只有一个Frame
    Frame:一个Frame 由一个或多个Scan组成
    Scan:Scan包含一个或者多个通道的全部编码

    MCU,DU的关系

    对于非交错模式,MCU 与DU相同。当是交错数据时,MCU是一个DU序列,他的顺序由这个Scan中通道的采样因子决定。

    关于JPEG编码中MCU,DU与采样的体会

    非交错顺序(Ns =1)

    当Ns=1时,Scan中的数据单元顺序是从左到右并从上到下,不管H1,V1的值如何,都是这个顺序。
    在这里插入图片描述

    交错顺序(Ns > 1)

    在交错模式中,每一个扫描通道数据段Csi是由DU组成的矩形数组,它的行由通道的Vk决定,列由Hk决定。下标k表示这个(Hk,Vk)对应于Csk. 对于每一个Hk,Vk 数组,数据的顺序都是以DU为单位从左到右,从上到下。此时一个MCU由顺序的Csi数据段组成,Cs1,Cs2,Cs3…
    如下图所示,Ns = 4, MCU1 的组成,由Cs1中H1 V1 块取得的数据,接下来是Cs2中H2 V2块中取得的数据,Cs3中H3 V3块中取得的数据,最后是Cs4 H4 V4块中取得的数据。MCU2的顺序也是如此。
    在这里插入图片描述

    ImageSharp源码详解之JPEG编码原理(2)采样

    展开全文
  • jpeg编码实现

    2018-03-10 22:17:02
    是一个课程作业,在vs2013下写的一个完整jpeg编码程序,包含了完整的压缩过程从图像分块到最后三个通道分别被压缩为二进制码,但是不包含解码过程,用了几个opencv2.4.9中读取存储图像的函数
  • JPEG 编码

    2013-02-26 11:11:19
    JPEG 编码技术,本文详细介绍了JPEG编码器在FPGA上的实现
  • DCT与JPEG编码

    2018-11-22 15:09:22
    PPT讲述了JPEG编码中DCT变换的理论推导和实际应用。。。
  • CUDA JPEG编码

    2019-07-01 19:17:33
    CUDA JPEG编码 https://www.cnblogs.com/betterwgo/p/7300920.html 基于英伟达的jpegNPP工程,分离实现独立的JPEG压缩。 由于原工程是直接把解码时的jpeg图片的信息直接作为编码时的信息,所以在做独立的JPEG...

    CUDA JPEG编码

    https://www.cnblogs.com/betterwgo/p/7300920.html

        基于英伟达的jpegNPP工程,分离实现独立的JPEG压缩。

        由于原工程是直接把解码时的jpeg图片的信息直接作为编码时的信息,所以在做独立的JPEG编码时,需要自己来填充各种信息。

     

    1.JPEG编码流程

    JPEG基本编码系统

        从网上一片文章中解出来的图,红色框框中的流程图算是JPEG编码的一个流程图,对JPEG编码流程的了解有助于对代码的理解。

     

    2.Huffman表和量化表

        Huffman表和量化表采用标准Huffman表和标准量化表。

    复制代码

    //标准亮度信号量化模板
    static unsigned char std_Y_QT[64] = 
    {
        16, 11, 10, 16, 24, 40, 51, 61,
        12, 12, 14, 19, 26, 58, 60, 55,
        14, 13, 16, 24, 40, 57, 69, 56,
        14, 17, 22, 29, 51, 87, 80, 62,
        18, 22, 37, 56, 68, 109,103,77,
        24, 35, 55, 64, 81, 104,113,92,
        49, 64, 78, 87, 103,121,120,101,
        72, 92, 95, 98, 112,100,103,99
    };
    
    //标准色差信号量化模板
    static unsigned char std_UV_QT[64] = 
    {
        17, 18, 24, 47, 99, 99, 99, 99,
        18, 21, 26, 66, 99, 99, 99, 99,
        24, 26, 56, 99, 99, 99, 99, 99,
        47, 66, 99 ,99, 99, 99, 99, 99,
        99, 99, 99, 99, 99, 99, 99, 99,
        99, 99, 99, 99, 99, 99, 99, 99,
        99, 99, 99, 99, 99, 99, 99, 99,
        99, 99, 99, 99, 99, 99, 99, 99
    };
    
    // 标准Huffman表 (cf. JPEG standard section K.3) 
    static unsigned char STD_DC_Y_NRCODES[17]={0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0};
    static unsigned char STD_DC_Y_VALUES[12] ={0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
    
    static unsigned char STD_DC_UV_NRCODES[17]={0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0};
    static unsigned char STD_DC_UV_VALUES[12] ={0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
    
    static unsigned char STD_AC_Y_NRCODES[17]={0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0X7D};
    static unsigned char STD_AC_Y_VALUES[162]= 
    {
        0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12,
        0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
        0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08,
        0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0,
        0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16,
        0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28,
        0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
        0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
        0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
        0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
        0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
        0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
        0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,
        0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
        0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
        0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5,
        0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4,
        0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
        0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
        0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
        0xf9, 0xfa 
    };
    
    static unsigned char STD_AC_UV_NRCODES[17]={0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0X77};
    static unsigned char STD_AC_UV_VALUES[162]=
    {
        0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21,
        0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71,
        0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91,
        0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0,
        0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34,
        0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26,
        0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38,
        0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
        0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
        0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
        0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
        0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
        0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96,
        0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5,
        0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4,
        0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3,
        0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2,
        0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
        0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,
        0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
        0xf9, 0xfa 
    };

    复制代码

     

    2.RGB转YUV

        编码的输入时YUV数据,所以对于原始数据需要先把RGB转成YUV。要说的是,我这里输入的RGB数据的存储形式为BBBBBBB……….GGGGGGG………RRRRRRR…..,其他形式的RGB数据需要对kernel函数中的坐标做点调整。

        RGB转YUV的公式网上有许多中,我这里采用:

        Y = 0.299R + 0.587G + 0.114B  
        U = -0.147R - 0.289G + 0.436B
        V = 0.615R - 0.515G - 0.100B
    
     
    代码:

    复制代码

    #include "cuda_kernels.h"
    
    typedef unsigned char   uint8;
    typedef unsigned int    uint32;
    typedef int             int32;
    
    namespace cuda_common
    {
        __device__ unsigned char clip_value(unsigned char x, unsigned char min_val, unsigned char  max_val){
            if (x>max_val){
                return max_val;
            }
            else if (x<min_val){
                return min_val;
            }
            else{
                return x;
            }
        }
    
        extern "C"
        __global__ void kernel_rgb2yuv(float *src_img, unsigned char* Y, unsigned char* u, unsigned char* v,
            int src_width, int src_height, size_t yPitch)
        {
            const int x = blockIdx.x * blockDim.x + threadIdx.x;
            const int y = blockIdx.y * blockDim.y + threadIdx.y;
    
            if (x >= src_width)
                return; //x = width - 1;
    
            if (y >= src_height)
                return; // y = height - 1;
    
            float B = src_img[y * src_width + x];
            float G = src_img[src_width * src_height + y * src_width + x];
            float R = src_img[src_width * src_height * 2 + y * src_width + x];
    
            Y[y * yPitch + x] = clip_value((unsigned char)(0.299 * R + 0.587 * G + 0.114 * B), 0, 255);
            u[y * src_width + x] = clip_value((unsigned char)(-0.147 * R - 0.289 * G + 0.436 * B + 128), 0, 255);
            v[y * src_width + x] = clip_value((unsigned char)(0.615 * R - 0.515 * G - 0.100 * B + 128), 0, 255);
    
            //Y[y * yPitch + x] = clip_value((unsigned char)(0.257 * R + 0.504 * G + 0.098 * B + 16), 0, 255);
            //u[y * src_width + x] = clip_value((unsigned char)(-0.148 * R - 0.291 * G + 0.439 * B + 128), 0, 255);
            //v[y * src_width + x] = clip_value((unsigned char)(0.439 * R - 0.368 * G - 0.071 * B + 128), 0, 255);
        }
    
        extern "C"
        __global__ void kernel_resize_UV(unsigned char* src_img, unsigned char *dst_img,
            int src_width, int src_height, int dst_width, int dst_height, int nPitch)
        {
            const int x = blockIdx.x * blockDim.x + threadIdx.x;
            const int y = blockIdx.y * blockDim.y + threadIdx.y;
    
            if (x >= dst_width)
                return; //x = width - 1;
    
            if (y >= dst_height)
                return; // y = height - 1;
    
            float fx = (x + 0.5)*src_width / (float)dst_width - 0.5;
            float fy = (y + 0.5)*src_height / (float)dst_height - 0.5;
            int ax = floor(fx);
            int ay = floor(fy);
            if (ax < 0)
            {
                ax = 0;
            }
            else if (ax > src_width - 2)
            {
                ax = src_width - 2;
            }
    
            if (ay < 0){
                ay = 0;
            }
            else if (ay > src_height - 2)
            {
                ay = src_height - 2;
            }
    
            int A = ax + ay*src_width;
            int B = ax + ay*src_width + 1;
            int C = ax + ay*src_width + src_width;
            int D = ax + ay*src_width + src_width + 1;
    
            float w1, w2, w3, w4;
            w1 = fx - ax;
            w2 = 1 - w1;
            w3 = fy - ay;
            w4 = 1 - w3;
    
            unsigned char val = src_img[A] * w2*w4 + src_img[B] * w1*w4 + src_img[C] * w2*w3 + src_img[D] * w1*w3;
    
            dst_img[y * nPitch + x] = clip_value(val,0,255);
        }
    
        cudaError_t RGB2YUV(float* d_srcRGB, int src_width, int src_height,
                            unsigned char* Y, size_t yPitch, int yWidth, int yHeight,
                            unsigned char* U, size_t uPitch, int uWidth, int uHeight,
                            unsigned char* V, size_t vPitch, int vWidth, int vHeight)
        {
            unsigned char * u ;
            unsigned char * v ;
    
            cudaError_t cudaStatus;
    
            cudaStatus = cudaMalloc((void**)&u, src_width * src_height * sizeof(unsigned char));
            cudaStatus = cudaMalloc((void**)&v, src_width * src_height * sizeof(unsigned char));
    
            dim3 block(32, 16, 1);
            dim3 grid((src_width + (block.x - 1)) / block.x, (src_height + (block.y - 1)) / block.y, 1);
            dim3 grid1((uWidth + (block.x - 1)) / block.x, (uHeight + (block.y - 1)) / block.y, 1);
            dim3 grid2((vWidth + (block.x - 1)) / block.x, (vHeight + (block.y - 1)) / block.y, 1);
    
            kernel_rgb2yuv << < grid, block >> >(d_srcRGB, Y, u, v, src_width, src_height, yPitch);
    
            cudaStatus = cudaGetLastError();
            if (cudaStatus != cudaSuccess) {
                fprintf(stderr, "kernel_rgb2yuv launch failed: %s\n", cudaGetErrorString(cudaStatus));
                goto Error;
            }
    
            cudaStatus = cudaDeviceSynchronize();
            if (cudaStatus != cudaSuccess) {
                fprintf(stderr, "cudaDeviceSynchronize returned error code %d after launching kernel_rgb2yuv!\n", cudaStatus);
                goto Error;
            }
    
            kernel_resize_UV << < grid1, block >> >(u, U, src_width, src_height, uWidth, uHeight, uPitch);
    
            cudaStatus = cudaGetLastError();
            if (cudaStatus != cudaSuccess) {
                fprintf(stderr, "kernel_resize_UV launch failed: %s\n", cudaGetErrorString(cudaStatus));
                goto Error;
            }
    
            cudaStatus = cudaDeviceSynchronize();
            if (cudaStatus != cudaSuccess) {
                fprintf(stderr, "cudaDeviceSynchronize returned error code %d after launching kernel_resize_UV!\n", cudaStatus);
                goto Error;
            }
    
            kernel_resize_UV << < grid2, block >> >(v, V, src_width, src_height, vWidth, vHeight, vPitch);
    
            cudaStatus = cudaGetLastError();
            if (cudaStatus != cudaSuccess) {
                fprintf(stderr, "kernel_resize_UV launch failed: %s\n", cudaGetErrorString(cudaStatus));
                goto Error;
            }
    
            cudaStatus = cudaDeviceSynchronize();
            if (cudaStatus != cudaSuccess) {
                fprintf(stderr, "cudaDeviceSynchronize returned error code %d after launching kernel_resize_UV!\n", cudaStatus);
                goto Error;
            }
    
    Error :
            cudaFree(u);
            cudaFree(v);
    
            return cudaStatus;
        }
    }

    复制代码

    RGB转码分了两步,第一步是按照RGB转YUV公式做转换,由kernel_rgb2yuv函数来完成;第二部把U、V的宽高缩小到原来的一半,由kernel_resize_UV函数来做,这个地方会影响到最终压缩后的图片的效果,我这里用的是一个双线性插值来缩小的,效果凑合,但和原图一比较还是能看出明显的色差,网上查到怎么缩小U、V的,这里的做法可能与标准做法不符,有待更好的方法。

     

    3.JPEG压缩

        jpegNPP工程包含了一个解码然后编码的过程,要分离出解码的话把编码部分去掉就行了,应该来说是比较容易的;要分离出编码部分就有点困难了,因为工程中编码所使用的参数是直接使用的解码读取到的图片的参数,所以要分离出编码,就需要自己来填充这些参数,这对于不懂JPEG压缩的来说就有点难度了,好在网上资料多。

    复制代码

    int jpegNPP(const char *szOutputFile, float* d_srcRGB, int img_width, int img_height)
    {
        NppiDCTState *pDCTState;
        NPP_CHECK_NPP(nppiDCTInitAlloc(&pDCTState));
    
        // Parsing and Huffman Decoding (on host)
        FrameHeader oFrameHeader;
        QuantizationTable aQuantizationTables[4];
        Npp8u *pdQuantizationTables;
        cudaMalloc(&pdQuantizationTables, 64 * 4);
    
        HuffmanTable aHuffmanTables[4];
        HuffmanTable *pHuffmanDCTables = aHuffmanTables;
        HuffmanTable *pHuffmanACTables = &aHuffmanTables[2];
        ScanHeader oScanHeader;
        memset(&oFrameHeader,0,sizeof(FrameHeader));
        memset(aQuantizationTables,0, 4 * sizeof(QuantizationTable));
        memset(aHuffmanTables,0, 4 * sizeof(HuffmanTable));
        int nMCUBlocksH = 0;
        int nMCUBlocksV = 0;
    
        int nRestartInterval = -1;
    
        NppiSize aSrcSize[3];
        Npp16s *apdDCT[3] = {0,0,0};
        Npp32s aDCTStep[3];
    
        Npp8u *apSrcImage[3] = {0,0,0};
        Npp32s aSrcImageStep[3];
        size_t aSrcPitch[3];
    
        /***************************
        *
        *   Output    编码部分
        *
        ***************************/
    
        unsigned char STD_DC_Y_NRCODES[16] = { 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 };
        unsigned char STD_DC_Y_VALUES[12] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
    
        unsigned char STD_DC_UV_NRCODES[16] = { 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 };
        unsigned char STD_DC_UV_VALUES[12] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
    
        unsigned char STD_AC_Y_NRCODES[16] = { 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0X7D };
        unsigned char STD_AC_Y_VALUES[162] =
        {
            0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12,
            0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
            0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08,
            0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0,
            0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16,
            0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28,
            0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
            0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
            0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
            0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
            0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
            0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
            0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,
            0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
            0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
            0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5,
            0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4,
            0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
            0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
            0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
            0xf9, 0xfa
        };
    
        unsigned char STD_AC_UV_NRCODES[16] = { 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0X77 };
        unsigned char STD_AC_UV_VALUES[162] =
        {
            0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21,
            0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71,
            0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91,
            0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0,
            0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34,
            0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26,
            0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38,
            0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
            0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
            0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
            0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
            0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
            0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96,
            0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5,
            0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4,
            0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3,
            0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2,
            0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
            0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,
            0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
            0xf9, 0xfa
        };
    
        //填充Huffman表
        aHuffmanTables[0].nClassAndIdentifier = 0;
        memcpy(aHuffmanTables[0].aCodes, STD_DC_Y_NRCODES, 16);
        memcpy(aHuffmanTables[0].aTable, STD_DC_Y_VALUES, 12);
    
        aHuffmanTables[1].nClassAndIdentifier = 1;
        memcpy(aHuffmanTables[1].aCodes, STD_DC_UV_NRCODES, 16);
        memcpy(aHuffmanTables[1].aTable, STD_DC_UV_VALUES, 12);
    
        aHuffmanTables[2].nClassAndIdentifier = 16;
        memcpy(aHuffmanTables[2].aCodes, STD_AC_Y_NRCODES, 16);
        memcpy(aHuffmanTables[2].aTable, STD_AC_Y_VALUES, 162);
    
        aHuffmanTables[3].nClassAndIdentifier = 17;
        memcpy(aHuffmanTables[3].aCodes, STD_AC_UV_NRCODES, 16);
        memcpy(aHuffmanTables[3].aTable, STD_AC_UV_VALUES, 162);
    
        //标准亮度信号量化模板
        unsigned char std_Y_QT[64] =
        {
            16, 11, 10, 16, 24, 40, 51, 61,
            12, 12, 14, 19, 26, 58, 60, 55,
            14, 13, 16, 24, 40, 57, 69, 56,
            14, 17, 22, 29, 51, 87, 80, 62,
            18, 22, 37, 56, 68, 109, 103, 77,
            24, 35, 55, 64, 81, 104, 113, 92,
            49, 64, 78, 87, 103, 121, 120, 101,
            72, 92, 95, 98, 112, 100, 103, 99
        };
    
        //标准色差信号量化模板
        unsigned char std_UV_QT[64] =
        {
            17, 18, 24, 47, 99, 99, 99, 99,
            18, 21, 26, 66, 99, 99, 99, 99,
            24, 26, 56, 99, 99, 99, 99, 99,
            47, 66, 99, 99, 99, 99, 99, 99,
            99, 99, 99, 99, 99, 99, 99, 99,
            99, 99, 99, 99, 99, 99, 99, 99,
            99, 99, 99, 99, 99, 99, 99, 99,
            99, 99, 99, 99, 99, 99, 99, 99
        };
    
        //填充量化表
        aQuantizationTables[0].nPrecisionAndIdentifier = 0;
        memcpy(aQuantizationTables[0].aTable, std_Y_QT, 64);
        aQuantizationTables[1].nPrecisionAndIdentifier = 1;
        memcpy(aQuantizationTables[1].aTable, std_UV_QT, 64);
    
        NPP_CHECK_CUDA(cudaMemcpyAsync(pdQuantizationTables , aQuantizationTables[0].aTable, 64, cudaMemcpyHostToDevice));
        NPP_CHECK_CUDA(cudaMemcpyAsync(pdQuantizationTables + 64, aQuantizationTables[1].aTable, 64, cudaMemcpyHostToDevice));
    
        //填充帧头
        oFrameHeader.nSamplePrecision = 8;
        oFrameHeader.nComponents = 3;
        oFrameHeader.aComponentIdentifier[0] = 1;
        oFrameHeader.aComponentIdentifier[1] = 2;
        oFrameHeader.aComponentIdentifier[2] = 3;
        oFrameHeader.aSamplingFactors[0] = 34;
        oFrameHeader.aSamplingFactors[1] = 17;
        oFrameHeader.aSamplingFactors[2] = 17;
        oFrameHeader.aQuantizationTableSelector[0] = 0;
        oFrameHeader.aQuantizationTableSelector[1] = 1;
        oFrameHeader.aQuantizationTableSelector[2] = 1;
        oFrameHeader.nWidth = img_width;
        oFrameHeader.nHeight = img_height;
    
        for (int i = 0; i < oFrameHeader.nComponents; ++i)
        {
            nMCUBlocksV = max(nMCUBlocksV, oFrameHeader.aSamplingFactors[i] & 0x0f);
            nMCUBlocksH = max(nMCUBlocksH, oFrameHeader.aSamplingFactors[i] >> 4);
        }
    
        for (int i = 0; i < oFrameHeader.nComponents; ++i)
        {
            NppiSize oBlocks;
            NppiSize oBlocksPerMCU = { oFrameHeader.aSamplingFactors[i] >> 4, oFrameHeader.aSamplingFactors[i] & 0x0f };
    
            oBlocks.width = (int)ceil((oFrameHeader.nWidth + 7) / 8 *
                static_cast<float>(oBlocksPerMCU.width) / nMCUBlocksH);
            oBlocks.width = DivUp(oBlocks.width, oBlocksPerMCU.width) * oBlocksPerMCU.width;
    
            oBlocks.height = (int)ceil((oFrameHeader.nHeight + 7) / 8 *
                static_cast<float>(oBlocksPerMCU.height) / nMCUBlocksV);
            oBlocks.height = DivUp(oBlocks.height, oBlocksPerMCU.height) * oBlocksPerMCU.height;
    
            aSrcSize[i].width = oBlocks.width * 8;
            aSrcSize[i].height = oBlocks.height * 8;
    
            // Allocate Memory
            size_t nPitch;
            NPP_CHECK_CUDA(cudaMallocPitch(&apdDCT[i], &nPitch, oBlocks.width * 64 * sizeof(Npp16s), oBlocks.height));
            aDCTStep[i] = static_cast<Npp32s>(nPitch);
    
            NPP_CHECK_CUDA(cudaMallocPitch(&apSrcImage[i], &nPitch, aSrcSize[i].width, aSrcSize[i].height));
    
            aSrcPitch[i] = nPitch;
            aSrcImageStep[i] = static_cast<Npp32s>(nPitch);
        }
    
        //RGB2YUV
        cudaError_t cudaStatus;
        cudaStatus = cuda_common::RGB2YUV(d_srcRGB, img_width, img_height,
                                            apSrcImage[0], aSrcPitch[0], aSrcSize[0].width, aSrcSize[0].height,
                                            apSrcImage[1], aSrcPitch[1], aSrcSize[1].width, aSrcSize[1].height,
                                            apSrcImage[2], aSrcPitch[2], aSrcSize[2].width, aSrcSize[2].height);
    
        /**
        * Forward DCT, quantization and level shift part of the JPEG encoding.
        * Input is expected in 8x8 macro blocks and output is expected to be in 64x1
        * macro blocks. The new version of the primitive takes the ROI in image pixel size and
        * works with DCT coefficients that are in zig-zag order.
        */
        int k = 0;
        NPP_CHECK_NPP(nppiDCTQuantFwd8x8LS_JPEG_8u16s_C1R_NEW(apSrcImage[0], aSrcImageStep[0],
                                                                apdDCT[0], aDCTStep[0],
                                                                pdQuantizationTables + k * 64,
                                                                aSrcSize[0],
                                                                pDCTState));
        k = 1;
        NPP_CHECK_NPP(nppiDCTQuantFwd8x8LS_JPEG_8u16s_C1R_NEW(apSrcImage[1], aSrcImageStep[1],
                                                                apdDCT[1], aDCTStep[1],
                                                                pdQuantizationTables + k * 64,
                                                                aSrcSize[1],
                                                                pDCTState));
    
        NPP_CHECK_NPP(nppiDCTQuantFwd8x8LS_JPEG_8u16s_C1R_NEW(apSrcImage[2], aSrcImageStep[2],
                                                                apdDCT[2], aDCTStep[2],
                                                                pdQuantizationTables + k * 64,
                                                                aSrcSize[2],
                                                                pDCTState));
    
    
        // Huffman Encoding
        Npp8u *pdScan;
        Npp32s nScanLength;
        NPP_CHECK_CUDA(cudaMalloc(&pdScan, 4 << 20));
    
        Npp8u *pJpegEncoderTemp;
        Npp32s nTempSize;
        NPP_CHECK_NPP(nppiEncodeHuffmanGetSize(aSrcSize[0], 3, &nTempSize));
        NPP_CHECK_CUDA(cudaMalloc(&pJpegEncoderTemp, nTempSize));
    
        NppiEncodeHuffmanSpec *apHuffmanDCTable[3];
        NppiEncodeHuffmanSpec *apHuffmanACTable[3];
    
        /**
        * Allocates memory and creates a Huffman table in a format that is suitable for the encoder.
        */
        NppStatus t_status ;
        t_status = nppiEncodeHuffmanSpecInitAlloc_JPEG(pHuffmanDCTables[0].aCodes, nppiDCTable, &apHuffmanDCTable[0]);
        t_status = nppiEncodeHuffmanSpecInitAlloc_JPEG(pHuffmanACTables[0].aCodes, nppiACTable, &apHuffmanACTable[0]);
        t_status = nppiEncodeHuffmanSpecInitAlloc_JPEG(pHuffmanDCTables[1].aCodes, nppiDCTable, &apHuffmanDCTable[1]);
        t_status = nppiEncodeHuffmanSpecInitAlloc_JPEG(pHuffmanACTables[1].aCodes, nppiACTable, &apHuffmanACTable[1]);
        t_status = nppiEncodeHuffmanSpecInitAlloc_JPEG(pHuffmanDCTables[1].aCodes, nppiDCTable, &apHuffmanDCTable[2]);
        t_status = nppiEncodeHuffmanSpecInitAlloc_JPEG(pHuffmanACTables[1].aCodes, nppiACTable, &apHuffmanACTable[2]);
    
        /**
        * Huffman Encoding of the JPEG Encoding.
        * Input is expected to be 64x1 macro blocks and output is expected as byte stuffed huffman encoded JPEG scan.
        */
        Npp32s nSs = 0;
        Npp32s nSe = 63;
        Npp32s nH = 0;
        Npp32s nL = 0;
        NPP_CHECK_NPP(nppiEncodeHuffmanScan_JPEG_8u16s_P3R(apdDCT, aDCTStep,
                                                           0, nSs, nSe, nH, nL,
                                                           pdScan, &nScanLength,
                                                           apHuffmanDCTable,
                                                           apHuffmanACTable,
                                                           aSrcSize,
                                                           pJpegEncoderTemp));
    
        for (int i = 0; i < 3; ++i)
        {
            nppiEncodeHuffmanSpecFree_JPEG(apHuffmanDCTable[i]);
            nppiEncodeHuffmanSpecFree_JPEG(apHuffmanACTable[i]);
        }
    
        // Write JPEG
        unsigned char *pDstJpeg = new unsigned char[4 << 20];
        unsigned char *pDstOutput = pDstJpeg;
    
        writeMarker(0x0D8, pDstOutput);
        writeJFIFTag(pDstOutput);
        writeQuantizationTable(aQuantizationTables[0], pDstOutput);
        writeQuantizationTable(aQuantizationTables[1], pDstOutput);
    
        writeFrameHeader(oFrameHeader, pDstOutput);
        writeHuffmanTable(pHuffmanDCTables[0], pDstOutput);
        writeHuffmanTable(pHuffmanACTables[0], pDstOutput);
        writeHuffmanTable(pHuffmanDCTables[1], pDstOutput);
        writeHuffmanTable(pHuffmanACTables[1], pDstOutput);
    
        oScanHeader.nComponents = 3;
        oScanHeader.aComponentSelector[0] = 1;
        oScanHeader.aComponentSelector[1] = 2;
        oScanHeader.aComponentSelector[2] = 3;
        oScanHeader.aHuffmanTablesSelector[0] = 0;
        oScanHeader.aHuffmanTablesSelector[1] = 17;
        oScanHeader.aHuffmanTablesSelector[2] = 17;
        oScanHeader.nSs = 0;
        oScanHeader.nSe = 63;
        oScanHeader.nA = 0;
    
        writeScanHeader(oScanHeader, pDstOutput);
        NPP_CHECK_CUDA(cudaMemcpy(pDstOutput, pdScan, nScanLength, cudaMemcpyDeviceToHost));
        pDstOutput += nScanLength;
        writeMarker(0x0D9, pDstOutput);
    
        {
            // Write result to file.
            std::ofstream outputFile(szOutputFile, ios::out | ios::binary);
            outputFile.write(reinterpret_cast<const char *>(pDstJpeg), static_cast<int>(pDstOutput - pDstJpeg));
        }
    
        // Cleanup
        delete [] pDstJpeg;
    
        cudaFree(pJpegEncoderTemp);
        cudaFree(pdQuantizationTables);
        cudaFree(pdScan);
    
        nppiDCTFree(pDCTState);
    
        for (int i = 0; i < 3; ++i)
        {
            cudaFree(apdDCT[i]);
            cudaFree(apSrcImage[i]);
        }
    
        return EXIT_SUCCESS;
    }

    复制代码

    可以看到,编码就是按照1中的流程图进行的。关键的地方都做了注释(中文的或英文的)。

     

    4.要注意的点

        把jpegNPP代码移到自己工程的时候,注意添加nppi.lib、nppc.lib。原工程这两个库添加的位置有点与我通常的添加方式相比有点隐秘,希望能帮助遇到同样问题的同学绕过这个坎。

     

    5.结果

    (1)原RGB图像

    QQ截图20170807180145

    (2)JPEG编码后的图像

    out

     

    由于有跳帧,所以这两张图片不是同一帧。这两张图片在我的电脑上看起来是有色差的。一个更有意思的现象是,用不同的软件打开编码出来的jpg图片,有的软件显示的效果正常,有一些软件显示的颜色会偏红。

     

    主要代码:http://download.csdn.net/download/qq_33892166/9924901

    修改自CUDA自带的jpegNPP工程,缺什么就到jpegNPP工程里面去找,工程配置也参考它。

    分类: CUDA

    标签: CUDA

    展开全文
  • JPEG编码算法

    2013-12-23 12:28:21
    VC实现的最精简的JPEG编码算法代码,可以学习和借鉴。
  • 研究JPEG编码器 代号“ Nether Poppleton” 该编码器的目标是产生具有最佳文件大小/质量比的文件,而不管编码速度或内存成本如何。 实验功能: 亮度加权的色度二次采样-白色或黑色背景上的细红色线条不会变得饱和...
  • JPEG编码原理

    2012-07-17 23:37:16
    条理清楚的jpeg编码小文章,包括离散余弦变换,量化,霍夫曼编码等
  • MATLAB实现jpeg编码和解码,包括编码DCT,哈夫曼编码,熵编码
  • C++实现jpeg编码解码

    2018-07-13 21:45:25
    网络多媒体课程,使用C++实现jpeg编码解码。简单易懂。
  • FPGA上并行JPEG编码的案例研究
  • 资源是一个压缩包包括以下内容: 1.一个JPEG编码PPT和一个JPEG解码PPT,详细讲解JPEG编解码结构及原理 2.JPEG编码标准PDF 3.JPEG编码可运行Matlab语言程序,可读取一幅图像,再进行编码
  • 实验报告 实验目的 通过阅读 JPEG 编码器代码了解编码原理编码过程和代码实现 实验要求 1 详细阅读 JPEG 编码器代码结合编码原理了解整个代码实现的过程 2 输入 bmp 文件在 VC6.0 下跑通代码查看编码器的压缩倍数 3 ...
  • jpeg编码过程

    2020-08-01 18:18:23
    jpeg编码过程 源图像 image_data = imread('tiger.jpg'); //读取图片源数据 //获取RGB数据 image_data_double = double(image_data); image_r = image_data_double(:,:,1);image_g = image_data_double(:,:,2);...
  • JPEG编码过程中的霍夫曼编码 jpeg文件中的霍夫曼编码分两个部分对DC系数编码和对AC系数的编码。 DC系数的编码 编码过程 DC系数的编码由两部分组成 y的bitlen是能够表示DC 系数y的最小bit数,它的定义为: 通过上面...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,366
精华内容 1,346
关键字:

jpeg编码