使用 K-Means 聚类进行图像分割

原文:towardsdatascience.com/image-segmentation-with-k-means-clustering-1bc53601f033

你可以在这里查看这个项目的笔记本 here

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/dd72df59bc4201bdd2fffd5cf9c528df.pnghttps://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/0a8b43d044b33bb9bae13ba3b85d7a02.png

LEFT: 原始照片,RIGHT: 分割图像(5 种颜色/段)

观察上面的图像,我们看到一个图像色调分离过滤器,它给图像带来卡通般的视觉效果,但幕后,这个过滤器实际上正在使用一种称为聚类的机器学习算法。

在探索这个过程的原理并了解我们如何在 Python 中实现它之前,让我们先看看为什么我们可能想要这样做。


图像分割

在一张普通照片中,一个像素可以呈现大约 16.7 百万种不同的颜色。然而,在这张处理过的图像中,却只有 5 种不同的颜色。我们已经将所有像素分成 5 个不同的组,将图像分割成这些不同的颜色区域。

我们还减少了图像中的噪声和变化量。因此,如果这要在某些其他机器学习应用中使用,我们已经大大减少了需要处理的数据量,尤其是如果这应用于整个图像库的话。

尽管我们简化了这张图像,但我们仍然保留了大部分重要的结构数据。我们仍然能够识别形状和形式、阴影和亮部,以及许多不同的质感和图案。所有告诉我们这是一张鸟儿站在墙上的图片的信息仍然存在。

这个过程不仅适用于图像。我们可以在其他不同上下文中的各种数据集上使用相同的工具,简化它们并减少所需的数据处理量。


除了减少数据处理需求之外,这个算法还有更多直接的应用,因为它使我们能够更容易地识别图像中的特征。例如,下面图像左侧的湖几乎完全是单一像素颜色,与湖周围的所有像素都不同,这使得它很容易被隔离。

这种技术可以用来追踪极地冰盖的大小;通过获取一张图像,应用这个过滤器来识别冰和海洋像素,然后你可以轻松地计算出被冰覆盖的区域。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/3afcac1a3545fa830603dc844ab7a435.pnghttps://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/06fda1610e8e7854d44f7a6627af0bd8.png

LEFT: 原始照片,RIGHT: 分割图像(5 种颜色)

聚类

支持此过程的机器学习技术被称为聚类。这是一个用于在数据集中识别簇或组的过程。例如,对于图像,我们识别出颜色相似的像素组。我们希望找到的组数是算法的一个参数。

聚类主要有三种不同类型:基于密度的、基于质心的和层次化的,每种类型都有许多不同的算法可以根据情况使用。对于这篇文章,我们将实现一种称为K-Means 聚类的基于质心的算法。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/773897c7b3d8b32db2bc8e45cd02216d.png

展示聚类算法结果的插图

我们将使用这个聚类算法对由图像数据组成的集合进行操作。图像中的每个像素都可以用 3 个值来表示,这些值对应于每个颜色通道的强度:红色、绿色和蓝色。每个强度值将在 0 到 255 之间。

如果我们取一个像素值,比如[103, 56, 213],这是一种明亮的紫色,我们可以在一组轴上将其绘制为坐标点(如下所示)。如果我们对图像中的所有像素重复此操作,我们可能会发现在生成的图中开始形成“点群”。所有蓝色像素将彼此靠近(因为它们都有相似的颜色值),绿色像素也是如此,以及黄色像素。结果可能看起来像这样:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/2ec5c70ec1d1f729b4f32ac848df3c6f.png

在三维空间中绘制的像素值

虽然从这个固定的视角来看并不容易看出,但很明显,在数据中存在一些簇,其中点密集地聚集在一起。

K-Means 聚类

让我们深入了解 K-Means 聚类是如何工作的。

我们首先选择一个k的值,这将是我们想要将数据分组成的簇的数量。选择正确的值是一个重要的步骤,因为它可以大大影响算法的成功。如果你碰巧知道你的数据点属于多少个不同的类别,那么你应该选择这个值。如果不是,有各种方法(如肘部方法)可以识别k的最佳值。目标是减少簇内变异(簇内的点应该彼此靠近),并增加簇间变异(簇本身尽可能明显)。

对于这个应用,处理图像时,很容易看出所选值如何影响结果。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/d8d9b5050890d0f141dffa5d19be3a26.png

不同 k 值的示例:2,3,5,7(从左到右,从上到下)

