2012-10-28 133 views
3

我将此函数写入给定文件名(一个jpeg文件)应以像素,w和h为单位打印其大小。根据教程,我读,从jpeg图像文件中获取宽度和高度

// 0xFFC0是包含文件大小 //所述0xFFC0块的结构的“帧开始”标记是相当简单的[0xFFC0] [USHORT 长度] [UCHAR精度] [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,这是真正的大小。

有人可以指出并解释我错了吗?

回答

8

JPEG文件由多个部分组成。每个部分以0xff开头,后面跟着1字节的节标识符,后面跟着节中的数据字节数(2字节),后面跟着数据字节。数据字节序列内部的序列0xffc0或任何其他0xff--双字节序列没有意义,也不标记段的开始。

作为例外,第一部分不包含任何数据或长度。

您必须依次读取每个部分标题,解析长度,然后在开始阅读下一部分之前跳过相应的字节数。你不能只搜索0xffc0,更不用说只是0xc0,而不考虑章节结构。

Source

+1

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

2

正如您所提到的,该规范指出该标记是0xFFC0。但似乎你只用代码寻找一个字节if (b==SOF)

如果你用十六进制编辑器打开文件,并搜索0xFFC0,你会发现标记。现在,只要文件中的第一个0xC0是标记,您的代码就可以工作。如果不是这样,你会得到各种不确定的行为。

我倾向于先阅读整个文件。这是一个JPG的权利,它有多大? (如果在嵌入式系统上,这一点很重要)然后,只需单步穿过它,查找标记的第一个字符。找到时,我会使用memcmp来查看接下来的3个字节是否与sig的其余部分匹配。

+3

我还想指出'width'和'height'应该在上面的代码中交换。换句话说'x'(先读取)给出了高度,'y'是宽度。同样为了支持各种SOF标记(例如基线DCT,渐进DCT等),人们可能希望扫描0xFFC0和0xFFCF之间的所有标记:参见[Ruby中的等效代码](https://github.com) /sstephenson/dimensions/blob/master/lib/dimensions/jpeg_scanner.rb)。 – deltheil

+1

好点!虽然我们在此,但不要忘记提及尺寸/大小以大端格式存储。这是来自320x128px图像的相关字节。 (FF C0 - 00 11 - 08 - 00 80 - 01 40)在保存到文件之前,似乎x,y坐标被压缩到4字节长。如果你把尺寸加载为4字节的int,那么改变字节顺序 - 你最终得到的正确的字符和x,y顺序。 – enhzflep

+0

@deltheil我不确定链接的Ruby代码。该规范只将0xffc0..0xffc3和0xffc9..0xffcb命名为SOF标记,而Ruby代码只添加0xffc5..0xffc7和0xffcd..0xffcf !? – ThomasH

4

有几个问题需要考虑,取决于你希望你的程序如何“普遍”。首先,我推荐使用libjpeg。一个好的JPEG解析器可以有点血腥,这个库为你做了很多繁重的工作。

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

大多数(所有?)现代数码相机还将图像属性编码在EXIF标签中。根据您的应用需求,这可能是获取图像大小最直接,最明确的方式。 EXIF standard document会告诉你关于编写EXIF解析器的所有知识。 (libExif是可用的,但它永远不适合我的应用程序。)无论如何,如果您推出自己的EXIF或依赖库,有一些检查EXIF数据的好工具。 jhead是非常好的工具,我也有与ExifTool祝你好运。

最后,要注意endianess。 SOF和其他标准JPEG标记是大端,但EXIF标记可能会有所不同。

相关问题