是否有使用 PIL 或 cv2 将常见图片文件扩展名转换为 .PGM "P2" 的正确方法?

Is there a proper way to convert common picture file extensions into a .PGM "P2" using PIL or cv2?

编辑:问题已解决并更新了代码。

对于这么长的post,我提前表示歉意。我想为 table 带来尽可能多的东西。我的问题由两部分组成。

背景:我需要一个简单的 Python 脚本来将常见的图片文件扩展名转换为 .PGM ASCII 文件。我想出一个天真的解决方案没有任何问题,因为 PGM 看起来很简单。

# convert-to-pgm.py is a script for converting image types supported by PIL into their .pgm 
# ascii counterparts, as well as resizing the image to have a width of 909 and keeping the 
# aspect ratio. Its main purpose will be to feed NOAA style images into an APT-encoder 
# program.

from PIL import Image, ImageOps, ImageEnhance
import numpy as np

# Open image, convert to greyscale, check width and resize if necessary
im = Image.open(r"pics/NEKO.JPG").convert("L")

image_array = np.array(im)
print(f"Original 2D Picture Array:\n{image_array}")  # data is stored differently depending on 
                                                     # im.mode (RGB vs L vs P)
image_width, image_height = im.size
print(f"Size: {im.size}")      # Mode: {im.mode}")
# im.show()
if image_width != 909:
    print("Resizing to width of 909 keeping aspect ratio...")
    new_width = 909
    ratio = (new_width / float(image_width))
    new_height = int((float(image_height) * float(ratio)))
    im = im.resize((new_width, new_height))
    print(f"New Size: {im.size}")
# im.show()

# Save image data in a numpy array and make it 1D.
image_array1 = np.array(im).ravel()
print(f"Picture Array: {image_array1}")

# create file w .pgm ext to store data in, first 4 lines are: pgm type, comment, image size, 
# maxVal (=white, 0=black)
file = open("output.pgm", "w+")
file.write("P2\n# Created by convert-to-pgm.py \n%d %d\n255\n" % im.size)

# Storing greyscale data in file with \n delimiter
for number in image_array1:
    # file.write(str(image_array1[number]) + '\n')   #### This was the culprit of the hindered image quality...changed to line below. Thanks to Mark in comments.
    file.write(str(number) + '\n')
file.close()
im = im.save(r"pics/NEKO-greyscale.jpg")

# Strings to replace the newline characters
WINDOWS_LINE_ENDING = b'\r\n'
UNIX_LINE_ENDING = b'\n'

with open('output.pgm', 'rb') as open_file:
    content = open_file.read()

content = content.replace(WINDOWS_LINE_ENDING, UNIX_LINE_ENDING)

with open('output.pgm', 'wb') as open_file:
    open_file.write(content)
open_file.close()

这会生成一个 .PGM 文件,当用文本编辑器打开时,它看起来与使用 GIMP 导出为 .PGM 的同一图像相似(我之前的解决方案是使用 GIMP 导出工具手动转换图片,我找不到任何其他支持“P2”格式的转换器)。但是,与使用 GIMP 导出工具生成的图片相比,生成的图片质量严重下降。我尝试了几种图像增强方法(亮度、均衡、posterize、自动对比度等)以获得更好的结果,但 none 完全成功。所以我的第一个问题是:我可以做些什么来获得看起来更像 GIMP 生成的结果?我不追求完美,只是一点点清晰和学习经验。 如何自动调整{insert whatever}以获得最佳图片?

下面是我的版本生成的.PGM图片对比GIMP的版本,在文本编辑器中打开,同样输入.jpg

我的版本与 GIMP 的版本:

下面是在创建 .pgm 文件之前添加各种增强功能与原始 .jpg 和转换为灰度 ("L") 的原始 .jpg 的比较。所有照片均通过GIMP打开。

原始.jpg

灰度 .jpg,在 .convert("L") 命令之后

**这就是我希望我的 .PGM 看起来像的样子。 为什么 numpy 数组数据接近,但与 GIMP .PGM 文件中的数据不同,即使生成的灰度图像看起来与 GIMP 生成的相同? 答:因为它没有保存正确的数据。 :D

GIMP 生成的 .PGM

我生成的 .PGM

我生成的 .PGM 亮度较低,Brightness.enhance(0.5)

生成的 .PGM 带有 posterize,ImageOps.posterize(im, 4)

第二个问题: 我的最后一个问题是使用各种 PGM 查看器查看 .PGM 图片,例如这些在线工具 (here and here)。 .PGM 文件无法通过上述 link 之一查看,但在使用其他 link 或 GIMP 查看时“正常”。同样,我用我的脚本生成的 .PGM 文件目前也与我打算使用它的程序不兼容。这对我来说最重要,因为它的目的是将格式正确的 PGM 图像提供给程序。我确定 .PGM 文件的前四行中的某些内容正在改变程序感知它确实是 PGM 的能力,而且我很确定这是微不足道的,因为其他一些查看器也没有能力阅读我的 PGM。所以我的主要问题是:是否有正确的方法来进行这种转换,或者通过适当的调整,我的脚本是否适合table?我是否漏掉了一些显而易见的东西? 我对图像处理的了解很少。

GitHub link 到我将 .PGM 图像输入的程序:here

有关此特定问题的更多信息:当 运行 使用我的其中一个 .PGM 图像时,程序会抛出错误,但与使用 GIMP 生成的 .PGM 图像完美配合。该程序在 C++ 中,行“ASSERT(buf[2] == '\n')” returns 错误,暗示我的 .PGM 文件格式不正确。如果我注释掉这一行并重新编译,另一个“ASSERT(width == 909)...”会抛出一个错误,暗示我的 .PGM 没有 909 像素的宽度。如果我也注释掉这一行并重新编译,我将留下臭名昭著的“分段错误(核心已转储)”。我在 Windows 上用 cygwin64 编译了这个。一切似乎都已准备就绪,所以程序在读取文件内容(或理解“\n”?)时遇到了问题。 如果用文本编辑器查看时,我的版本和 GIMP 的版本在格式上基本相同,这怎么可能?

终端输出:

感谢大家的帮助,any and all insight/criticism is acceptable.

我的问题的第一部分在评论中得到了回答,这对我来说是一个愚蠢的错误,因为我还在学习语法。上面的代码现在可以正常工作了。

我能够对我的问题的第二部分做更多的研究,我注意到一些非常重要的东西,昨天错过它也觉得很傻。

当然,我的程序在读取 '\n' 字符时出现问题的原因很简单,因为 Windows 将换行符编码为 CRLF,也就是 '\r\n' 而不是 Unix LF 的方式又名 '\n'。所以在我的脚本最后我只是添加了简单的代码[取自]:

# replacement strings
WINDOWS_LINE_ENDING = b'\r\n'
UNIX_LINE_ENDING = b'\n'


with open('output.pgm', 'rb') as open_file:
    content = open_file.read()

content = content.replace(WINDOWS_LINE_ENDING, UNIX_LINE_ENDING)

with open('output.pgm', 'wb') as open_file:
    open_file.write(content)

现在,无论文本文件是用 CRLF 还是 LF 编码,脚本都可以正常运行。