前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用OpenCV和Python标记超像素色彩

使用OpenCV和Python标记超像素色彩

作者头像
周旋
发布2020-09-29 10:50:29
1.6K0
发布2020-09-29 10:50:29
举报
文章被收录于专栏:行走的机械人行走的机械人

本文翻译自光头哥哥的博客:

【Labeling superpixel colorfulness with OpenCV and Python】,仅做学习分享。

原文链接:

代码语言:javascript
复制
https://www.pyimagesearch.com/2017/06/26/labeling-superpixel-colorfulness-opencv-python/

在我们上一篇关于计算图像色彩的文章发表之后,PyImageSearch的读者Stephan在教程中留言,询问是否有一种方法可以计算图像特定区域(而不是整个图像)的色彩。

有多种方法可以解决这个问题。第一种方法是应用一个滑动窗口来循环图像,并计算每个ROI的色彩分数。如果需要在多个尺度上计算特定区域的色彩,甚至可以应用图像金字塔。 然而,更好的方法是使用超像素。超像素是通过一种分割算法来提取的,该算法根据像素的局部颜色/纹理将其分组为非矩形区域。在流行的SLIC超像素算法中,基于k均值的局部版本对图像区域进行分组。

考虑到超像素会比滑动窗口更自然地分割输入图像,我们可以通过以下方法来计算图像中特定区域的色彩:

  • 对输入图像进行超像素分割。
  • 循环每个超像素,并计算其各自的彩色数值。
  • 更新一个包含每个超像素的色彩数值的掩膜。

基于这个,我们可以看到图像中色彩最丰富的区域。图像中色彩较丰富的区域会有较大的彩色度量分数,而色彩较不丰富的区域会有较小的数值。

使用OpenCV和Python标记超像素色彩

在接下来的部分中,我们将学习如何应用SLIC算法从输入图像中提取超像素。Achanta等人在2010年发表的SLIC Superpixels的原稿详细介绍了这种方法和技术。 给定这些超像素,我们将逐个循环它们并计算它们的色彩得分,注意计算特定区域而不是整个图像的色彩度量。

在实现脚本之后,我们将对一组输入图像应用超像素+图像色彩的组合。

使用超像素进行分割

让我们在你最喜欢的编辑器或IDE中打开一个新文件,命名为colorful_regions.py,然后插入以下代码:

代码语言:javascript
复制
# import the necessary packages
from skimage.exposure import rescale_intensity
from skimage.segmentation import slic
from skimage.util import img_as_float
from skimage import io
import numpy as np
import argparse
import cv2

第1-8行处理我们的导入——正如你所看到的,我们在本教程中大量使用了一些scikit-image函数。 slic函数将用于计算超像素

代码语言:javascript
复制
scikit-image文档:https://scikit-image.org/docs/dev/api/skimage.segmentation.html#skimage.segmentation.slic

接下来,我们将定义我们的色彩度量函数,在上一篇文章中做了一点小小的修改:

代码语言:javascript
复制
def segment_colorfulness(image, mask):
  # split the image into its respective RGB components, then mask
  # each of the individual RGB channels so we can compute
  # statistics only for the masked region
  (B, G, R) = cv2.split(image.astype("float"))
  R = np.ma.masked_array(R, mask=mask)
  G = np.ma.masked_array(B, mask=mask)
  B = np.ma.masked_array(B, mask=mask)
  # compute rg = R - G
  rg = np.absolute(R - G)
  # compute yb = 0.5 * (R + G) - B
  yb = np.absolute(0.5 * (R + G) - B)
  # compute the mean and standard deviation of both `rg` and `yb`,
  # then combine them
  stdRoot = np.sqrt((rg.std() ** 2) + (yb.std() ** 2))
  meanRoot = np.sqrt((rg.mean() ** 2) + (yb.mean() ** 2))
  # derive the "colorfulness" metric and return it
  return stdRoot + (0.3 * meanRoot)

