【问题标题】:Determining the size of a JPEG (JFIF) image确定 JPEG (JFIF) 图像的大小
【发布时间】:2010-12-06 03:17:50
【问题描述】:

我需要找到 JPEG (JFIF) 图像的大小。图像未保存为独立文件,因此我不能使用GetFileSize 或任何其他 API,例如这个(图像被放置在流中,除了通常的 JPEG/JFIF 标头外,不存在其他标头(s))。

我做了一些研究,发现 JPEG 图像由不同的部分组成,每个部分都以帧标记 (0xFF 0xXX) 开头,以及该帧的大小。使用这些信息,我能够解析文件中的大量信息。

问题是,我找不到压缩数据的大小,因为压缩数据似乎没有帧​​标记。此外,压缩数据似乎遵循 SOS (FFDA) 标记,图像以图像结束 (EOI) (FFD9) 标记结束。

实现这一点的一种方法是逐字节搜索 EOI 标记,但我认为压缩数据可能包含这种字节组合,对吧?

是否有一种简单而正确的方法来查找图像的总大小? (我更喜欢一些代码/想法没有任何外部库

基本上,我需要图像开始 (SOI-FFE0) 和图像结束 (EOI-FFD9) 之间的距离(以字节为单位)。

【问题讨论】:

  • 嗯...JFIF 文件中的 SOS 标记?...我觉得我错过了规范中的某些内容...
  • 原帖说“没有文件”。他说有一个SOS和一个EOI。不知何故,他嵌入了一个没有任何外部包装器的 JFIF 流。

标签: size jpeg


【解决方案1】:

由于您没有发布任何语言,我不确定这是否可行,但是:

你能Stream.Seek(0, StreamOffset.End);然后占据流的位置吗?

请具体说明您使用的是什么框架。

事情的真相是,如果文件头没有指定预期的大小,你必须寻找(或读取)到图像的末尾。

编辑

由于您尝试流式传输多个文件,因此您需要使用流式友好的容器格式。

OGG 应该很适合这个。

JPEG 实际上已经对流式传输友好,但您必须保证每个文件在将其发送到流式传输之前都有一个有效的终止符,否则您将面临因意外输入而导致应用程序崩溃的风险。

【讨论】:

  • 我可以使用 C 或 perl,任何一个都可以。但是请注意,我不能使用任何形式的 GetFileSize/GetStreamSize,因为我的流可能包含多张图片或图片后面的任何其他信息。
  • 如果您传输不完整的 JPEG 文件,则会出现问题。你永远不会看到终结者。 JPEG 文件预计是流中唯一的内容。看我回答的最后一句话。
  • 所以,基本上,您的意见是,您不能在不添加额外标题的情况下将两个 jpeg 图像放入一个流中?换句话说,您的意思是 JPEG 标头无法判断 JPEG 图像有多大?
  • 差不多,是的。我不确定 JFIF 是否不包含该信息,但如果您一次向流中发送两个文件,则绝对必须自己进行框架。 Ogg Container 格式可能正是您想要的。
  • 更改流格式不是我的选择。就是这样,我必须尽我所能处理它:)
【解决方案2】:

在 python 中,您可以将整个文件读入一个字符串对象,然后找到第一次出现的 FF E0 和最后一次出现的 FF D9。想必,这些就是你要找的起点和终点?

f = open("filename.jpg", "r")
s = f.read()
start = s.find("\xff\xe0")
end = s.rfind("\xff\xd9")
imagesize = end - start

【讨论】:

  • \xff\xd9 完全有可能出现在 jpeg 图像的中间。任何两个字节匹配该模式的几率是 1/65536。
  • 是的,确实如此。但是,假设您有一个有效的 JPEG 文件,find 和 rfind 将分别返回字符串的第一次和最后一次出现。我认为假设第一次出现是图像数据的开始,最后一次出现是图像数据的结束是相当安全的?
  • @cgkanchi:当您不知道图像的末尾在哪里时,问题是找到最后一个。 OP 似乎打算通过同一流传输多个 JPEG 文件。
  • 还有一个问题是 OP 没有“文件”,而是在 JFIF/JPEG 图像之后有内容的流。
【解决方案3】:

压缩后的数据不包含 SOI 或 EOI 字节,因此您可以放心使用。但注释、应用程序数据或其他标题可能会。幸运的是,您可以根据给出的长度识别并跳过这些部分。

JPEG 规范告诉您需要什么:
http://www.w3.org/Graphics/JPEG/itu-t81.pdf