在我们选择了k的值之后,下一步是设置我们的初始质心点这些代表每个簇的中心。我们为每个质心随机选择一组坐标,在可能的值范围内。

然后重复以下步骤:

  • 计算每个像素与每个质心之间的距离

  • 将每个像素分配给最近的质心

  • 对于每个质心,找到所有分配像素的平均值,这应该对每个维度分别进行。

  • 通过更新它们的坐标到这些平均值来“移动”质心

这会一直重复,直到质心停止移动。这表明簇没有进一步的变化,每个像素都被分配到最近的簇,质心处于最佳位置。

现在我们将用 Python 实现这个算法的简单版本。正如我们在测试中将会看到的那样,这不是一个计算效率高的方法,但它是一个非常直接的方法。

在以后,我们将介绍一种更有效的方法,使用向量计算。这将使我们能够处理更高分辨率的图像,显著减少执行时间。


Python 算法 #1

我们将首先导入我们的图像并将其转换为像素数组。

img=cv2.imread("bird-small.jpg")# swap colour channels so that it is displayed correctlyimg=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)# display imageplt.axis('off')plt.imshow(img);# split into channelsr,g,b=cv2.split(img2)# reformat pixel colours into 1xn arrayr=np.array(r.flatten())g=np.array(g.flatten())b=np.array(b.flatten())

这张图像的维度是 (267, 400, 3) ,即,高度 = 267,宽度 = 400,通道 = 3。我们将通道分成 3 个数组,然后将这些数组重新格式化为 1xn 数组(一长串像素值而不是像素网格)。

例如,红色通道现在看起来是这样的:

[130, 130, 130, ..., 145, 156, 159]包含 267*400 = 106,800 个值

现在我们定义一个距离函数,它将用于计算像素和质心之间的距离。在这个例子中,我们将使用欧几里得距离,如下公式所示:

distance = sqrt( (b0-a0)² + (b1-a1)² + (b2-a2)² ),其中ab是两个像素,而a0, a1, a2, b0, b1, b2是每个像素的颜色分量。

# euclidean distancedefdist(a,b):returnnp.sqrt((a[0]-b[0])**2+(a[1]-b[1])**2+(a[2]-b[2])**2)

接下来,我们将设置初始质心位置,并为此方法定义一些参数。

# number of clusters/centroidsk=4# random initial starting points within range 0, 255centroids=np.array([[random.randint(0,255),random.randint(0,255),random.randint(0,255)]foriinrange(k)])# number of pixels in the imagepixels=len(r)# exit conditions - stop repeating when max iterations have been reached# or the centroids stop movingmax_iter=8moved=True

现在我们可以构建算法的主体部分。

iter=0whilemovedanditer<=max_iter:iter+=1# cluster assignments, placeholder arrayassignment=[0]*pixels# for each pixelforiinrange(pixels):# compute distance between each pixel and each centroiddistances=[0]*kforjinrange(k):distances[j]=dist(centroids[j],[r[i],g[i],b[i]])# find minimum distance, returns index (0, .., k-1) of nearest centroidnearest=np.argmin(distances)# will look something like:# [0, 0, 1, 0, 1, 2, 2, 0, ...]# with a centroid value assigned to each pixelassignment[i]=nearest prev_centroids=centroids.copy()# for each cluster, calculate mean of allocated points for each dimensionforiinrange(k):# list of array indices of pixels that belong to each clusterind=[jforjinrange(pixels)ifassignment[j]==i]# check cluster assignment is not empty# prevents divide by zero error when calculating meaniflen(ind)!=0:centroids[i][0]=np.mean(r[ind])centroids[i][1]=np.mean(g[ind])centroids[i][2]=np.mean(b[ind])else:centroids[i][0]=0centroids[i][1]=0centroids[i][2]=0# check if centroids have movedifnp.array_equal(centroids,prev_centroids):moved=False

算法完成后,我们现在可以重建图像,将每个像素分配给其簇中心的颜色以产生所需的效果。

# make copy of colour channelsr_copy=np.array(r.copy())g_copy=np.array(g.copy())b_copy=np.array(b.copy())# update pixels to be the colour of their clusterforiinrange(k):ind=[jforjinrange(pixels)ifassignment[j]==i]r_copy[ind]=centroids[i][0]g_copy[ind]=centroids[i][1]b_copy[ind]=centroids[i][2]# compile channelsimg2=np.array([r_copy,g_copy,b_copy])# transpose to group values into pixelsimg2=img2.transpose()# reshape list of pixels into height x widgh x channelsimg2=img2.reshape(img.shape)plt.axis('off')plt.imshow(img2)plt.savefig("bird-small(k4).png",format="png",dpi=600)