第1-18行表示我们的色彩度度量函数,它已被修改为用于计算图像特定区域的色彩度。 区域可以是任何形状,因为我们利用NumPy掩膜阵列,只有像素部分掩膜将包括在计算中。 对于特定图像的指定掩模区域,segment_colorfulness函数执行以下任务:

  • 将图像分割为RGB组件通道(第5行)。
  • 使用mask(每个通道)对图像进行蒙版,这样色彩度量只在指定的区域执行——在这种情况下,该区域将是我们的超像素(第6-8行)。
  • 使用R和G组件计算rg(第10行)。
  • 使用RGB组件计算yb(第12行)。
  • 计算rg和yb的均值和标准偏差,同时合并他们(第15和16行)。
  • 执行度量的最终计算,并将其返回(第19行)给调用函数。

现在定义了关键的色彩度量函数,下一步是解析命令行参数:

代码语言:javascript
复制
# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True,
  help="path to input image")
ap.add_argument("-s", "--segments", type=int, default=100,
  help="# of superpixels")
args = vars(ap.parse_args())

在第2-7行,我们使用argparse定义两个参数:

  • ——image:输入图像的路径。
  • ——segments:超像素的数量。SLIC的超像素展示了将图像分解成不同数量的超像素的例子。这个参数很有趣(因为它控制你的超像素的粒度级别)。但是,我们将使用默认值100。值越小,超像素越少,超像素越大,从而使算法运行得更快。细分的数量越大,区域的粒度就越细,SLIC将花费更长的时间运行(因为需要计算更多的集群)。

现在是时候将图像加载到内存中,为我们的可视化分配空间,并计算SLIC超像素分割:

代码语言:javascript
复制
# load the image in OpenCV format so we can draw on it later, then
# allocate memory for the superpixel colorfulness visualization
orig = cv2.imread(args["image"])
vis = np.zeros(orig.shape[:2], dtype="float")
# load the image and apply SLIC superpixel segmentation to it via
# scikit-image
image = io.imread(args["image"])
segments = slic(img_as_float(image), n_segments=args["segments"],
  slic_zero=True)

在第3行,我们将命令行参数image作为原图 (OpenCV格式)加载到内存中。 然后,我们为可视化图像vis分配与原始输入图像相同形状(宽度和高度)的内存。 接下来,我们将命令行参数image作为图像加载到内存中,这次使用的是scikit-image格式。我们使用scikitimage的格式的原因是因为OpenCV以BGR格式加载图像,而不是RGB格式(scikit-image是这样的)。slic函数将在超像素生成期间将我们的输入图像转换为L*a*b*颜色空间。

因此我们有两种选择:

  • 用OpenCV加载图像,克隆它,然后交换通道的顺序。
  • 只需使用scikit-image加载原始图像的副本。

任何一种方法都是有效的,并将产生相同的输出。 超像素是通过调用slic函数来计算的,其中我们指定image、n_segments和slic_zero参数。指定slic_zero=True表示我们希望使用SLIC的零参数版本,它是对原始算法的扩展,不需要我们手动调优算法的参数。在脚本的其余部分中,我们将超像素称为片段。

现在我们来计算每个超像素的色彩:

代码语言:javascript
复制
# loop over each of the unique superpixels
for v in np.unique(segments):
  # construct a mask for the segment so we can compute image
  # statistics for *only* the masked region
  mask = np.ones(image.shape[:2])
  mask[segments == v] = 0
  # compute the superpixel colorfulness, then update the
  # visualization array
  C = segment_colorfulness(orig, mask)
  vis[segments == v] = C

我们首先循环遍历2行上的每个片段。

第5和6行负责为当前的超像素构建掩码。蒙版将与我们的输入图像具有相同的宽度和高度,并将填充(最初)一组1(第5行)。

