用豆包实现将图片色彩渐变部分近似成素色

之前用豆包生成了一张很酷的机械猫的图片,想着如果能依据它来做一个矢量图就好了,可是原图片色彩非常丰富,很难符合矢量图的要求,于是想着,如果有种方法,将图片中的色彩渐变部分近似成素色,那就好了。于是抱着试试看的心态,向豆包进行了询问。


我:有没有一种算法,将一副图片中的所有色彩渐变部分,分别近似成素色,以便可以勾勒出矢量图形?

豆包:有几种算法和工具可以实现将图片色彩渐变部分近似成素色,以辅助生成矢量图形,以下是常见的方法:

色彩量化算法

K-Means 聚类算法:这是一种无监督学习算法,常用于图像色彩量化。它把图像中的所有像素点看成是分布在高维空间(RGB 三维空间)中的数据点,尝试将这些点聚类成 K 个类别。算法不断迭代更新聚类中心,直至各聚类内的点到中心的距离之和最小。每个聚类最终就可以用一个代表色(素色)来替代该聚类内所有像素的颜色,从而让渐变区域变成一块块的纯色区域。例如,在 Python 的scikit-learn库中,能轻松调用KMeans类实现这个功能,对图像进行处理后,再用矢量绘图库(如svgwrite )基于处理后的色彩信息勾勒出简单的矢量图形。

中位切分算法:它是专门为图像色彩量化设计的。该算法从包含所有图像颜色的色彩空间立方体出发,每次沿色彩范围最大的维度,将立方体切成两半,持续这个过程,直到得到预定数量的色彩区域,每个区域用其中位色表示,使得渐变过渡的色彩被简化成有限的纯色。

图像编辑软件与工具

Adobe Illustrator:打开图片后,使用 “图像描摹” 功能,软件会自动分析图像的色彩和形状,将色彩渐变区域智能转化成不同的纯色区域填充,之后可进一步编辑成矢量图形。用户还能调整描摹的参数,精细控制色彩简化的程度与图形细节保留程度。

Inkscape(开源免费):导入位图图片后,执行 “路径 - 描摹位图” 命令,它内置了多种描摹模式,其中部分模式可以把色彩渐变平缓的地方处理成纯色填充的图形块,完成从位图到矢量图形的初步转换,后续再按需编辑矢量节点、优化图形。


感觉 K-Means 好适合我的样子,毕竟我的电脑上已经安装了 python 的环境,于是我继续向豆包进行提问

我:如何在 python 3.8.3 中安装 scikit-learn 呢

豆包:在 Python 3.8.3 中安装scikit-learn,你可以使用以下几种常见方法:

使用 pip 安装(推荐)

确保你已经安装了pip,一般情况下,Python 3.8.3 自带了pip 。打开命令行终端(Windows 下是 “命令提示符” 或 “PowerShell”,Linux 和 macOS 下是终端程序)。

在终端中输入以下命令:

【bash】

pip install -U scikit-learn

这条命令中,-U 表示升级到最新版本,如果你的系统上已经安装了旧版scikit-learn,此参数会将它更新;要是全新安装,它也能保证装上的是最新稳定版。安装过程中,pip 会自动处理依赖包的下载与安装,耐心等待片刻即可完成安装。

安装完成后,你可以在 Python 交互环境或脚本中导入scikit-learn验证是否安装成功:

【python】

import sklearn

若没有报错,就说明scikit-learn已成功安装到你的系统中。

我:那在 python 中如何查看 scikit-learn 的版本

豆包:在 Python 中查看 scikit-learn 的版本可以通过以下几种方式:

1. 直接导入并打印版本号

【python】

import sklearn

print(sklearn.__version__)

这种方式是最直接的,你首先导入 sklearn 库,然后通过 __version__ 属性来获取并打印其版本号。

2. 使用 pkg_resources 模块(适用于 Python 3.8.3)

【python】

import pkg_resources

try:

    version = pkg_resources.get_distribution("scikit-learn").version

    print(version)

except pkg_resources.DistributionNotFound:

    print("scikit-learn is not installed.")