测试

如我们所见,图像已被减少到只有 4 种不同的颜色。然而,正如预期的那样,这是一个非常低效的实现,运行时间大约为 30 秒,而且这只是一个低分辨率图像。

//k=4Initial Random Centroids:[[8211780][134182172][214107227][15545225]]Final Centroids:[[837962][13012798][178171127][333131]]

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/415cfddc200a05d4322f0c6668b0faf4.pnghttps://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/e0d9fe16e27df8cdbe38c80a34f89edf.png

左侧:聚类前,右侧:聚类后(k=4)


Python 算法 #2(使用 NumPy 优化)

我们进行与之前类似的设置,除了我们将图像保持为 3D 数组而不是将其拆分为通道。我们还重写了距离函数,使其能够处理值的向量而不是单个点。这允许更高效的执行,因为一些操作可以并行进行。

img=import_image("corfu.jpg")# split image into channels, reformat h x w x c structureimg=np.array(cv2.split(img))img=img.transpose(1,2,0)# exit conditionsmax_iter=10iter=0moved=True# number of clustersk=7# initial cluster centresclusters=[[random.randint(0,255)foriinrange(3)]forjinrange(k)]# define distance functiondefdist(a,b):return(np.sqrt(np.sum((b-a)**2,2)))

算法的主要部分遵循类似的架构,除了我们使用不同的编码方法来跟踪哪些像素对应于哪个簇,并使用 2D 掩码来索引图像,从而允许更快的访问。

iter=0whileiter<=max_iterandmoved==True:iter+=1# calculate distance between pixels and cluster, for every clusterdistances=[dist(img,clusters[i])foriinrange(k)]# index (0, ..., k) of the nearest cluster centre for each pixel# produces an array the same shape as the image, instead of pixels,# it stores in the index of the nearest cluster# this can be used as a mask later onnearest=np.argmin(distances,0)prev_clusters=clusters.copy()foriinrange(k):# create 1-hot encoded mask of which pixels belong to the clusterind=np.array(np.where(nearest==i,1,0),dtype=bool)# apply mask to image to extract subset of pixelssubset=img[ind]# calculate mean of the identified subset - update cluster centresclusters[i]=[np.round(np.mean(subset[:,0])),np.round(np.mean(subset[:,1])),np.round(np.mean(subset[:,2]))]# remove NaN values - replace with 0ifnp.isnan(clusters[i][0]):clusters[i][0]=0ifnp.isnan(clusters[i][1]):clusters[i][1]=0ifnp.isnan(clusters[i][2]):clusters[i][2]=0ifclusters==prev_clusters:moved=False

现在来显示图像。

# After the final iteration, the cluster centres represent the pixel colour# of each cluster. We apply the final version of the array, nearest, as a# mask to sample colours for each pixelclusters=np.array(clusters,dtype=int)img2=clusters[nearest]# display imageplt.axis('off')plt.imshow(img2)plt.savefig("corfu(k7).png",format="png",dpi=600)

测试

使用之前的图像测试这个版本,我们看到有显著的改进,总执行时间不到 1 秒。这现在使我们能够尝试更大的图像,并且具有更高的max_iter截止点(可能带来更准确的结果)。

下面的图像具有(2003,3456)的维度,给它提供了 6,922,368 个像素。这几乎是之前的 65 倍(106,800 个像素)。

使用这种新的实现,图像的处理时间大约为 40 秒(对于相同的k值)。因此,使用之前的实现处理这个相同的、新的图像,将需要超过 15 分钟。

(值得注意的是,由于代码的结构,性能会受到k选择的影响而略有影响)。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/e374354f5e7d7c0c3cbde0abbf7fb665.pnghttps://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/8db5ee30a37922d7892cd74cf37b3c71.png

左:原始图像,右:聚类图像(k=7)

进一步优化

肯定还有更多可以对此代码进行优化的地方。它可以进一步向量化以一次计算所有像素和所有质心的距离,但这会使代码变得有些混乱且难以阅读。

为了在性能上看到更大的提升,我们可以考虑重新编写这个算法,使其能够在 GPU 上运行。作为一个高度并行的算法,具有许多可以同时进行的操作,我们可能会看到速度的大幅提升。


在 Python 中实现 k 均值聚类是理解算法基本概念的好方法。通过探索另一种实现,我们突出了一些可以进行的优化,以加快性能,不仅在这个算法中,而且在许多类似的程序中也是如此。

— – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –

参考资料及阅读材料

  • 机器学习中的聚类

— – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –

除非另有说明,所有图像均为作者所有。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1098550.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JLink驱动安装日志分析方法:快速定位错误原因

JLink驱动装不上&#xff1f;别急着重插USB&#xff0c;先看日志&#xff01; 你有没有遇到过这样的场景&#xff1a; 新电脑刚装好Keil&#xff0c;信心满满地把J-Link往USB口一插——结果设备管理器里冒出个“未知设备”&#xff1b; 或者团队同事说“我这边没问题”&…

IAR安装教程(STM32):手把手带你完成环境搭建

手把手教你安装IAR并搭建STM32开发环境&#xff1a;从零开始&#xff0c;一次成功 你是不是也曾在搜索引擎里反复输入“ iar安装教程 stm32 ”&#xff0c;却总被一堆过时信息、断链下载和莫名其妙的授权错误搞得焦头烂额&#xff1f;别急——这篇文章就是为你写的。 作为一…

【2025最新】基于SpringBoot+Vue的乡村养老服务管理系统管理系统源码+MyBatis+MySQL

摘要 随着我国人口老龄化问题日益突出&#xff0c;乡村地区的养老服务需求快速增长&#xff0c;但传统养老服务模式存在信息化程度低、管理效率不足等问题。乡村养老服务管理系统旨在通过数字化手段优化资源配置&#xff0c;提升服务效率&#xff0c;满足老年人的多样化需求。该…

Miniconda-Python3.10镜像如何实现按需付费的Token模式

Miniconda-Python3.10镜像如何实现按需付费的Token模式 在AI训练任务日益频繁、科研协作愈发紧密的今天&#xff0c;一个常见的痛点反复浮现&#xff1a;为什么我在本地跑通的代码&#xff0c;到了同事或云端环境就“依赖报错”&#xff1f;更进一步的问题是——即使解决了环境…

STM32 USART波特率超详细版配置流程说明

搞定STM32串口通信&#xff0c;从波特率配置开始&#xff1a;不只是“设个数”&#xff0c;而是理解整个时钟链路你有没有遇到过这种情况&#xff1f;STM32程序烧进去后&#xff0c;串口助手打开却只看到一堆乱码——不是字符错位&#xff0c;就是满屏“烫烫烫”。第一反应是查…

cc2530串口通信项目应用:IAR平台操作指南

CC2530串口通信实战&#xff1a;从IAR工程搭建到UART调试全解析你有没有遇到过这样的情况&#xff1f;代码烧进去了&#xff0c;板子也上电了&#xff0c;但串口助手就是收不到一个字节的数据。LED不闪&#xff0c;波形没有&#xff0c;程序仿佛“静音”了一般——这是每一个嵌…

CCS20冗余架构设计原理:图解说明

深入理解CCS20冗余架构&#xff1a;从原理到实战的全链路解析在轨道交通信号系统、智能电网调度中心或核电站控制平台中&#xff0c;你是否曾思考过——当主控制器突然宕机时&#xff0c;系统如何做到“毫无察觉”地继续运行&#xff1f;这不是魔法&#xff0c;而是现代高可用控…

Jupyter内核配置错误修复:确保Miniconda-Python3.10正确注册Python环境

Jupyter内核配置错误修复&#xff1a;确保Miniconda-Python3.10正确注册Python环境 在数据科学和AI开发中&#xff0c;一个看似微小的环境配置问题&#xff0c;常常会拖慢整个项目进度。你是否曾遇到这样的情况&#xff1a;好不容易搭建好基于 Python 3.10 的 Miniconda 环境&a…

使用Miniconda为大模型推理服务配置负载均衡

使用Miniconda为大模型推理服务配置负载均衡 在构建高并发、多节点的大模型推理系统时&#xff0c;一个常被低估但至关重要的环节是——环境一致性。你有没有遇到过这样的场景&#xff1a;同一个模型&#xff0c;在开发机上运行正常&#xff0c;部署到生产集群后却频繁报错&…

Miniconda-Python3.10镜像在儿童教育大模型中的适配优化

Miniconda-Python3.10镜像在儿童教育大模型中的适配优化 在当今智能教育快速演进的背景下&#xff0c;越来越多的AI技术被引入课堂——从自动作文批改到语音纠音辅导&#xff0c;再到个性化学习路径推荐。这些功能背后往往依赖复杂的深度学习模型&#xff0c;而支撑它们稳定运…