请记住,在使用NumPy掩码数组时,只有在相应掩码值被设置为零(意味着像素被解除掩码)的情况下,数组中的给定条目才会包含在计算中。如果掩码中的值为1,则假定该值被掩码,因此被忽略。

在这里,我们最初设置所有像素为掩膜,然后只设置当前超像素的像素部分为掩膜(第6行)。

使用我们的原图像和蒙版作为segment_colorfulness的参数,我们可以计算C,这是超像素的色彩数值(第9行)。

然后,我们用C的值更新可视化数组vis(第10行)。

现在,我们已经回答了PyImageSearch读者Stephan的问题——我们已经计算出了图像不同区域的色彩。

自然,我们想看到我们的结果,所以我们继续构建一个覆盖在原图上的可视化的最彩色/最不彩色的区域:

代码语言:javascript
复制
# scale the visualization image from an unrestricted floating point
# to unsigned 8-bit integer array so we can use it with OpenCV and
# display it to our screen
vis = rescale_intensity(vis, out_range=(0, 255)).astype("uint8")
# overlay the superpixel colorfulness visualization on the original
# image
alpha = 0.6
overlay = np.dstack([vis] * 3)
output = orig.copy()
cv2.addWeighted(overlay, alpha, output, 1 - alpha, 0, output)

由于vis目前是一个浮点数组,有必要将其重新缩放为一个典型的8位无符号整数[0-255]数组。这一点很重要,这样我们就可以用OpenCV将输出图像显示到屏幕上。我们通过使用rescale_intensity函数(来自skimage)来实现这一点。在第4行。 现在我们已经把超像素的彩色可视化覆盖在原始图像之上。

最后,让我们在屏幕上显示图像并关闭此脚本:

代码语言:javascript
复制
# show the output images
cv2.imshow("Input", orig)
cv2.imshow("Visualization", vis)
cv2.imshow("Output", output)
cv2.waitKey(0)

我们将使用cv2在屏幕上显示三个图像。imshow,包括:

  • 定位:我们的输入图像。
  • vis:我们的可视化图像(即,每个超像素区域的色彩数值)。
  • 输出:我们的输出图像。

超像素和彩色度量结果

让我们看看我们的Python脚本的运行效果,打开python工作终端,并输入以下命令:

代码语言:javascript
复制
$ python colorful_regions.py --image images/example_01.jpg

在左边你可以看到原始的输入图像,我在羚羊峡谷探险的照片,可以说是美国最美丽的狭槽峡谷。这里我们可以看到一个混合的颜色。 在中间,我们计算了每100个超像素的可视化结果。在这张可视化图中,黑暗的区域指的是色彩较少的区域,而光明的区域表示的是色彩较多的区域。

在这里,我们可以看到最缺乏色彩的区域是峡谷壁上,离相机最近的地方——这是最缺乏光线的地方。 输入图像中色彩最丰富的区域是光线直接进入峡谷内部的地方,像烛光一样照亮墙壁的一部分。 最后,在右边,我们有我们的原始输入图像与色彩可视化覆盖-这一图像使我们更容易识别图像中色彩最丰富/最不丰富的区域。

下面这张照片是我在波士顿站在标志性的Citgo标志旁边俯瞰Kenmore广场的照片:

在这里,我们可以看到图像中最缺乏色彩的区域是在底部,阴影遮蔽了人行道的大部分。色彩更丰富的区域可以在标志和天空的方向找到。 最后,这是一张来自彩虹点的照片,这里是布莱斯峡谷的最高点:

请注意,我的黑色连帽衫和短裤是图像中色彩最不丰富的区域,而天空和靠近照片中心的树叶是最丰富多彩的区域。

总结

在今天的博客文章中,我们学习了如何使用SLIC分割算法来计算输入图像的超像素。 然后我们访问每个单独的超像素并应用我们的色彩度量。 每个区域的色彩分数被合并到一个掩膜中,显示出输入图像中色彩最丰富或最缺乏色彩的区域。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-09-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Opencv视觉实践 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档