计数彩色色板 - 需要更快的方法

Counting coloured swatches - faster method required

我目前正在尝试找到一种计算彩色色板的方法。它们都是规则的,大小相同,呈棋盘状排列,颜色各不相同。样本的数量从几百到大约 90,000 不等。

在这个例子中,只有三种颜色,我需要它来计算浅绿色方块的数量 = 8

在 Photoshop 中,我可以对每个样本进行采样,其中 x、y 是样本的中心,colArr 是这些样本的所有 RGB 值的数组。

var pointSample = app.activeDocument.colorSamplers.add([x,y]);

// Obtain array of RGB values.
var rgb = [
  Math.round(pointSample.color.rgb.red,0),
  Math.round(pointSample.color.rgb.green,0),
  Math.round(pointSample.color.rgb.blue,0)
];

colArr.push(rgb);
delete_all_colour_samples();

function delete_all_colour_samples()
{
  app.activeDocument.colorSamplers.removeAll();
}

如果颜色与先前建立的数组中的颜色匹配,则会被计算在内。

有效!对于小样本来说,这是相当瞬时的。但是,对于超过 3000 个样本的图像,大约需要 25 秒。虽然我很高兴知道图像已经过处理,但我很好奇它是否可以更快地完成。

我的问题是:使用 say Python 可以加快速度吗?

我还不太清楚你所问的细节 - 比如每张图片中的正方形是否总是相同大小,你是否事先知道给定图像中有多少,它们是否总是正方形(您将它们描述为 "regular")以及您是否真的想在每个图像中计算不止一种颜色 - 您的示例是 "light green" 但你似乎有一个数组 "previously established colours" 所以不清楚你是否想知道该数组中每种颜色有多少像素。

无论如何,让我们先尝试一下答案,使用 OpenCV 和基本的 Numpy 索引来获取中心。

import cv2
import numpy as np

# Load an image
im = cv2.imread('Qcza1.png')

# Assume the swatches are 64x64 pixels
sw, sh = 64, 64

# Get colours at each swatch centre, so we start 1/2 a swatch in and step by a whole swatch
centres = im[sh//2::sh, sw//2::sw]

这给了我们这个大小为 [4,6,3] 的数组,因为 Numpy 数组是 [height, width, channels] 并且我们有 4 个垂直样本中心,6 个水平样本中心,每个样本有 3 种颜色。

array([[[141, 244, 159],
    [ 85, 202, 105],
    [255, 255, 255],
    [ 85, 202, 105],
    [141, 244, 159],
    [ 85, 202, 105]],

   [[255, 255, 255],
    [141, 244, 159],
    [ 85, 202, 105],
    [141, 244, 159],
    [ 85, 202, 105],
    [141, 244, 159]],

   [[141, 244, 159],
    [ 85, 202, 105],
    [255, 255, 255],
    [ 85, 202, 105],
    [141, 244, 159],
    [255, 255, 255]],

   [[ 85, 202, 105],
    [141, 244, 159],
    [ 85, 202, 105],
    [255, 255, 255],
    [ 85, 202, 105],
    [255, 255, 255]]], dtype=uint8)

如果您不熟悉 Numpy 索引,它基本上是:

array[start:end:stride]

所以在上面的代码中,我开始将样本宽度的一半放入数组(以到达第一个样本的中心),然后使用等于样本宽度的步幅到达下一个中心。

如果你现在想要计算具有颜色 [141,244,159] 的样本中心的数量,你可以这样做:

tally = np.sum(np.all(centres==[141,244,159], axis=-1))

得到结果8.


请注意,您可以使用 Python 成像库 PIL/Pillow 来完成所有这些操作,如果这对您来说更容易安装,则替换为:

import cv2
im = cv2.imread(...)

与:

from PIL import Image

# Open PIL Image and make into Numpy array
im = np.array(Image.open(...).convert('RGB'))

请记住,OpenCV 使用 BGR 排序,而 PIL 使用 RGB,因此您正在寻找的颜色三元组将被反转.


我使用 1,000 种独特的颜色生成了 90,000 个 10x10 像素色板的样本图像,如下所示:

import cv2
import numpy as np

# Define 10 possible values for R, G and B
vals = np.arange(0,255,26)

# Make 90,000 pixel image using approx 10*10*10 = 1000 colours
h, w = 200, 450
np.random.seed(42)
im = np.random.choice(vals, (h,w,3)).astype(np.uint8)

# Scale up to make swatches 10x10
sw, sh = 10, 10
im = cv2.resize(im, (w*sw, h*sh), 0, 0, interpolation=cv2.INTER_NEAREST)

然后我对它计时并得到 1.9 毫秒,它找到了 83 个颜色为 [0, 78, 156] 的色板,这就是你所期望的 90,000 个色板,1,000 种颜色:

%%timeit
centres = im[sh//2::sh, sw//2::sw]
np.sum(np.all(centres==[0,78,156], axis=-1))

1.95 ms ± 30.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

计算具有给定颜色的连续区域的数量:

  • 首先找出哪些像素具有该颜色,
  • 然后使用连通分量分析来计算连续区域的数量。

大多数图像处理库都具有轻松执行此操作所需的工具。例如使用 DIPlib(披露:我是作者)它将是:

import diplib as dip

img = dip.ImageRead('Qcza1.png')

color = dip.Create0D([159,244,141])
squares = dip.AllTensorElements(img == color)
squares = dip.Label(squares, connectivity=1)
count = dip.Maximum(squares)[0][0]

对于 OpenCV 它将是:

import cv2

img = cv2.imread('Qcza1.png')  # Uses BGR ordering!

color = (141,244,159)
squares = cv2.inRange(img, color, color)
count, squares = cv2.connectedComponents(squares, connectivity=4)
count = count - 1  # count included background label

这里有两件事可能会出错:

  1. 颜色不准确(光照变化、压缩伪影等)。在这种情况下,您需要使用一系列颜色(dip.InRange(img, color1, color2) 而不是 img == color,或 cv2.inRange(img, color1, color2))。

  2. 正方形并没有像示例中那样完全分开。如果连接仅发生在顶点,则在标记之前对二进制 squares 图像进行小幅腐蚀应该可以解决问题。如果方块可以并排放置,则需要一种更复杂的方法:我们会查看大小的直方图,期望在单个方块的大小处看到第一个峰值,然后在该大小的倍数处看到进一步的峰值。从这个直方图中,我们可以计算出有多少个正方形,方法是将接近 2x 大小的 bin 乘以 2,接近 3x 大小的 bin 乘以 3,等等,然后对所有值求和。