使用Python将SVG转换为灰度图像

4

场景

我正在尝试在 Windows 10 设备上的 Anaconda 4.8.3 中使用 Python 3.6 将一个有颜色的 example.svg 文件转换为灰度图像 example_gs.svg

尝试

首先我尝试应用正则表达式将 'rgb(xxx,yyy,zzz)' 转换为黑色,但这样会创建一个黑色矩形并导致图像丢失。接下来我安装了 Inkscape 并运行了一个灰度命令,看起来可以工作,但没有修改 example.svg。第三次尝试使用 Pillow Image 时无法加载 .svg

MWE

# conda install -c conda-forge inkscape
# https://www.commandlinefu.com/commands/view/2009/convert-a-svg-file-to-grayscale
# inkscape -f file.svg --verb=org.inkscape.color.grayscale --verb=FileSave --verb=FileClose
import re
import os
import fileinput
from PIL import Image
import cv2

# Doesn't work, creates a black square. Perhaps set a threshold to not convert rgb white/bright colors
def convert_svg_to_grayscale(filepath):
    # Read in the file
    with open(filepath, 'r') as file :
      filedata = file.read()

    # Replace the target string
    filedata = re.sub(r'rgb\(.*\)', 'black', filedata)

    # Write the file out again
    with open(filepath, 'w') as file:
      file.write(filedata)
    
# opens inkscape, converts to grayscale but does not actually export to the output file again
def convert_svg_to_grayscale_inkscape(filepath):
   command = f'inkscape -f {filepath} --verb=org.inkscape.color.grayscale --verb=FileSave --verb=FileClose'
   os.system(f'cmd /k {command}')
   
# Pillow Image is not able to import .svg files
def grayscale(filepath):
    image = Image.open(filepath)
    cv2.imwrite(f'{filepath}', image.convert('L'))


# walks through png files and calls function to convert the png file to .svg
def main():
    filepath = 'example.svg'            
    convert_svg_to_grayscale_inkscape(filepath)
    convert_svg_to_grayscale(filepath)
    grayscale(filepath)
                

if __name__ == '__main__':
    main()

问题

如何在 Windows 设备上使用 Python 3.6 将彩色的 .svg 文件转换为灰度图像?

2个回答

2
您选择了一个转换为灰度的正确工具。您之前的尝试很不错,但是您需要导入提供svg2png功能的cairosvg。接下来使用Pillow加载PNG文件并将其转换为np.array,然后可以轻松地使用openCV加载并像您所做的那样将其转换为灰度。最后,您可以使用svglib和reportlab在SVG中导出图像。 请使用以下示例代码片段: https://dev59.com/Gbvoa4cB1Zd3GeqP6I8K#62345450

1
将矢量图像转换为PNG光栅图像,然后再转换回SVG矢量图像将会丢失大量的信息 - 如果它能够合理运作的话。 - ccprog
你怎么打算进行反向转换呢?根据svglib文档,它无法接受PNG作为输入,只能接受SVG。 - ccprog
这是一个在Python中将png转换为svg的实现。同时,我很乐意了解您更好的解决方案并从中学习。这只是我能想到的第一个解决方案。 - Hesam Korki
1
你提供的代码将栅格图像的每个像素转换为1*1像素矩形。虽然生成的图像在技术上是矢量图像,但已失去了可伸缩性:将其放大10倍,您将只看到一些块。我没有好的解决方案,我的评论是关于栅格和矢量图像之间的一般关系以及矢量到栅格转换总是会导致信息丢失的数学事实。 - ccprog

1

被接受的解决方案对SVG进行了光栅化处理。因此,它基本上将矢量图形转换为像素图像。我提出了一个版本,保持了SVG的所有理想特性。

您可以尝试以下解决方案。该代码还可用作在Python中编写脚本进行其他SVG相关更改的模板。

原则

代码所做的是使用XML解析器解析SVG。为此,使用XPath并查找所有具有style属性的元素。作为参考:这是SVG中示例行的外观:

style="fill:#ffe6cc;fill-opacity:1;fill-rule:nonzero;stroke:none"

我们需要进行字符串编辑,将RGB #ffe6cc 更改为灰度值。代码的大部分工作就是这样做。

最小工作示例

#!/usr/bin/env -S python3
import xml.etree.ElementTree as ET
from xml.etree.ElementTree import ElementTree
tree = ET.parse('example.svg')
root = tree.getroot()

# using XPath addressing
for elem in root.findall(".//{http://www.w3.org/2000/svg}path[@style]"):
    style = elem.attrib['style']
    print(style)

    # do string editing
    d = {}
    for i in style.split(';'):
        att,value = i.split(':')
        d[att] = value
    for atatt in ['fill','stroke']:
        # convert fill or stroke to grayscale
        if atatt in d and d[atatt] != 'none':
            s = d[atatt]
            r = int(s[1:3],16)
            g = int(s[3:5],16)
            b = int(s[5:],16)
            gray = int(0.3*r + 0.59*g + 0.11*b + 0.5)
            snew = '#%02x%02x%02x' % (gray, gray, gray)
            #print(s,r,g,b,gray,snew)
            d[atatt] = snew
    # write back dict
    s = ';'.join(map(':'.join, d.items()))
    print(s)

    # write back edited string
    elem.attrib['style'] = s

with open('example_gs.svg', 'wb') as f:
    ElementTree(root).write(f)

欢迎提出改进意见。


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