从JPEG图像文件中获取宽度和高度

3
我编写了这个函数来获取给定文件名(一个jpeg文件)的像素大小,w和h。根据我正在阅读的教程,0xFFC0是“帧开始”标记,其中包含文件大小。0xFFC0块的结构非常简单[0xFFC0][ushort length][uchar precision][ushort x][ushort y]。因此,我编写了这个struct。
#pragma pack(1)
struct imagesize {
  unsigned short len; /* 2-bytes */
  unsigned char c;    /* 1-byte */
  unsigned short x;   /* 2-bytes */
  unsigned short y;   /* 2-bytes */
}; //sizeof(struct imagesize) == 7
#pragma pack()

接着:

#define SOF 0xC0 /* start of frame */

    void jpeg_test(const char *filename)
    {
      FILE *fh;
      unsigned char buf[4];
      unsigned char b;

      fh = fopen(filename, "rb");
      if(fh == NULL) 
        fprintf(stderr, "cannot open '%s' file\n", filename);

      while(!feof(fh)) {
        b = fgetc(fh);

        if(b == SOF) {

          struct imagesize img;
    #if 1
          ungetc(b, fh);
          fread(&img, 1, sizeof(struct imagesize), fh);
    #else
          fread(buf, 1, sizeof(buf), fh);
          int w = (buf[0] << 8) + buf[1];
          int h = (buf[2] << 8) + buf[3];
          img.x = w;
          img.y = h;
    #endif

          printf("%dx%d\n",
             img.x,
             img.y);

          break;
        }
      }

      fclose(fh);
    }

但是我得到的是 520x537,而不是实际大小的700x537

有人能指出并解释我错在哪里吗?

3个回答

8
一个JPEG文件由多个部分组成,每个部分以0xff开头,后跟1字节的部分标识符,然后是部分中的数据字节数(以2字节表示),最后是数据字节。在数据字节序列中,序列0xffc0或任何其他0xff--两个字节的序列都没有意义,也不标志着一个节的开始。
作为例外,第一个部分不包含任何数据或长度。
您必须依次读取每个节标题,解析长度,然后跳过相应数量的字节,才能开始读取下一个节。您不能只搜索0xffc0,更不能忽略节结构,仅搜索0xc0来源

1
非常好的解释,我也曾陷入同样的陷阱。小提示:有一些部分不遵循一般的方案,即SOI(图像开始,你提到的0xffd8),RSTn(重启标记,0xffdn,n = 0..7)和EOI(图像结束,0xffd9)。DRI(0xffdd)遵循该方案,但长度值始终为4。http://en.wikipedia.org/wiki/Jpeg#Syntax_and_structure - ThomasH

4

如果您想使程序“通用”,则需要考虑几个问题。首先,我建议使用libjpeg。一个好的JPEG解析器可能会有点复杂,而这个库可以为您完成很多繁重的工作。

接下来,为了澄清n.m.的说法,您无法保证第一个0xFFCO对是感兴趣的SOF。我发现现代数码相机喜欢用许多APP0和APP1块来加载JPEG头,这意味着在顺序读取期间遇到的第一个SOF标记可能实际上是图像缩略图。这个缩略图通常以JPEG格式存储(至少在我观察到的范围内),因此配备了自己的SOF标记。一些相机和/或图像编辑软件可以包括比缩略图更大的图像预览(但小于实际图像)。这个预览图像通常是JPEG格式,并且也有它自己的SOF标记。图像SOF标记是最后一个并不罕见。

现代数字相机(全部?)也会在EXIF标签中编码图像属性。根据您的应用要求,这可能是获取图像大小最简单、明确的方法。EXIF标准文档将告诉您有关编写EXIF解析器的所有所需信息。(libExif可用,但不适合我的应用程序。)无论如何,如果您自己编写EXIF或依赖库,则有一些很好的工具可用于检查EXIF数据。jhead是非常好的工具,我也使用ExifTool取得了良好的效果。
最后,请注意字节序。SOF和其他标准JPEG标记是大端字节序,但EXIF标记可能会有所不同。

2
正如您所提到的,规范说明标记为0xFFC0。但是,您似乎只查找了一个字节的代码if (b==SOF) 如果您使用十六进制编辑器打开文件并搜索0xFFC0,则会找到标记。只要文件中的第一个0xC0是标记,您的代码就可以正常工作。但是,如果不是这样,您将获得各种未定义的行为。
我倾向于先读取整个文件。这是jpg格式的,它可能有多大呢?(尽管这在嵌入式系统上很重要)。然后,只需逐步查找我的标记的第一个字符。找到后,我将使用memcmp来查看接下来的3个字节是否与其余的签名匹配。

3
我还想指出,在上面的代码中应该交换 widthheight。换句话说,x(读取第一个)表示高度,而 y 表示宽度。此外,为了支持所有类型的 SOF 标记(例如基线 DCT、渐进式 DCT 等),您可能希望扫描 0xFFC00xFFCF 之间的所有标记:请参见 Ruby 中的等效代码 - deltheil
1
好主意!顺便提一下,尺寸/大小是以大端格式存储的。以下是320x128像素图像的相关字节。(FF C0 - 00 11 - 08 - 00 80 - 01 40) 看起来x,y坐标在保存到文件之前被打包成4字节长整型。如果将尺寸作为4字节整数加载,然后更改字节序,最终可以得到正确的坐标并按照x,y顺序排列。 - enhzflep
@deltheil 我对链接的 Ruby 代码不确定。规范只命名了 0xffc0..0xffc3 和 0xffc9..0xffcb 作为 SOF 标记,而 Ruby 代码添加了 0xffc5..0xffc7 和 0xffcd..0xffcf!? - ThomasH

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接