【问题标题】:Go image bytes and Python PIL tobytes produce different byte dataGo image bytes 和 Python PIL tobytes 产生不同的字节数据
【发布时间】:2021-07-28 23:03:40
【问题描述】:

我已经接手了一些以前用python写的别人写的代码,没有在go,我自己写的。

这段代码的一部分是打开一张图片,读取它的数据,然后做一个 MD5 哈希来防止重复。

此代码的python版本使用了PIL:

from PIL import Image
f = Image.open('2d16395b-da48-11eb-8cbe-36a331f79a1e.png')
hashlib.md5(f.tobytes()).hexdigest()
'c699a448b38df386d036ed418d7714f3'

而 go 版本只是将字节读入 md5 散列

    f, _ := os.Open(p)
    _, err := f.Seek(0, io.SeekStart)
    if err != nil {
        fmt.Println(err)
    }

    h := md5.New()
    if _, err := io.Copy(h, f); err != nil {
        log.Fatal(err)
    }

    hb := h.Sum(nil)
    hash := hex.EncodeToString(hb)

但是这些会产生不同的 MD5。

似乎 PIL 库正在以不同的方式准备字节,可能是剥离标头/元数据或其他什么?

有谁知道我可以在 go 中复制 PIL 的字节读取以获得相同的 MD5 哈希?

