Python - Найдите похожие цвета, лучший способ

Я сделал функцию, чтобы найти цвет внутри изображения и вернуть 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 немного сложнее, поскольку оба имеют цилиндрический оттенок, но некоторые части математики решают это, а затем используют квадратный эвкл. здесь.

В большинстве случаев я добавил "отдельные параметры" для допуска для каждого канала (используя 1 глобальный допуск и альтернативные "модификаторы" HueTol := Tolerance * hueMod или LightTol := Tolerance * LightMod).


Кажется, что цветовые пространства, построенные поверх XYZ (LAB, LCH), лучше всего работают во многих моих сценариях. Tho HSL дает очень хорошие результаты в некоторых случаях, и гораздо дешевле конвертировать в RGB, RGB также отлично подходит для большинства моих потребностей.

Ответ 1

Вычисление расстояний между цветами 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.

РЕДАКТИРОВАТЬ:

В качестве альтернативы, как предлагается в этом ответе, вы можете использовать HLS и HSV. Модуль colorsys похоже, имеет функции для преобразования из RGB. Его документация также ссылается на эти страницы, которые стоит прочитать, чтобы понять, почему евклидово расстояние RGB на самом деле не работает:

РЕДАКТИРОВАТЬ 2:

Согласно этому ответу, эта библиотека должна быть полезной: http://code.google.com/p/python-colormath/

Ответ 2

Ниже приведена оптимизированная версия Python, адаптированная из аксессуара Bruno:

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

Ответ 3

Предполагая, что 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

Ответ 4

Вместо этого:

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 - максимальная разница, которую вы готовы принять в любом из цветовых каналов.

Что он делает, это вычесть каждый канал из ваших целевых значений, принять абсолютные значения, а затем максимальные значения.

Ответ 5

Простой:

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

Ответ 6

from 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)

вам просто нужно немного исправить, и вы готовы к работе.

Ответ 7

Вот простая функция, которая не требует никаких библиотек:

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

Ответ 8

Здесь векторизованная версия Python (numpy) ответов Bruno и Developer (т. Е. Реализация полученного здесь приближения), которая принимает пару массивов numpy формы (x, 3) где отдельные строки находятся в порядке [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))

Он был оценен по сравнению с версией Developer для каждого элемента выше и дает те же результаты (за исключением ошибок с плавающей точностью в двух случаях из тысячи).