这里使用了 pkg_resources 模块,该模块是 setuptools 的一部分。get_distribution 方法尝试获取 scikit-learn 的分发信息,如果能找到,则获取其版本号;如果未找到,则打印出 scikit-learn 未安装的信息。

3. 在命令行中使用 pip

【bash】

pip show scikit-learn

打开终端或命令提示符,输入上述命令,你将看到 scikit-learn 的详细信息,包括版本号、安装位置、依赖项等。

解释

第一种方法简单且常用,在代码中需要 scikit-learn 时,可以直接使用 sklearn.__version__ 查看其当前版本,方便你在代码中对 scikit-learn 的不同版本进行兼容处理。

第二种方法的优点是可以更通用地检查任何已安装的 Python 包的版本,即使你不确定是否安装了 scikit-learn,也可以避免导入错误。

第三种方法是在命令行中使用,适合在不启动 Python 脚本或交互环境的情况下,快速检查 scikit-learn 的安装信息。

你可以根据自己的需要选择其中一种方法,以确认你正在使用的 scikit-learn 的版本,以便进行开发和调试。


我决定再过分一点,让它帮我直接把代码写出来

我:那帮我写段 python 的脚本 使用 K-Means 算法 实现从一个 jpg 文件到另外一个 jpg 文件的转换

豆包:以下是一个使用 Python 中的 scikit-learn 库中的 K-Means 算法将一个 JPG 文件转换为另一个 JPG 文件的脚本。这个脚本会读取一个 JPG 文件,将其像素颜色使用 K-Means 聚类,然后用聚类中心的颜色替换原像素颜色,最后保存为新的 JPG 文件。

from sklearn.cluster import KMeans

from PIL import Image

import numpy as np

def image_kmeans(input_path, output_path, n_clusters=8):

    # 打开图像文件

    image = Image.open(input_path)

    # 将图像转换为 numpy 数组

    image_np = np.array(image)

    # 获取图像的形状

    rows, cols, channels = image_np.shape

    # 重塑数组,将其转换为 (rows * cols, channels) 形状

    pixels = image_np.reshape(rows * cols, channels)

    # 初始化 K-Means 算法

    kmeans = KMeans(n_clusters=n_clusters, random_state=0).fit(pixels)

    # 获取聚类中心

    cluster_centers = kmeans.cluster_centers_

    # 获取每个像素的聚类标签

    labels = kmeans.labels_

    # 用聚类中心的颜色替换原像素颜色

    quantized_pixels = cluster_centers[labels].astype(np.uint8)

    # 重塑数组回原图像形状

    quantized_image = quantized_pixels.reshape(rows, cols, channels)

    # 转换为图像对象

    quantized_image = Image.fromarray(quantized_image)

    # 保存图像

    quantized_image.save(output_path)

if __name__ == "__main__":

    input_image_path = "input.jpg"  # 输入 JPG 文件的路径

    output_image_path = "output.jpg"  # 输出 JPG 文件的路径

    n_clusters = 8  # 聚类中心的数量,可根据需要调整

    image_kmeans(input_image_path, output_image_path, n_clusters)

代码解释

导入必要的库

from sklearn.cluster import KMeans:从 scikit-learn 库中导入 KMeans 算法。

from PIL import Image:从 PIL(Python Imaging Library)中导入 Image 类,用于图像的处理。

import numpy as np:导入 numpy 库并将其简称为 np,用于处理图像数组。

定义 image_kmeans 函数

image = Image.open(input_path):使用 PIL 的 Image.open 打开输入图像文件。

image_np = np.array(image):将图像转换为 numpy 数组,方便后续处理。

rows, cols, channels = image_np.shape:获取图像的行数、列数和通道数。

pixels = image_np.reshape(rows * cols, channels):将图像数组重塑为 (rows * cols, channels) 形状,以方便 K-Means 处理。

kmeans = KMeans(n_clusters=n_clusters, random_state=0).fit(pixels):初始化 K-Means 算法并拟合像素数据,n_clusters 是聚类中心的数量,random_state 用于可重复性。

cluster_centers = kmeans.cluster_centers_:获取 K-Means 聚类的中心。

labels = kmeans.labels_:获取每个像素所属的聚类标签。