【问题讨论】:

    标签: python image go python-imaging-library byte


    【解决方案1】:

    我将从我使用的(虚拟)测试图像开始:

    这是一个只有 2 种颜色的 32X16 (RGBA) .png

    • 红色(237、28、36)
    • 蓝色 (0, 0, 255)

    回到问题:[ReadTheDocs.Pillow]: Image.load()(由 open 隐式调用)处理(解码)图像,产生其原始位图数据,这与(编码的)文件内容完全不同。

    这是两者之间的区别。

    code00.py

    #!/usr/bin/env python
    
    import sys
    from PIL import Image
    from hashlib import md5
    
    
    def read_file_data(file_name):
        with open(file_name, "rb") as fin:
            return fin.read()
    
    
    def read_img_data(file_name):
        with Image.open(file_name) as img:
            return img.tobytes()
    
    
    def process_bytes(buf, first=20, last=20):
        print("Len: {:d}\nFirst bytes:\n  ".format(len(buf)), end=" ")
        for i in range(first):
            print("0x{:02X}".format(buf[i]), end=" ")
        print("\nLast bytes:\n  ", end=" ")
        for i in range(-last, 0, 1):
            print("0x{:02X}".format(buf[i]), end=" ")
        print("\nMD5: {:}".format(md5(buf).hexdigest()))
    
    
    def main(*argv):
        img_name = "rb.png"
        funcs = [
            read_file_data,
            read_img_data,
        ]
        for func in funcs:
            print("\nFunction {:s}".format(func.__name__))
            b = func(img_name)
            process_bytes(b)
    
    
    if __name__ == "__main__":
        print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
                                                       64 if sys.maxsize > 0x100000000 else 32, sys.platform))
        rc = main(*sys.argv[1:])
        print("\nDone.")
        sys.exit(rc)
    
    

    输出

    [cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q068231412]> sopr.bat
    ### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###
    
    [prompt]> "e:\Work\Dev\VEnvs\py_pc064_03.08.07_test0\Scripts\python.exe" code00.py
    Python 3.8.7 (tags/v3.8.7:6503f05, Dec 21 2020, 17:59:51) [MSC v.1928 64 bit (AMD64)] 064bit on win32
    
    
    Function read_file_data:
    Len: 169
    First bytes:
       0x89 0x50 0x4E 0x47 0x0D 0x0A 0x1A 0x0A 0x00 0x00 0x00 0x0D 0x49 0x48 0x44 0x52 0x00 0x00 0x00 0x10
    Last bytes:
       0xE2 0x8A 0x24 0x69 0x53 0x4C 0xB3 0x03 0x00 0x00 0x00 0x00 0x49 0x45 0x4E 0x44 0xAE 0x42 0x60 0x82
    MD5: 8368b5c29a12b298cea2ad4b32955830
    
    Function read_img_data:
    Len: 2048
    First bytes:
       0xED 0x1C 0x24 0xFF 0xED 0x1C 0x24 0xFF 0xED 0x1C 0x24 0xFF 0xED 0x1C 0x24 0xFF 0xED 0x1C 0x24 0xFF
    Last bytes:
       0x00 0x00 0xFF 0xFF 0x00 0x00 0xFF 0xFF 0x00 0x00 0xFF 0xFF 0x00 0x00 0xFF 0xFF 0x00 0x00 0xFF 0xFF
    MD5: ebdf44b7ad36d79b221a70ea2b0fa0c7
    
    Done.
    

    经过几个小时的研究(反复试验,Googleing,阅读 docs 和示例 - 我不能在这里不提[SO]: Get a pixel array from from golang image.Image (@ArunaHerath's answer)),我能够将上述脚本翻译成Go

    code00.go

    package main
    
    import (
        "crypto/md5"
        "fmt"
        "image"
        "image/draw"
        "io/ioutil"
        "os"
    
        _ "image/gif"
        _ "image/jpeg"
        _ "image/png"
    )
    
    type ImageFunc func(string) []byte
    type ImageFuncs []ImageFunc
    
    func ReadFileData(fileName string) []byte {
        buf, _ := ioutil.ReadFile(fileName)
        return buf
    }
    
    func ReadImgData(fileName string) []byte {
        reader, _ := os.Open(fileName)
        defer reader.Close()
        img, _, _ := image.Decode(reader)
        rect := img.Bounds()
        rgba := image.NewRGBA(rect)
        draw.Draw(rgba, rect, img, rect.Min, draw.Src)
        //fmt.Printf("%v\n", rgba.Pix)
        return rgba.Pix
    }
    
    func ProcessBytes(buf []byte, first int, last int) {
        lb := len(buf)
        fmt.Printf("Len: %d\nFirst bytes:\n  ", lb)
        for i := 0; i < first; i++ {
            fmt.Printf("0x%02X ", buf[i])
        }
        fmt.Printf("\nLast bytes:\n  ")
        for i := lb - last; i < lb; i++ {
            fmt.Printf("0x%02X ", buf[i])
        }
        fmt.Printf("\nMD5: %x", md5.Sum(buf))
    }
    
    func main() {
        imgName := "rb.png"
        first := 20
        last := 20
        funcs := ImageFuncs{
            ReadFileData,
            ReadImgData,
        }
        for idx := range funcs {
            function := funcs[idx]
            fmt.Printf("\n\nFunction %#v:\n", function)
            b := function(imgName)
            ProcessBytes(b, first, last)
        }
        fmt.Printf("\n\nDone.\n")
    }
    
    

    输出

    [prompt]> "f:\Install\pc064\Google\GoLang\1.16.5\bin\go.exe" run code00.go
    
    
    Function (main.ImageFunc)(0x9f1060):
    Len: 169
    First bytes:
      0x89 0x50 0x4E 0x47 0x0D 0x0A 0x1A 0x0A 0x00 0x00 0x00 0x0D 0x49 0x48 0x44 0x52 0x00 0x00 0x00 0x10
    Last bytes:
      0xE2 0x8A 0x24 0x69 0x53 0x4C 0xB3 0x03 0x00 0x00 0x00 0x00 0x49 0x45 0x4E 0x44 0xAE 0x42 0x60 0x82
    MD5: 8368b5c29a12b298cea2ad4b32955830
    
    Function (main.ImageFunc)(0x9f10e0):
    Len: 2048
    First bytes:
      0xED 0x1C 0x24 0xFF 0xED 0x1C 0x24 0xFF 0xED 0x1C 0x24 0xFF 0xED 0x1C 0x24 0xFF 0xED 0x1C 0x24 0xFF
    Last bytes:
      0x00 0x00 0xFF 0xFF 0x00 0x00 0xFF 0xFF 0x00 0x00 0xFF 0xFF 0x00 0x00 0xFF 0xFF 0x00 0x00 0xFF 0xFF
    MD5: ebdf44b7ad36d79b221a70ea2b0fa0c7
    
    Done.
    

    注意事项

    • 重要提示:我只使用(虚拟)RGBA 图像测试了代码,对于其他颜色格式可能需要额外的工作
    • 如所见,我没有进行任何错误处理,以保持代码简短(但无论如何,这超出了本问题的范围)

    【讨论】:

    • 不,这就是我正在做的事情——结果不一样。您甚至可以查看您是否使用 pil readbytes 读取它,并打印前 50 个字节与通常的 rb 文件读取的前 50 个字节读取它与您显示的不同。在 go 中阅读它我无法复制 tobytes 正在做的事情。遗憾的是,在这种情况下使用图像解码没有帮助
    • 我认为这个答案很有帮助。它表明tobytes() 方法正在返回图像解码字节的视图——而不是文件的原始字节。 @aescript要复制PIL在go中所做的事情,您需要解码图像,并将表示图像RGB(A)值的字节的MD5作为字节。
    • 不过如此。我尝试将它们像 R+B+G 一样连接起来,我尝试了 R1+B1+G1+R2+B2+G2... 等等,没有任何东西可以复制我从 pil 中得到的东西。这就是我问这个的原因
    • @aescript:我想我不明白。我展示了使用 PIL 的结果,并简单地读取图像(您从 Go 执行),它们不同(事实上它们的大小不同就足够了,计算哈希是无用的)。所以你也必须在 Go 中解码图像。单我对 Go 不熟悉(可能应该有一个图像编码/解码库)我指出了另一个 URL(似乎这样做)。
    • @aescript:您有机会查看我提供的 (Go) 代码吗?
    【解决方案2】:

    这里有一个根本性的误解。

    JPEG/PNG/TIFF 文件在磁​​盘上被编码。它包含以下部分或全部内容:

    • 图片宽度和高度
    • 图像色彩空间和压缩表
    • 创建日期
    • GPS 坐标
    • 作者
    • 版权
    • 压缩像素

    前几个字节将是一个 JPEG/PNG 幻数(或签名),然后是其他 PNG 块或 JPEG 部分,包含上面列出的信息。

    PIL Image,在转换为字节后,只包含未压缩的像素。前几个字节将是左上角的像素,接下来的几个字节将是第一行中的第二个像素。

    它们根本上是不同的,并且永远不会有相同的 MD5 校验和。

    【讨论】:

    • 我明白这一点。这个问题的重点是询问golang是否有一个库或其他东西可以像pil一样为我提供原始像素字节......没有真正的误解,只是寻找我可以在另一个中使用的类似功能语言
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-30
    相关资源
    最近更新 更多