点击上方"蓝色小字"关注我呀
本文翻译自光头哥哥的博客:
【Labeling superpixel colorfulness with OpenCV and Python】,仅做学习分享。
原文链接: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,然后插入以下代码:
# import the necessary packagesfrom skimage.exposure import rescale_intensityfrom skimage.segmentation import slicfrom skimage.util import img_as_floatfrom skimage import ioimport numpy as npimport argparseimport cv2
第1-8行处理我们的导入——正如你所看到的,我们在本教程中大量使用了一些scikit-image函数。slic函数将用于计算超像素
scikit-image文档:https://scikit-image.org/docs/dev/api/skimage.segmentation.html#skimage.segmentation.slic
接下来,我们将定义我们的色彩度量函数,在上一篇文章中做了一点小小的修改:
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行)给调用函数。
现在定义了关键的色彩度量函数,下一步是解析命令行参数:
# construct the argument parse and parse the argumentsap = 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超像素分割:
# load the image in OpenCV format so we can draw on it later, then# allocate memory for the superpixel colorfulness visualizationorig = cv2.imread(args["image"])vis = np.zeros(orig.shape[:2], dtype="float")# load the image and apply SLIC superpixel segmentation to it via# scikit-imageimage = 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的零参数版本,它是对原始算法的扩展,不需要我们手动调优算法的参数。在脚本的其余部分中,我们将超像素称为片段。
现在我们来计算每个超像素的色彩:
# loop over each of the unique superpixelsfor 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的问题——我们已经计算出了图像不同区域的色彩。
自然,我们想看到我们的结果,所以我们继续构建一个覆盖在原图上的可视化的最彩色/最不彩色的区域:
# 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 screenvis = rescale_intensity(vis, out_range=(0, 255)).astype("uint8")# overlay the superpixel colorfulness visualization on the original# imagealpha = 0.6overlay = 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行。现在我们已经把超像素的彩色可视化覆盖在原始图像之上。
最后,让我们在屏幕上显示图像并关闭此脚本:
# show the output imagescv2.imshow("Input", orig)cv2.imshow("Visualization", vis)cv2.imshow("Output", output)cv2.waitKey(0)
我们将使用cv2在屏幕上显示三个图像。imshow,包括:
定位:我们的输入图像。
vis:我们的可视化图像(即,每个超像素区域的色彩数值)。
输出:我们的输出图像。
让我们看看我们的Python脚本的运行效果,打开python工作终端,并输入以下命令:
$ python colorful_regions.py --image images/example_01.jpg
在左边你可以看到原始的输入图像,我在羚羊峡谷探险的照片,可以说是美国最美丽的狭槽峡谷。这里我们可以看到一个混合的颜色。在中间,我们计算了每100个超像素的可视化结果。在这张可视化图中,黑暗的区域指的是色彩较少的区域,而光明的区域表示的是色彩较多的区域。
在这里,我们可以看到最缺乏色彩的区域是峡谷壁上,离相机最近的地方——这是最缺乏光线的地方。输入图像中色彩最丰富的区域是光线直接进入峡谷内部的地方,像烛光一样照亮墙壁的一部分。最后,在右边,我们有我们的原始输入图像与色彩可视化覆盖-这一图像使我们更容易识别图像中色彩最丰富/最不丰富的区域。
下面这张照片是我在波士顿站在标志性的Citgo标志旁边俯瞰Kenmore广场的照片:
在这里,我们可以看到图像中最缺乏色彩的区域是在底部,阴影遮蔽了人行道的大部分。色彩更丰富的区域可以在标志和天空的方向找到。最后,这是一张来自彩虹点的照片,这里是布莱斯峡谷的最高点:
请注意,我的黑色连帽衫和短裤是图像中色彩最不丰富的区域,而天空和靠近照片中心的树叶是最丰富多彩的区域。
总结在今天的博客文章中,我们学习了如何使用SLIC分割算法来计算输入图像的超像素。然后我们访问每个单独的超像素并应用我们的色彩度量。每个区域的色彩分数被合并到一个掩膜中,显示出输入图像中色彩最丰富或最缺乏色彩的区域。