如何在shell脚本中判断扫描的PDF文件分辨率?

10

我有一大批文档以PDF格式扫描,希望编写一个shell脚本,将每个文档转换为DjVu格式。有些文档以200dpi扫描,有些以300dpi和600dpi扫描。由于DjVu是一个基于像素的格式,因此我想确保在目标DjVu文件中使用与扫描时相同的分辨率。

有人知道我可以运行哪个程序或如何编写程序来确定生成扫描PDF所使用的分辨率吗?(像素数也可能有效,因为几乎所有文档都是8.5英寸x11英寸)


在回答后进行澄清:我意识到Breton所指出的困难,并愿意承认一般情况下问题不明确,但我不是在询问关于一般的PDF文件。我的特定文档是从扫描仪中输出的。它们每页包含一个扫描图像,每个页面的分辨率都相同。如果我将PDF转换为PostScript,则可以手动查找像素尺寸;我可能需要更多工作来查找图像大小。如果万不得已,我可以修改gs正在使用的字典堆栈;很久以前,我写过PostScript Level 1的解释器。

所有这些都是我试图避免的。


感谢获得的帮助,我在下面发布了一个答案:

  1. 使用identify从PDF中提取页面的边界框,仅获取第一页的输出,并理解单位将是PostScript点,其中每英寸有72个点。
  2. 使用pdfimages从第一页中提取图像。
  3. 获取图像的高度和宽度。此时,identify将给出像素数。
  4. 添加所有图像的总面积以获取平方点数。
  • 为了获得分辨率,计算边界框在英寸平方内的面积,将点数平方除以英寸平方,取平方根并四舍五入到最接近的10的倍数。
  • 7个回答

    7

    pdfimages有一个-list选项,可以给出高度、宽度(以像素为单位),以及y-ppix-ppi

     pdfimages -list tmp.pdf           
    page   num  type   width height color comp bpc  enc interp  object ID x-ppi y-ppi size ratio
    --------------------------------------------------------------------------------------------
       1     0 image    3300  2550  gray    1   1  ccitt  no       477  0   389   232  172K  17%
       2     1 image    3300  2550  gray    1   1  ccitt  no         3  0   389   232  103K  10%
       3     2 image    3300  2550  gray    1   1  ccitt  no         7  0   389   232  236K  23%
       4     3 image    3300  2550  gray    1   1  ccitt  no        11  0   389   232  210K  20%
       5     4 image    3300  2550  gray    1   1  ccitt  no        15  0   389   232  250K  24%
       6     5 image    3300  2550  gray    1   1  ccitt  no        19  0   389   232  199K  19%
       7     6 image    3300  2550  gray    1   1  ccitt  no        23  0   389   232  503K  49%
       8     7 image    3300  2550  gray    1   1  ccitt  no        27  0   389   232  154K  15%
       9     8 image    3300  2550  gray    1   1  ccitt  no        31  0   389   232 21.5K 2.1%
      10     9 image    3300  2550  gray    1   1  ccitt  no        35  0   389   232  286K  28%
      11    10 image    3300  2550  gray    1   1  ccitt  no        39  0   389   232 46.8K 4.6%
      12    11 image    3300  2550  gray    1   1  ccitt  no        43  0   389   232 55.5K 5.4%
      13    12 image    3300  2550  gray    1   1  ccitt  no        47  0   389   232 35.0K 3.4%
      14    13 image    3300  2550  gray    1   1  ccitt  no        51  0   389   232 26.9K 2.6%
      15    14 image    3300  2550  gray    1   1  ccitt  no        55  0   389   232 66.5K 6.5%
      16    15 image    3300  2550  gray    1   1  ccitt  no        59  0   389   232 73.9K 7.2%
      17    16 image    3300  2550  gray    1   1  ccitt  no        63  0   389   232 47.0K 4.6%
      18    17 image    3300  2550  gray    1   1  ccitt  no        67  0   389   232 30.1K 2.9%
      19    18 image    3300  2550  gray    1   1  ccitt  no        71  0   389   232 70.3K 6.8%
      20    19 image    3300  2550  gray    1   1  ccitt  no        75  0   389   232 46.0K 4.5%
      21    20 image    3300  2550  gray    1   1  ccitt  no        79  0   389   232 28.9K 2.8%
      22    21 image    3300  2550  gray    1   1  ccitt  no        83  0   389   232 72.7K 7.1%
      23    22 image    3300  2550  gray    1   1  ccitt  no        87  0   389   232 47.5K 4.6%
      24    23 image    3300  2550  gray    1   1  ccitt  no        91  0   389   232 30.1K 2.9%
    

    5
    如果PDF是通过扫描创建的,则每个页面应只关联一个图像。您可以使用iText(Java)或iTextSharp(.net端口)库轻松地解析PDF以查找每个页面图像的每个图像分辨率。
    如果要自己创建实用程序来执行此操作,请在iTextSharp中执行以下操作:
    PdfReader reader = new PdfReader(filename);
    for (int i = 1; i <= reader.NumberOfPages; i++)
    {
    PdfDictionary pg = reader.GetPageN(i);
    PdfDictionary res = (PdfDictionary)PdfReader.GetPdfObject(pg.Get(PdfName.RESOURCES));
    PdfDictionary xobjs = (PdfDictionary)PdfReader.GetPdfObject(res.Get(PdfName.XOBJECT));
    if (xobjs != null) 
    {
        foreach (PdfName xObjectKey in xobjs.Keys)
        {
        PdfObject xobj = xobjs.Get(xObjectKey);
        PdfDictionary tg = (PdfDictionary)PdfReader.GetPdfObject(xobj);
        PdfName subtype = (PdfName)PdfReader.GetPdfObject(tg.Get(PdfName.SUBTYPE));
        if  (subtype.Equals(PdfName.IMAGE))
        {
            PdfNumber width = (PdfNumber)tg.Get(PdfName.WIDTH);
            PdfNumber height = (PdfNumber)tg.Get(PdfName.HEIGHT);
            MessageBox.Show("image on page [" + i + "] resolution=[" + width +"x" + height + "]");
        }
        }
    }
    }   
    reader.Close();
    

    在这里,我们会针对每个页面阅读其子类型为Image的每个XObject,并获取WIDTH和HEIGHT值。这将是扫描仪嵌入PDF中的图像的像素分辨率。

    请注意,将此图像缩放以匹配页面分辨率(如Acrobat中呈现的页面大小 - A4、Letter等)是在页面内容流中单独执行的,该流表示为postscript的子集,并且很难在不解析postscript的情况下找到。

    请注意,有些扫描仪会将扫描的图像嵌入为一组小图像的网格(我认为是为了某种尺寸优化)。因此,如果您看到每个页面弹出50个小图像之类的东西,那可能就是原因。

    希望这能以某种方式帮助您自己开发实用程序。


    1
    谢谢您的建议。这些库显然非常强大。从代码示例中,widthheight变量的单位是什么? - Norman Ramsey
    1
    当谈到XObject图像的宽度和高度时,单位为像素。 - Spiffeah
    每页多个图像的警报非常有用。非常感谢。完整解决方案现已发布。 - Norman Ramsey

    4

    我猜测扫描件以图片形式包含在PDF文件中,因此您可以使用pdfimages先提取它们。然后,identify应该能够找到正确的数据。


    不错的想法!这是一个90%的解决方案——非常快速,并且可以给我准确的像素宽度和高度。我需要看看如何提取和组合边界框信息。 - Norman Ramsey
    非常有帮助。完整的解决方案已经发布。 - Norman Ramsey

    4
    以下是答案的要素:
    - pdfimages将提取图像,以便可以发现点的数量。 - identify将给出图像的大小,单位为PostScript点(每英寸72个)。 - 由于一些扫描仪可能将单个页面拆分为多个大小和形状不同的图像,因此关键在于添加所有图像的面积。将平方点除以平方英寸并取平方根即可得出答案。
    以下是解决问题的Lua脚本。我可能可以使用纯shell,但捕获宽度和高度会更加麻烦。
    #!/usr/bin/env lua
    
    require 'osutil'
    require 'posixutil'
    require 'mathutil'
    
    local function runf(...) return os.execute(string.format(...)) end
    
    assert(arg[1], "no file on command line")
    
    local function dimens(filename)
      local cmd = [[identify -format "return %w, %h\n" $file | sed 1q]]
      cmd = cmd:gsub('$file', os.quote(filename))
      local w, h = assert(loadstring(os.capture(cmd)))()
      assert(w and h)
      return w, h
    end
    
    assert(#arg == 1, "dpi of just one file")
    
    for _, pdf in ipairs(arg) do
      local w, h = dimens(pdf)  -- units are points
      local insquared = w * h / (72.00 * 72.00)
      local imagedir = os.capture 'mktemp -d'
      assert(posix.isdir(imagedir))
      runf('pdfimages -f 1 -l 1 %s %s 1>&2', os.quote(pdf),
                                             os.quote(imagedir .. '/img'))
      local dotsquared = 0
      for file in posix.glob(imagedir .. '/img*') do
        local w, h = dimens(file)  -- units are pixels
        dotsquared = dotsquared + w * h
      end
      os.execute('rm -rf ' .. os.quote(imagedir))
      local dpi = math.sqrt(dotsquared / insquared)
    
      if true then
        io.stderr:write(insquared, " square inches\n")
        io.stderr:write(dotsquared, " square dots\n")
        io.stderr:write(dpi, " exact dpi\n")
        io.stderr:write(math.round(dpi, 10), " rounded dpi\n")
      end
      print(math.round(dpi, 10))
    end
    

    1
    这段代码对我来说非常好用。我将它翻译成了Python,但那只是小菜一碟 :). 做得好,@Norman Ramsey。 - Vladir Parrado Cruz

    2

    这段内容太长无法放在评论中,但是ImageMagick和GraphicsMagic都不能胜任这项工作;每个答案都是错误的

    : nr@yorkie 1932 ; gm identify -format "x=%x y=%y w=%w h=%h" drh*rec*pdf
    x=0 y=0 w=612 h=792
    x=0 y=0 w=612 h=792
    x=0 y=0 w=612 h=792
    x=0 y=0 w=612 h=792
    x=0 y=0 w=612 h=792
    x=0 y=0 w=612 h=792
    x=0 y=0 w=612 h=792
    x=0 y=0 w=612 h=792
    
    : nr@yorkie 1933 ; identify -format "x=%x y=%y w=%w h=%h" drh*rec*pdf   
    x=72 Undefined y=72 Undefined w=612 h=792x=72 Undefined y=72 Undefined     w=612 h=792x=72 Undefined y=72 Undefined w=612 h=792x=72 Undefined     y=72 Undefined w=612 h=792x=72 Undefined y=72 Undefined w=612     h=792x=72 Undefined y=72 Undefined w=612 h=792x=72 Undefined y=72     Undefined w=612 h=792x=72 Undefined y=72 Undefined w=612 h=792
    : nr@yorkie 1934 ; 
    

    这个文档的正确参数是每个扫描页面宽度为5100像素,高度为6600像素,这并不奇怪,因为它是以600dpi扫描的8.5乘11英寸纸张。ImageMagic的输出令人惊讶地不专业。

    没有点踩是因为你试图提供帮助,但*Magick不起作用。


    ImageMagick的输出完全符合您的要求;我几乎看不出它有什么“不专业”的地方。(例如,输出字符串中没有文件名,因为您没有要求它)。 “未定义”很明显:文件格式使得这些属性没有意义。 - Charles Duffy
    抱歉,我的意思是你运行命令时要求它做了什么,而不是在你的问题中要求了什么。 :) - Charles Duffy
    我猜ImageMagick通过将PDF渲染为某个默认分辨率,然后对其进行处理来转换PDF。您首先需要提取图像数据(请参见我的答案)。 - Svante
    @Charles:输出结果不是我要求的:数字是错误的。报告'72 Undefined'的%y值让人觉得不专业。这个值既不有用也不合理。缺少换行符也让我感到烦恼,但也许在格式字符串中加入\n是我的责任。 - Norman Ramsey
    @Harlequin:看起来没问题。无论真相如何,都是72dpi。 - Norman Ramsey
    2
    @Norman Ramsey:你应该对pdfimages输出结果运行identify,而不是对原始PDF运行。如果你对原始PDF运行它,identify将会调用Ghostscript来帮助处理(因为它不能本地处理PDF)。而Ghostscript将会使用默认分辨率72dpi将其转换为图像格式供identify使用(PPM?)。 - Kurt Pfeifle

    0

    PDF是一种分辨率无关的格式,这是一个毫无意义的问题。您可能已经以特定分辨率扫描了一些位图,并将这些位图单独嵌入到pdf中,但PDF本身可能包含多个分辨率的图像,以及分辨率无关的矢量图形。除非打开pdf并检查其中的每个对象,否则无法知道。

    继续阐述问题:

    您可能会很幸运,使用扫描文档的软件嵌入了一些有关此信息的元数据,但不要抱太大希望。这种元数据不太可能是标准的。就解析pdf而言,您需要预先编写的库,例如ghostscript。问题在于,PDF实际上不是一种格式,而更像是PostScript编程语言的指定子集,以及一种约定俗成的压缩/编译该子集和一些二进制文件的方式。因此,与其他类型的图像格式相比,阅读PDF更加复杂,因为它涉及编写语言解释器-不是那么简单。

    最好的方法是要么放弃,要么仔细研究ghostscript,看看是否可以得到答案。


    4
    @Breton:PDF 的本地成像操作符可能是与分辨率无关的。但是,作为容器格式,PDF 可以嵌入各种图像类型——这就是分辨率重要的地方。因此,“这是一个没有意义的问题”的说法是错误的。 - Kurt Pfeifle
    @pipitas,你正在犯组合谬误。这仍然是一个无意义的问题,就像“斯坦福大学的头发颜色是什么?”一样毫无意义。 - Breton
    @pipitas 如果你把它说成“斯坦福大学的学生平均头发颜色是什么”,这样会更有意义。原作者也可以提问得更有意义,例如“如何通过Shell脚本确定PDF中所有图像的平均分辨率?” - Breton
    1
    @Breton:作为非英语母语者,我不理解“组合谬误”这个术语。然而,在面对您的智力优势时,我不会再就这个话题与您争论了。——至于我,像往常一样,将继续考虑PDF中各种图形对象的分辨率依赖性,作为像素和向量的奴隶。愿您在分辨率独立的PDF世界里继续快乐无忧地生活... - Kurt Pfeifle
    @pipitas,“组合谬误”是一种逻辑错误,当你断言整体的属性是从其部分继承而来时,就会犯这种错误。因此,机器没有齿轮的同样属性,大学没有学生或教职员工的同样属性,PDF文件没有图像的“分辨率”属性(尽管图像有,PDF可以包含图像-但分辨率属性不会转移到“pdf”)。 - Breton
    @pipitas,你可能会认为我过于拘泥小节,但请记住,这是一个编程网站。如果我在工作中使用词不达意,就会发生严重的错误。 - Breton

    0
    Apago的PDF Spy可以告诉您PDF中图像的实际分辨率以及其他许多信息。这是一款商业产品,但有10天的演示版。

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