查看第 32 页的表 B.1。带有 * 的符号后面没有长度字段(RST、SOI、EOI、TEM)。其他人都这样做。

您将需要跳过各个字段,但这还不错。

如何通过:

  1. 开始阅读 SOI (FFD8)。这是开始。它应该是流中的第一件事。

    • 然后,浏览文件,找到更多标记并跳过标题:

    • SOI 标记 (FFD8):图像损坏。您应该已经找到意向书了!

    • TEM (FF01):独立标记,继续。

    • RST(FFD0FFD7):独立标记,继续。您可以验证重启标记是否从FFD0FFD7 向上计数并重复,但这对于测量长度不是必需的。

    • EOI 标记 (FFD9):大功告成!

    • 任何不是 RST、SOI、EOI、TEM 的标记(FF01FFFE,减去上述例外):在标记之后,读取接下来的 2 个字节,这是 16 位大- 该帧头的字节序长度(不包括 2 字节标记,但包括长度字段)。跳过给定的数量(通常是长度减去 2,因为您已经获得了这些字节)。

    • 如果您在 EOI 之前收到文件结尾,则说明您的图片已损坏。

    • 一旦您获得了意向书,您就已经通过了 JPEG 并且应该有长度。如果您希望流中包含多个 JPEG,您可以通过读取另一个 SOI 重新开始。

【讨论】:

  • 这对我帮助很大,但我发现另一个参考资料说,当你找到 SOS 标记时,你需要开始阅读数据以寻找 EOI 标记,这将是结束。 gvsoft.homedns.org/exif/Exif-explanation.html 这与我在 ATM 上工作的图像相匹配。
  • 您的意思是所有这些标记都存在于 JFIF 中吗?我认为它们是 EXIF 规范的一部分,而这又意味着通常与 JFIF 不兼容?.. 我真的错过了某个地方吗?
  • 这里缺少的是,当您找到 SOS(扫描开始)标记时,您不仅必须跳过标记段本身,还要跳过紧随其后的熵编码段。标记不能出现在熵编码段中,因此请继续扫描,直到您读取 FF 后跟任何不等于 0 的字节。(参见 B.1.1.5“熵编码数据段”,注 2。)
  • 是正确的,更多信息请参考这个:media.mit.edu/pia/Research/deepview/exif.html
  • @devconsole 它怎么不见了?如果您继续浏览文件,找到标记,您将跳过FFDA 之后的所有数据字节。
【解决方案4】:

可能是这样的

int GetJpgSize(unsigned char *pData, DWORD FileSizeLow, unsigned short *pWidth, unsigned short *pHeight)
{
  unsigned int i = 0;


  if ((pData[i] == 0xFF) && (pData[i + 1] == 0xD8) && (pData[i + 2] == 0xFF) && (pData[i + 3] == 0xE0)) {
    i += 4;

    // Check for valid JPEG header (null terminated JFIF)
    if ((pData[i + 2] == 'J') && (pData[i + 3] == 'F') && (pData[i + 4] == 'I') && (pData[i + 5] == 'F')
        && (pData[i + 6] == 0x00)) {

      //Retrieve the block length of the first block since the first block will not contain the size of file
      unsigned short block_length = pData[i] * 256 + pData[i + 1];

      while (i < FileSizeLow) {
        //Increase the file index to get to the next block
        i += block_length; 

        if (i >= FileSizeLow) {
          //Check to protect against segmentation faults
          return -1;
        }

        if (pData[i] != 0xFF) {
          return -2;
        } 

        if (pData[i + 1] == 0xC0) {
          //0xFFC0 is the "Start of frame" marker which contains the file size
          //The structure of the 0xFFC0 block is quite simple [0xFFC0][ushort length][uchar precision][ushort x][ushort y]
          *pHeight = pData[i + 5] * 256 + pData[i + 6];
          *pWidth = pData[i + 7] * 256 + pData[i + 8];

          return 0;
        }
        else {
          i += 2; //Skip the block marker

          //Go to the next block
          block_length = pData[i] * 256 + pData[i + 1];
        }
      }

      //If this point is reached then no size was found
      return -3;
    }
    else {
      return -4;
    } //Not a valid JFIF string
  }
  else {
    return -5;
  } //Not a valid SOI header

  return -6;
}  // GetJpgSize

【讨论】:

    猜你喜欢
    • 2011-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-08
    • 2014-06-25
    • 1970-01-01
    • 1970-01-01
    • 2016-02-11
    相关资源
    最近更新 更多