quantized_pixels = cluster_centers[labels].astype(np.uint8):将每个像素的颜色替换为其所属聚类中心的颜色,并将结果转换为 uint8 类型,以便于显示。

quantized_image = quantized_pixels.reshape(rows, cols, channels):将结果重塑回原图像的形状。

quantized_image = Image.fromarray(quantized_image):将 numpy 数组转换回 PIL 图像对象。

quantized_image.save(output_path):保存生成的图像到输出路径。

主程序部分

input_image_path 和 output_image_path:分别定义输入和输出 JPG 文件的路径。

n_clusters:设置聚类中心的数量,可以根据需要调整。

image_kmeans(input_image_path, output_image_path, n_clusters):调用 image_kmeans 函数进行转换。


我试着运行了豆包给我的代码,执行的很顺利,太棒了,只是在执行时出现了一个 warning,于是,我继续向豆包进行提问。

我:我运行它时,报了这样一个 warning:FutureWarning: The default value of `n_init` will change from 10 to 'auto' in 1.4. Set the value of `n_init` explicitly to suppress the warning  super()._check_params_vs_input(X, default_n_init=10)

豆包:错误点和优化点

这个警告信息表明你使用的 scikit-learn 库的 K-Means 算法中,n_init 参数的默认值在未来的版本(1.4 及以后)将会从 10 改变为 'auto'。当前使用的默认值 10 可能会在未来发生变化,为了避免代码在更新 scikit-learn 后出现意外的行为,建议显式设置 n_init 参数的值。

修改后的代码

from sklearn.cluster import KMeans

from PIL import Image

import numpy as np

def image_kmeans(input_path, output_path, n_clusters=8):

    # 打开图像文件

    image = Image.open(input_path)

    # 将图像转换为 numpy 数组

    image_np = np.array(image)

    # 获取图像的形状

    rows, cols, channels = image_np.shape

    # 重塑数组,将其转换为 (rows * cols, channels) 形状

    pixels = image_np.reshape(rows * cols, channels)

    # 初始化 K-Means 算法,显式设置 n_init 参数的值

    kmeans = KMeans(n_clusters=n_clusters, random_state=0, n_init=10).fit(pixels)

    # 获取聚类中心

    cluster_centers = kmeans.cluster_centers_

    # 获取每个像素的聚类标签

    labels = kmeans.labels_

    # 用聚类中心的颜色替换原像素颜色

    quantized_pixels = cluster_centers[labels].astype(np.uint8)

    # 重塑数组回原图像形状

    quantized_image = quantized_pixels.reshape(rows, cols, channels)

    # 转换为图像对象

    quantized_image = Image.fromarray(quantized_image)

    # 保存图像

    quantized_image.save(output_path)

if __name__ == "__main__":

    input_image_path = "input.jpg"  # 输入 JPG 文件的路径

    output_image_path = "output.jpg"  # 输出 JPG 文件的路径

    n_clusters = 8  # 聚类中心的数量,可根据需要调整

    image_kmeans(input_image_path, output_path=output_image_path, n_clusters=n_clusters)

代码解释

与原代码相比,修改的部分主要是在 KMeans 类的初始化中添加了 n_init=10 参数。

kmeans = KMeans(n_clusters=n_clusters, random_state=0, n_init=10).fit(pixels):通过显式设置 n_init 的值为 10,确保在使用 K-Means 算法时,即使 scikit-learn 版本更新,该参数的默认值发生变化,也不会影响到代码的行为。


我自己查看了豆包新给出的代码,将改动的部分手动合并到我的本地代码中,运行了一下,果然 warning 消失了

为了更好的理解代码,我继续向豆包发问。

我:这里的 n_clusters,是指的一共用多少种颜色来重新渲染这个图片吗?是不是设置了多少,生成的图片就只会有多少种颜色?

豆包:解释

在使用 K-Means 算法对图像进行颜色量化的这个上下文中,n_clusters 参数确实是指最终用于重新渲染图像的颜色种类的数量。

当你将 n_clusters 设置为一个特定值时,K-Means 算法会尝试将图像中的所有像素颜色聚类为 n_clusters 个类别。每个类别会有一个代表颜色(即聚类中心),在完成聚类操作后,图像中的每个像素会被替换为其所属类别的代表颜色。因此,最终生成的图像将只包含 n_clusters 种颜色。

