我制作了一个函数来查找图像中的颜色,并返回 x, y。现在我需要添加一个新函数,在其中我可以找到具有给定容差的颜色。应该很容易吧?
查找图像颜色的代码,并返回 x, y:
def FindColorIn(r,g,b, xmin, xmax, ymin, ymax):
image = ImageGrab.grab()
for x in range(xmin, xmax):
for y in range(ymin,ymax):
px = image.getpixel((x, y))
if px[0] == r and px[1] == g and px[2] == b:
return x, y
def FindColor(r,g,b):
image = ImageGrab.grab()
size = image.size
pos = FindColorIn(r,g,b, 1, size[0], 1, size[1])
return pos
结果:
从答案中得出,比较两种颜色的常规方法是欧几里德距离或切比雪夫距离。
我决定主要使用(平方)欧几里得距离和多个不同的颜色空间。 LAB、deltaE (LCH)、XYZ、HSL 和 RGB。在我的代码中,大多数颜色空间使用平方欧氏距离来计算差异。
例如,LAB、RGB 和 XYZ 是一个简单的平方 euc。距离可以解决问题:
if ((X-X1)^2 + (Y-Y1)^2 + (Z-Z1)^2) <= (Tol^2) then
...
LCH 和 HSL 稍微复杂一些,因为两者都有圆柱形色调,但一些数学解决了这个问题,然后就可以使用平方 eucl 了。这里也有。
在大多数情况下,我为每个通道的容差添加了“单独的参数”(使用 1 个全局容差和替代“修饰符”
HueTol := Tolerance * hueMod
或 LightTol := Tolerance * LightMod
)。
看起来建立在 XYZ(LAB、LCH)之上的色彩空间在我的许多场景中确实表现最佳。虽然 HSL 在某些情况下会产生非常好的结果,而且从 RGB 转换成的价格要便宜得多,但 RGB 也很棒,可以满足我的大部分需求。
以对眼睛有意义的方式计算 RGB 颜色之间的距离并不像仅计算两个 RGB 向量之间的欧几里德距离那么容易。
这里有一篇关于此的有趣文章:http://www.compuphase.com/cmetric.htm
C 语言的示例实现是这样的:
typedef struct {
unsigned char r, g, b;
} RGB;
double ColourDistance(RGB e1, RGB e2)
{
long rmean = ( (long)e1.r + (long)e2.r ) / 2;
long r = (long)e1.r - (long)e2.r;
long g = (long)e1.g - (long)e2.g;
long b = (long)e1.b - (long)e2.b;
return sqrt((((512+rmean)*r*r)>>8) + 4*g*g + (((767-rmean)*b*b)>>8));
}
移植到Python应该不会太困难。
编辑:
colorsys
模块似乎具有从RGB进行转换的功能。它的文档还链接到这些页面,这些页面值得阅读以了解为什么 RGB 欧几里德距离实际上不起作用:
编辑2:
根据这个答案,这个库应该很有用:http://code.google.com/p/python-colormath/
这是一个优化的
Python
版本,改编自Bruno的asnwer:
def ColorDistance(rgb1,rgb2):
'''d = {} distance between two colors(3)'''
rm = 0.5*(rgb1[0]+rgb2[0])
d = sum((2+rm,4,3-rm)*(rgb1-rgb2)**2)**0.5
return d
用途:
>>> import numpy
>>> rgb1 = numpy.array([1,1,0])
>>> rgb2 = numpy.array([0,0,0])
>>> ColorDistance(rgb1,rgb2)
2.5495097567963922
而不是这个:
if px[0] == r and px[1] == g and px[2] == b:
试试这个:
if max(map(lambda a,b: abs(a-b), px, (r,g,b))) < tolerance:
其中
tolerance
是您在任何颜色通道中愿意接受的最大差异。
它的作用是从目标值中减去每个通道,取绝对值,然后取其中的最大值。
假设 rtol、gtol 和 btol 分别是 r、g 和 b 的公差,为什么不这样做:
if abs(px[0]- r) <= rtol and \
abs(px[1]- g) <= gtol and \
abs(px[2]- b) <= btol:
return x, y
来自 pyautogui 源代码
def pixelMatchesColor(x, y, expectedRGBColor, tolerance=0):
r, g, b = screenshot().getpixel((x, y))
exR, exG, exB = expectedRGBColor
return (abs(r - exR) <= tolerance) and (abs(g - exG) <= tolerance) and (abs(b - exB) <= tolerance)
您只需要稍作修改即可开始。
这是 Bruno 和开发人员答案的矢量化 Python (numpy) 版本(即,这里导出的近似值的实现),它接受一对形状为
(x, 3)
的 numpy 数组,其中各个行按 [R, G, B]
顺序排列,并且各个颜色值 ε[0, 1].
您可以将其减少为两行,但会牺牲可读性。我不完全确定它是否是最优化的版本,但它应该足够好。
def colour_dist(fst, snd):
rm = 0.5 * (fst[:, 0] + snd[:, 0])
drgb = (fst - snd) ** 2
t = np.array([2 + rm, 4 + 0 * rm, 3 - rm]).T
return np.sqrt(np.sum(t * drgb, 1))
它根据上面开发人员的每个元素版本进行评估,并产生相同的结果(除了千分之二的情况下的浮动精度错误)。
此处所述函数的更清晰的Python实现,该函数采用2个图像路径,使用cv.imread读取它们,并输出一个矩阵,每个矩阵单元具有不同的颜色。您可以将其更改为轻松匹配 2 种颜色
import numpy as np
import cv2 as cv
def col_diff(img1, img2):
img_bgr1 = cv.imread(img1) # since opencv reads as B, G, R
img_bgr2 = cv.imread(img2)
r_m = 0.5 * (img_bgr1[:, :, 2] + img_bgr2[:, :, 2])
delta_rgb = np.square(img_bgr1- img_bgr2)
cols_diffs = delta_rgb[:, :, 2] * (2 + r_m / 256) + delta_rgb[:, :, 1] * (4) +
delta_rgb[:, :, 0] * (2 + (255 - r_m) / 256)
cols_diffs = np.sqrt(cols_diffs)
# lets normalized the values to range [0 , 1]
cols_diffs_min = np.min(cols_diffs)
cols_diffs_max = np.max(cols_diffs)
cols_diffs_normalized = (cols_diffs - cols_diffs_min) / (cols_diffs_max - cols_diffs_min)
return np.sqrt(cols_diffs_normalized)
def eq_with_tolerance(a, b, t):
return a-t <= b <= a+t
def FindColorIn(r,g,b, xmin, xmax, ymin, ymax, tolerance=0):
image = ImageGrab.grab()
for x in range(xmin, xmax):
for y in range(ymin,ymax):
px = image.getpixel((x, y))
if eq_with_tolerance(r, px[0], tolerance) and eq_with_tolerance(g, px[1], tolerance) and eq_with_tolerance(b, px[2], tolerance):
return x, y
如您所见,欧几里德距离并没有从感知颜色距离中得出太多。 以下是在 Python 中获取值的代码:
def get_perceived_color_distance_unnormalized(color1, color2):
r1, g1, b1 = color1
r2, g2, b2 = color2
r = r1 - r2
g = g1 - g2
b = b1 - b2
r_mean = (r1 + r2) / 2
return math.sqrt(
(512 + r_mean) / 256 * r ** 2 # >> 8 is same as dividing by 256
+ 4 * g ** 2
+ (767 - r_mean) / 256 * b ** 2)
def get_euclidean_color_distance_unnormalized(color1, color2):
r1, g1, b1 = color1
r2, g2, b2 = color2
return math.sqrt(
(r1 - r2) ** 2
+ (g1 - g2) ** 2
+ (b1 - b2) ** 2)
Black = (0, 0, 0)
White = (255, 255, 255)
maximum_perceived_distance = get_perceived_color_distance_unnormalized(White, Black)
maximum_euclidean_distance = get_euclidean_color_distance_unnormalized(White, Black)
def get_perceived_color_distance(color1, color2):
""" Calculates a normalized distance (from 0 to 1) between two colors meaningful to the eyes (https://www.compuphase.com/cmetric.htm). """
return get_perceived_color_distance_unnormalized(color1, color2) / maximum_perceived_distance
def get_euclidean_color_distance(color1, color2):
""" Calculates a normalized Euclidean distance (from 0 to 1) between two colors. """
return get_euclidean_color_distance_unnormalized(color1, color2) / maximum_euclidean_distance
def color_distance(rgb1, rgb2):
rm = 0.5 * (rgb1[0] + rgb2[0])
rd = ((2 + rm) * (rgb1[0] - rgb2[0])) ** 2
gd = (4 * (rgb1[1] - rgb2[1])) ** 2
bd = ((3 - rm) * (rgb1[2] - rgb2[2])) ** 2
return (rd + gd + bd) ** 0.5
假设 rgb1 和 rgb2 是 RBG 元组