且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

如何使用 OpenCV 在 Python 中找到图像的平均颜色?

更新时间:2023-08-29 14:05:46

如何修复错误

发生此错误的潜在原因有两个:

  1. 文件名拼写错误.
  2. 图像文件不在当前工作目录中.

要解决此问题,您应该确保文件名拼写正确(以防万一,请进行区分大小写的检查)并且图像文件位于当前工作目录中(这里有两个选项:您可以更改当前工作目录在您的 IDE 中或指定文件的完整路径).

平均颜色与主色

然后计算平均颜色"你必须决定你的意思.在灰度图像中,它只是整个图像灰度的平均值.颜色通常通过 3 维向量表示,而灰度级是标量.

平均颜色是所有像素的总和除以像素数.然而,这种方法可能会产生与最突出的视觉颜色不同的颜色.您可能真正想要的是主色,而不是平均色.

实施

我们慢慢看代码.我们首先导入必要的模块并读取图像:

导入 cv2将 numpy 导入为 np从 skimage 导入 ioimg = io.imread('https://i.stack.imgur.com/DNM65.png')[:, :, :-1]

然后我们可以按照类似于@Ruan B 提出的方法计算每个色度通道的平均值:

average = img.mean(axis=0).mean(axis=0)

接下来我们应用

获得的平均颜色值,即颜色的分量是三个色度通道的均值,与通过 k-means 聚类计算的主色有很大不同:

在[30]中:平均值出[30]:数组([91.63179156, 69.30190754, 58.11971896])在 [31] 中:占主导地位出[31]:数组([179.3999,27.341282,2.294441],dtype=float32)

让我们看看这些颜色的外观,以更好地了解两种方法之间的差异.在下图的左侧部分显示了平均颜色.很明显,计算出的平均颜色没有正确描述原始图像的颜色内容.实际上,原始图像中没有具有该颜色的单个像素.该图的右侧部分显示了按重要性(出现频率)降序从上到下排序的五种最具代表性的颜色.这个调色板明显地表明主色是红色,这与原始图像中最大的均匀颜色区域对应于红色乐高积木的事实是一致的.

这是用于生成上图的代码:

将 matplotlib.pyplot 导入为 pltavg_patch = np.ones(shape=img.shape, dtype=np.uint8)*np.uint8(average)指数 = np.argsort(counts)[::-1]freqs = np.cumsum(np.hstack([[0], counts[indices]/float(counts.sum())]))行 = np.int_(img.shape[0]*freqs)dom_patch = np.zeros(shape=img.shape, dtype=np.uint8)对于范围内的 i(len(rows) - 1):dom_patch[rows[i]:rows[i + 1], :, :] += np.uint8(palette[indices[i]])图, (ax0, ax1) = plt.subplots(1, 2, figsize=(12,6))ax0.imshow(avg_patch)ax0.set_title('平均颜色')ax0.axis('关闭')ax1.imshow(dom_patch)ax1.set_title('主色')ax1.axis('关闭')plt.show(图)

TL;DR 答案

总而言之,尽管平均颜色的计算 - 正如@Ruan B. 的回答中所建议的 - 是正确的,但产生的结果可能无法充分代表图像的颜色内容.一种更明智的方法是通过矢量量化(聚类)来确定主色.

I tried this code:

import cv2
image = cv2.imread("sample.jpg")
pixel = image[200, 550]
print pixel

But I am getting error as:

'Nonetype' no attributes error getitem

This error is getting displayed after executing the third line of code.

How to fix the error

There are two potential causes for this error to happen:

  1. The file name is misspelled.
  2. The image file is not in the current working directory.

To fix this issue you should make sure the filename is correctly spelled (do case sensitive check just in case) and the image file is in the current working directory (there are two options here: you could either change the current working directory in your IDE or specify the full path of the file).

Average colour vs. dominant colour

Then to calculate the "average colour" you have to decide what you mean by that. In a grayscale image it is simply the mean of gray levels across the image. Colours are usually represented through 3-dimensional vectors whilst gray levels are scalars.

The average colour is the sum of all pixels divided by the number of pixels. However, this approach may yield a colour different to the most prominent visual color. What you might really want is dominant color rather than average colour.

Implementation

Let's go through the code slowly. We start by importing the necessary modules and reading the image:

import cv2
import numpy as np
from skimage import io

img = io.imread('https://i.stack.imgur.com/DNM65.png')[:, :, :-1]

Then we can calculate the mean of each chromatic channel following a method analog to the one proposed by @Ruan B.:

average = img.mean(axis=0).mean(axis=0)

Next we apply k-means clustering to create a palette with the most representative colours of the image (in this toy example n_colors was set to 5).

pixels = np.float32(img.reshape(-1, 3))

n_colors = 5
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 200, .1)
flags = cv2.KMEANS_RANDOM_CENTERS

_, labels, palette = cv2.kmeans(pixels, n_colors, None, criteria, 10, flags)
_, counts = np.unique(labels, return_counts=True)

And finally the dominant colour is the palette colour which occurs most frequently on the quantized image:

dominant = palette[np.argmax(counts)]

Comparison of results

To illustrate the differences between both approaches I've used the following sample image:

The obtained values for the average colour, i.e. a colour whose components are the means of the three chromatic channels, and the dominant colour calculated throug k-means clustering are rather different:

In [30]: average
Out[30]: array([91.63179156, 69.30190754, 58.11971896])

In [31]: dominant
Out[31]: array([179.3999  ,  27.341282,   2.294441], dtype=float32)

Let's see how those colours look to better understand the differences between both approaches. On the left part of the figure below it is displayed the average colour. It clearly emerges that the calculated average colour does not properly describe the colour content of the original image. In fact, there's no a single pixel with that colour in the original image. The right part of the figure shows the five most representative colours sorted from top to bottom in descending order of importance (occurrence frequency). This palette makes it evident that the dominant color is the red, which is consistent with the fact that the largest region of uniform colour in the original image corresponds to the red Lego piece.

This is the code used to generate the figure above:

import matplotlib.pyplot as plt

avg_patch = np.ones(shape=img.shape, dtype=np.uint8)*np.uint8(average)

indices = np.argsort(counts)[::-1]   
freqs = np.cumsum(np.hstack([[0], counts[indices]/float(counts.sum())]))
rows = np.int_(img.shape[0]*freqs)

dom_patch = np.zeros(shape=img.shape, dtype=np.uint8)
for i in range(len(rows) - 1):
    dom_patch[rows[i]:rows[i + 1], :, :] += np.uint8(palette[indices[i]])
    
fig, (ax0, ax1) = plt.subplots(1, 2, figsize=(12,6))
ax0.imshow(avg_patch)
ax0.set_title('Average color')
ax0.axis('off')
ax1.imshow(dom_patch)
ax1.set_title('Dominant colors')
ax1.axis('off')
plt.show(fig)

TL;DR answer

In summary, despite the calculation of the average colour - as proposed in @Ruan B.'s answer - is correct, the yielded result may not adequately represent the colour content of the image. A more sensible approach is that of determining the dominant colour through vector quantization (clustering).