例如:

如果你将 n_clusters 设置为 2,K-Means 算法会将图像中所有的像素颜色划分为 2 个类别,然后找出这两个类别的代表颜色。图像中的每个像素会根据它属于哪个类别,被替换为该类别的代表颜色,最终生成的图像将只显示这两种颜色。

如果你将 n_clusters 设置为 8,最终生成的图像将只包含 8 种颜色。

影响

较小的 n_clusters 值会导致图像的颜色较少,图像会显得更加简洁和抽象,因为许多原始的颜色信息被合并为较少的类别,可能会丢失一些细节,但可以产生独特的艺术效果。

较大的 n_clusters 值会保留更多的细节,因为有更多的颜色可以表示图像的不同部分,但与原始图像相比,仍然会对颜色进行一定程度的简化,并且不会超过 n_clusters 种颜色。

注意事项

你可以根据想要达到的视觉效果和图像复杂度,多次调整 n_clusters 的值。例如,对于简单的卡通图像,可能较小的 n_clusters 就足够;对于复杂的风景或人物照片,可能需要较大的 n_clusters 来保持足够的细节。

虽然 n_clusters 可以让你控制颜色的种类,但图像的最终效果还会受到图像本身的颜色分布以及 K-Means 算法随机性(可以通过 random_state 参数部分控制)的影响。

通过不断调整 n_clusters 的值,你可以找到最适合你需求的图像转换效果,同时可以尝试不同的 random_state 值来观察不同的聚类结果,以得到满意的图像。

在使用 K-Means 进行图像颜色量化时,n_clusters 是一个重要的控制参数,它直接影响了最终图像的颜色数量和整体效果。


我:那 random_state 和 n_init 分别如何取值呢,都会产生什么效果

豆包:以下是对 random_state 和 n_init 参数的解释及取值建议:

random_state

含义:random_state 是一个随机种子,用于控制 K-Means 算法中的随机初始化步骤。在 K-Means 算法中,初始的聚类中心是随机选择的,random_state 确保了这种随机选择的可重复性。

取值

可以取任意整数,例如 random_state = 0、random_state = 42 等。

如果你将 random_state 设置为 None(默认值),则每次运行代码时,初始的聚类中心会随机生成,结果可能会有所不同。

效果

当 random_state 被设置为一个特定的整数时,每次运行代码,只要其他参数和输入数据不变,K-Means 算法得到的结果将是一致的,这对于结果的可重复性和调试非常有用。

不同的 random_state 值会导致不同的初始聚类中心,因此可能会产生不同的最终聚类结果,尤其是对于那些数据分布比较复杂,且 K-Means 算法可能陷入局部最优解的情况。通过改变 random_state 的值,你可以观察到不同的颜色聚类效果,找到最满意的结果。

n_init

含义:n_init 表示运行 K-Means 算法的次数,每次使用不同的初始聚类中心。最终的结果将是在 n_init 次运行中,具有最小惯性(inertia,即样本到最近聚类中心的平方和)的结果。

取值

在 scikit-learn 的 K-Means 中,n_init 的默认值是 10(在 scikit-learn 1.4 版本之前),并且在未来版本中会改为 'auto'。你可以根据数据量和计算资源来调整这个值。

如果你想尝试更多的初始条件,可以将 n_init 设置为一个较大的整数,例如 n_init = 20 或 n_init = 50。但要注意,较大的 n_init 值会增加计算时间。

效果

较大的 n_init 值可以帮助 K-Means 算法找到更优的聚类结果,因为它尝试了更多不同的初始聚类中心,有助于避免陷入局部最优解。

对于相对简单的数据,较小的 n_init 值(如 5 或 10)可能已经足够找到较好的聚类结果,并且计算成本较低。


最后,我希望豆包帮我完善一下代码,在 main 函数中读取命令行参数

我:如何在命令行向 python 的 __main__ 里传递参数

豆包:以下是几种在命令行向 Python 的 __main__ 中传递参数的方法:

1. 使用 sys.argv

import sys