Java Web 箱包存储系统系统源码-SpringBoot2+Vue3+MyBatis-Plus+MySQL8.0【含文档】

摘要 随着电子商务的快速发展&#xff0c;箱包行业对信息化管理的需求日益增长。传统的手工记录和简单的电子表格管理方式已无法满足企业对库存管理、订单处理和数据分析的精准化需求。箱包存储系统通过信息化手段实现商品分类、库存监控、订单管理和用户交互等功能&#xff0c…

Miniconda-Python3.10环境下安装DGL进行图神经网络研究

Miniconda-Python3.10环境下安装DGL进行图神经网络研究 在当今AI研究中&#xff0c;图神经网络&#xff08;GNN&#xff09;正迅速成为处理复杂关系数据的核心工具。从社交网络中的用户行为建模&#xff0c;到药物分子结构预测&#xff0c;再到知识图谱推理&#xff0c;越来越多…

从Anaconda迁移到Miniconda-Python3.10:节省70%磁盘空间的方法

从 Anaconda 迁移到 Miniconda-Python3.10&#xff1a;如何节省 70% 磁盘空间 在 GPU 云服务器上启动一个数据科学环境时&#xff0c;你是否曾因等待 Anaconda 加载而浪费了整整十分钟&#xff1f;或者在 CI/CD 流水线中&#xff0c;构建镜像的时间一半都花在了解压和安装冗余包…

LTspice仿真错误排查技巧:常见报错通俗解释

LTspice仿真卡住了&#xff1f;别慌&#xff0c;这些报错其实你都懂&#xff01;你有没有过这样的经历&#xff1a;花了一个小时搭好一个同步Buck电路&#xff0c;信心满满点下“Run”&#xff0c;结果几秒后弹出一行红字——“Time step too small”。再试几次&#xff0c;还是…

通过jflash实现安全启动配置:工业应用

用jFlash打造工业级安全启动&#xff1a;从烧录到信任链的实战指南你有没有遇到过这样的场景&#xff1f;产线上的设备莫名其妙运行异常&#xff0c;排查后发现固件被替换成“山寨版”&#xff1b;或者现场部署的控制器被人通过调试口读出了全部代码&#xff0c;核心算法一夜之…

PyTorch安装教程GPU版:Miniconda-Python3.10环境下一键部署深度学习模型

PyTorch GPU版环境部署实战&#xff1a;基于Miniconda-Python3.10的高效AI开发配置 在深度学习项目中&#xff0c;最让人头疼的往往不是模型设计本身&#xff0c;而是环境搭建——明明代码写好了&#xff0c;却因为CUDA版本不匹配、PyTorch无法识别GPU、包依赖冲突等问题卡住数…

使用Miniconda实现PyTorch模型的金丝雀发布

使用Miniconda实现PyTorch模型的金丝雀发布 在现代AI工程实践中&#xff0c;一个看似简单的“模型上线”背后&#xff0c;往往隐藏着复杂的环境依赖、版本冲突和部署风险。你有没有遇到过这样的场景&#xff1a;本地训练好的PyTorch模型&#xff0c;在生产服务器上却因为CUDA版…

Miniconda-Python3.10镜像在诗歌生成大模型中的创意应用

Miniconda-Python3.10镜像在诗歌生成大模型中的创意应用在人工智能不断渗透创作领域的今天&#xff0c;AI写诗早已不再是实验室里的奇技淫巧。从古风绝句到现代散文诗&#xff0c;大规模语言模型已经能够产出令人惊艳的文本作品。然而&#xff0c;真正让这些“数字诗人”稳定发…

Java SpringBoot+Vue3+MyBatis 项目申报管理系统系统源码|前后端分离+MySQL数据库

摘要 在信息化快速发展的时代背景下&#xff0c;项目申报管理系统的需求日益增长。传统的纸质申报方式效率低下&#xff0c;容易出现数据丢失或重复提交的问题&#xff0c;无法满足现代高效管理的需求。随着互联网技术的普及&#xff0c;越来越多的机构和企业开始采用数字化管理…

使用Miniconda-Python3.10镜像批量部署百台服务器AI环境

使用Miniconda-Python3.10镜像批量部署百台服务器AI环境 在现代AI工程实践中&#xff0c;一个看似不起眼却极其关键的环节正悄然决定着整个项目的成败——环境一致性。你是否经历过这样的场景&#xff1a;训练脚本在开发机上运行完美&#xff0c;但一提交到集群就报错&#xf…