def main():

    if len(sys.argv) > 1:

        # sys.argv[0] 是脚本的名称,从 sys.argv[1] 开始是命令行参数

        input_path = sys.argv[1]

        output_path = sys.argv[2]

        n_clusters = int(sys.argv[3]) if len(sys.argv) > 3 else 8

        random_state = int(sys.argv[4]) if len(sys.argv) > 4 else 0

        n_init = int(sys.argv[5]) if len(sys.argv) > 5 else 10

    else:

        input_path = "input.jpg"

        output_path = "output.jpg"

        n_clusters = 8

        random_state = 0

        n_init = 10

    print(f"Input path: {input_path}")

    print(f"Output path: {output_path}")

    print(f"Number of clusters: {n_clusters}")

    print(f"Random state: {random_state}")

    print(f"Number of initializations: {n_init}")

if __name__ == "__main__":

    main()

2. 使用 argparse 模块(推荐)

import argparse

def main():

    parser = argparse.ArgumentParser(description='Image quantization using K-Means')

    parser.add_argument('input_path', type=str, help='Input image path')

    parser.add_argument('output_path', type=str, help='Output image path')

    parser.add_argument('--n_clusters', type=int, default=8, help='Number of clusters')

    parser.add_argument('--random_state', type=int, default=0, help='Random state')

    parser.add_argument('--n_init', type=int, default=10, help='Number of initializations')

    args = parser.parse_args()

    print(f"Input path: {args.input_path}")

    print(f"Output path: {args.output_path}")

    print(f"Number of clusters: {args.n_clusters}")

    print(f"Random state: {args.random_state}")

    print(f"Number of initializations: {args.n_init}")

if __name__ == "__main__":

    main()


最后的最后,我感觉同时提供输入图片路径和输出图片路径,还是有点繁琐了,于是,继续请求豆包的帮忙。

我:我想将 output_path 从 input_path 自动生成,生成的规则是在 input_path 的文件名上加 -export 后缀,在生成时 需检查该文件是否已存在,如果已存在 则继续加 -1 后缀,如果依然存在,则加 -2 后缀,直到 output_path 执行的文件不存在为止

豆包:以下是使用 os 和 argparse 模块来实现你需求的 Python 脚本:

import os

import argparse

def generate_output_path(input_path):

    base_name, ext = os.path.splitext(input_path)

    output_path = f"{base_name}-export{ext}"

    index = 1

    while os.path.exists(output_path):

        output_path = f"{base_name}-export-{index}{ext}"

        index += 1

    return output_path

def main():

    parser = argparse.ArgumentParser(description='Image quantization using K-Means')

    parser.add_argument('input_path', type=str, help='Input image path')

    parser.add_argument('--n_clusters', type=int, default=8, help='Number of clusters')

    parser.add_argument('--random_state', type=int, default=0, help='Random state')

    parser.add_argument('--n_init', type=int, default=10, help='Number of initializations')

    args = parser.parse_args()

    output_path = generate_output_path(args.input_path)

    print(f"Input path: {args.input_path}")

    print(f"Output path: {output_path}")

    print(f"Number of clusters: {args.n_clusters}")

    print(f"Random state: {args.random_state}")

    print(f"Number of initializations: {args.n_init}")

if __name__ == "__main__":

    main()

代码解释

导入模块

import os:用于文件和路径操作,如检查文件是否存在和拆分文件路径。

import argparse:用于解析命令行参数。

定义 generate_output_path 函数

base_name, ext = os.path.splitext(input_path):使用 os.path.splitext 将输入路径拆分为文件名和扩展名。

output_path = f"{base_name}-export{ext}":生成初始的输出路径,在原文件名基础上加 -export 后缀。

while os.path.exists(output_path)::循环检查生成的输出路径是否已经存在。

output_path = f"{base_name}-export-{index}{ext}":如果存在,添加 -<index> 后缀,index 从 1 开始,每次循环加 1。

定义 main 函数

parser = argparse.ArgumentParser(description='Image quantization using K-Means'):创建一个 argparse 参数解析器。

parser.add_argument('input_path', type=str, help='Input image path'):添加位置参数 input_path,用于输入图像路径。

parser.add_argument('--n_clusters', type=int, default=8, help='Number of clusters'):添加可选参数 n_clusters 并设置默认值为 8。

parser.add_argument('--random_state', type=int, default=0, help='Random state'):添加可选参数 random_state 并设置默认值为 0。

parser.add_argument('--n_init', type=int, default=10, help='Number of initializations'):添加可选参数 n_init 并设置默认值为 10。

args = parser.parse_args():解析命令行参数。

output_path = generate_output_path(args.input_path):调用 generate_output_path 函数根据输入路径生成输出路径。


终于完成了,很好用,很方便。

我:n_init 和 n_clusters 之间有什么关系么,选择这两个参数时,需不需要它们两个很接近,或者其中一个必须大于另外一个?

豆包:n_init 和 n_clusters 的关系

n_init 和 n_clusters 是 K-Means 算法中两个不同的参数,它们之间没有严格的数值关系,并且在选择它们的值时,也不需要其中一个必须大于另一个或两者接近。

n_clusters

它表示你期望将数据聚类成多少个类别。在图像颜色量化的例子中,它代表了最终图像中使用的颜色种类。

它主要影响聚类结果的数量和图像的最终视觉效果。选择 n_clusters 的值取决于你想要达到的图像颜色简化程度或数据聚类的粒度。如果 n_clusters 较小,图像会更抽象,因为更多的颜色被归为较少的类别;如果 n_clusters 较大,会保留更多细节,但仍会比原图像的颜色少,因为它是将相似颜色进行了合并。

n_init

它表示 K-Means 算法运行的次数,每次使用不同的初始聚类中心。其目的是为了找到最优的聚类结果,因为 K-Means 算法对初始聚类中心敏感,不同的初始中心可能会导致不同的聚类结果,部分结果可能陷入局部最优。通过多次运行(由 n_init 决定),最终选取使聚类结果的惯性(样本到其最近聚类中心的平方和)最小的结果。

较大的 n_init 值可以提高找到全局最优解的可能性,但会增加计算成本。

如何选择 n_init 和 n_clusters 的值

n_clusters 的选择

对于图像颜色量化:

如果你想要更简洁的图像,可以选择较小的 n_clusters 值,如 2 到 10,这样图像会更具抽象感。

如果你想要保留更多细节,可选择较大的 n_clusters 值,如 32 或 64,甚至更高,但要考虑最终图像仍会对颜色进行简化。

对于数据聚类:

根据你对数据的先验知识或聚类的目的选择 n_clusters。例如,如果你要将客户分成不同的群体,你可能根据业务需求确定几个主要类别,比如将用户分成 3 类(高价值、中价值、低价值用户),此时 n_clusters 可设置为 3。

n_init 的选择

对于小规模数据或简单数据分布,n_init 可以设置为较小的值,如 5 到 10,因为数据不太复杂,较少的尝试可能足以找到较好的聚类结果。

对于大规模或复杂的数据,为了找到更好的聚类结果,可考虑设置 n_init 为 20 或更高,但要注意计算成本会增加。


在文章的最后,附上其中一张处理后的图(n_clusters=4):


©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 小伙伴们大家好~o( ̄▽ ̄)ブ我是菜菜,这里是我的sklearn课堂第6期:sklearn中的聚类算法K-Mean...
    菜菜TsaiTsai阅读 420评论 0 1
  • 聚类分析在客户细分中极为重要。有三类比较常见的聚类模型,K-mean聚类、层次(系统)聚类、最大期望EM算法。在聚...
    keeeeeenon阅读 3,078评论 0 3
  • 1.1 概述 聚类算法又叫无监督算法,其目的是将数据划分有意义或有用的组或簇,划分一般是基于业务需求或建模需求...
    诗云HSY阅读 1,666评论 0 1
  • 原理 随机从数据集中选择k个点作为我们聚类的中心点; 每个点分配到离它最近的类中心点,就形成了k类。然后重新计算每...
    dingtom阅读 411评论 0 0
  • 源自《python数据分析与挖掘实战》 项目目标 借助航空公司客户数据,对客户进行分类 对不同的客户类别进行特征分...
    林清猫耳阅读 5,495评论 3 4

友情链接更多精彩内容