Python - поиск наиболее часто используемого цвета на изображении - кажется, скрипт находит неточные цвета - PullRequest
0 голосов
/ 20 марта 2020

Я написал скрипт, который находит 3 самых популярных цвета, используемых в jpg файле. Это работает по большей части, но я заметил, что у меня есть некоторые выбросы.

Это изображение, о котором идет речь: https://public.tableau.com/s/sites/default/files/media/votd_02_08_13_snip.png

Я скачал и сохранил это как jpg.

Мой сценарий обнаруживает следующие полностью отключенные цвета:

  1. 255, 176, 213
  2. 197, 255, 255
  3. 174, 187, 120

Мой скрипт:

from PIL import Image
from collections import Counter

def most_frequent_colour2(img):
    width, height = img.size

    r_total = []
    g_total = []
    b_total = []

    for x in range(0, width):
        for y in range(0, height):

            # Define r, g, b for the rgb colour space and fetch the RGB colour for each pixel
            r, g, b = img.getpixel((x,y))

            r_total.append(r)
            g_total.append(g)
            b_total.append(b)       

    # Count the most common colours
    r_dict, g_dict, b_dict = Counter(r_total), Counter(g_total), Counter(b_total)

    #pick the top 3 most frequent colours
    return r_dict.most_common(3), g_dict.most_common(3), b_dict.most_common(3)

file = r"Filepath\https://public.tableau.com/s/sites/default/files/media/votd_02_08_13_snip.jpg"
img = Image.open(file)
img = img.convert('RGB')
colour = most_frequent_colour2(img)

# colour has the following format: 
#([(Most frequent R value, number of occurences), (2nd most frequent R value, number of occurences)],
# [(Most frequent G value, number of occurences), (2nd most frequent G value, number of occurences)]
# [(Most frequent B value, number of occurences), (2nd most frequent B value, number of occurences)])
print(colour)

Скрипт действительно хорошо работает для этого изображения: https://public.tableau.com/s/sites/default/files/media/tourdefrance1.jpg, где он возвращается 85, 85, 85 как доминирующий цвет.

Может ли проблема быть преобразованием из png в jpg?

Ответы [ 4 ]

3 голосов
/ 20 марта 2020

Я думаю, что проблема скорее со структурой вашей программы. Вы должны добавлять значения RGB вместе, а не по отдельности. Наиболее распространенные значения, которые вы производите, относятся к каждой цветовой группе, а не к самим цветам.

def most_frequent_colour2(img):
width, height = img.size

rgb_total = []

for x in range(0, width):
    for y in range(0, height):

        # Define r, g, b for the rgb colour space and fetch the RGB colour for each pixel
        r, g, b = img.getpixel((x,y))

        rgb_total.append("r:"+str(r)+"g:"+str(g)+"b:"+str(b))     

# Count the most common colours
rgb_dict = Counter(rgb_total)

#pick the top 3 most frequent colours
return rgb_dict.most_common(3)
#[('r:197g:176b:213', 7488), ('r:255g:255b:255', 4320), ('r:255g:187b:120', 3488)]
2 голосов
/ 20 марта 2020

Кстати, PIL имеет метод подсчета уникальных цветов на изображении.

from PIL import Image

image = Image.open("votd_02_08_13_snip.png")

most_common = sorted(image.getcolors(maxcolors=2**16), key=lambda t: t[0], reverse=True)[0:3]
print(most_common)

Вывод:

[(7488, (197, 176, 213, 255)), (4320, (255, 255, 255, 255)), (3488, (255, 187, 120, 255))]

PIL.Image.getcolors возвращает список кортежей, где первый элемент - это счетчик, а второй - кортеж, содержащий информацию о полосе / цвете канала (в данном случае RGBA). Список не отсортирован, поэтому мы сортируем его по первому элементу (количеству) каждого кортежа. Сортировка выполняется в порядке убывания, поэтому элементы с наибольшим количеством находятся на первом месте. Я произвольно нарезал результирующий список, чтобы получить три наиболее распространенных цвета.

РЕДАКТИРОВАТЬ - забыл упомянуть, PIL.Image.getcolors принимает необязательный аргумент ключевого слова maxcolors, который по умолчанию равен 256. Это значение для максимального количества уникальных цветов для рассмотрения. Если ваше изображение содержит больше уникальных цветов, чем это значение, PIL.Image.getcolors вернет None. Поскольку это конкретное изображение имеет глубину 32, мой первый импульс должен был передать 2**32 как maxcolors, но это привело к OverflowError в моей системе. Среднее изображение не будет содержать сколько-нибудь цветов, которые его битовая глубина может представлять в любом случае, поэтому я просто вдвое уменьшил это число (опять же, произвольно).

Время не должно вызывать беспокойства, если только у вас нет Я думаю, огромное изображение:

from timeit import Timer

setup_method_1 = """
from PIL import Image
image = Image.open("votd_02_08_13_snip.png")
def get_most_common(image):
    most_common = sorted(image.getcolors(maxcolors=2**16), key=lambda t: t[0], reverse=True)[0:3]
    return most_common
"""

setup_method_2 = """
from PIL import Image
from collections import Counter
image = Image.open("votd_02_08_13_snip.png")
def get_most_common(image):
    width, height = image.size

    rgb_total = []

    for x in range(0, width):
        for y in range(0, height):

            r, g, b, _ = image.getpixel((x, y))
            rgb_total.append(f"r:{r}g:{g}b:{b}")
    rgb_dict = Counter(rgb_total)
    return rgb_dict.most_common(3)
"""

time_method_1 = min(Timer("get_most_common(image)", setup=setup_method_1).repeat(3, 10))
time_method_2 = min(Timer("get_most_common(image)", setup=setup_method_2).repeat(3, 10))

print(time_method_1)
print(time_method_2)

Вывод:

0.016010588999999964
0.6741786089999999
>>> 

Альтернативный метод, который я только что придумал, используя collections.Counter:

from timeit import Timer

setup = """

from PIL import Image
from collections import Counter

image = Image.open("votd_02_08_13_snip.png")

def get_most_common(image):
    return Counter(image.getdata()).most_common(3)

"""

time = min(Timer("get_most_common(image)", setup=setup).repeat(3, 10))
print(time)

Вывод:

0.05364039000000004
1 голос
/ 20 марта 2020

Вот пример программы с массивом пикселей, которую я обещал. Он основан на популярном пакете Pygame. Я работаю с операционной системой Windows, поэтому, если вы работаете с какой-либо другой ОС, вам нужно будет прокомментировать или удалить команды Window-Speci c. Пиксельные массивы являются родными для Pygame, поэтому эта программа должна работать на других системах, совместимых с pygame, после отключения windows. Windows может связываться с ничего не подозревающим кодером, если только не будут устранены причуды, так что простите мне эти строки кода Windows, если вы используете хорошо функционирующую ОС. Я пытался удержать материал Windows в верхней части программы.

from os import system
import ctypes
from time import sleep
import pygame
from pygame import Color
pygame.init()

system('cls') # Windows-specific command to clear the Command Console Window.

# Windows can play weird tricks related to scaling when you try to position or show images with pygame.
# The following will disable the Windows Auto-scaling feature.
# Query DPI Awareness (Windows 10 and 8)
awareness = ctypes.c_int()
errorCode = ctypes.windll.shcore.GetProcessDpiAwareness(0, ctypes.byref(awareness))
# Set DPI Awareness  (Windows 10 and 8)
errorCode = ctypes.windll.shcore.SetProcessDpiAwareness(2) # the argument is the awareness level, which can be 0, 1 or 2:
                                                       # (for 1-to-1 pixel control it seems to need a non-zero value.)

pygame.mouse.set_visible(False) # Hide the mouse

# Use a Rectangle object to find your system's maximum screen size.
screen_rec = pygame.display.set_mode((0,0),pygame.FULLSCREEN,32).get_rect()

# Create a light blue screen and show it.
screen = pygame.display.set_mode(screen_rec.size,pygame.FULLSCREEN,32)
screen.fill(Color("CornflowerBlue"))
pygame.display.flip()
sleep(2) # Pause for effect.

# Now let's create an example image using a pygame surface.
# The image will be our stand-in for an image loaded from a .bmp or .png
# file from disk. (You can also think of the image as a sprite.) We will 
# display the image on the screen, but we will work on the image surface
# using a pixel array. The image could be created using a pixelarray too,
# but pygame's draw methods are much faster so we will create the image
# using those. Then, once the image is created, we will use
# a pixel array to sample the colors and then to modify that image.

image = pygame.Surface((300,100))
image.fill(Color("IndianRed"))
image_rec = image.get_rect()  # Pygame Rectangle Objects are So useful!

pygame.draw.rect(image,Color("GhostWhite"),image_rec,7)
pygame.draw.line(image,Color("GhostWhite"),image_rec.topleft,image_rec.bottomright,4)

image_rec.center = screen_rec.center
screen.blit(image,image_rec)
pygame.display.update()
sleep(2) # Another dramatic pause!

# We now have an example image in 'image' which we will pretent was
# loaded from an image file. We want to sample the colors using 
# a pixel array.

''' ================================================================='''
#  The key thing to remember about pixel arrays is that the image is
#  locked out when the array is created, and is only releasod when
#  the array is deleted. You are working on the image at the pixel
#  level through the array and will not be able to do anything else
#  with the image while the pixel array exists.
''' ================================================================='''
my_pixel_array = pygame.PixelArray(image)  # The image is locked out.

color_log = {}  # Create a dictionary object. It will contain each unique
                # color and the pixel count having that color. 

for row in my_pixel_array:
    for pix in row:
        a,r,g,b = Color(pix) # ignore 'a', the 'alpha' value. It is always '0'
        color = (r,g,b)      # Create a new color tuple for our log. (We do this only for the sake of esthetics in the final output to the CCW.) 

        cnt = color_log.setdefault(color,0) # If the color is not in our log, add it, with a count value of 0. Always returns the count value. 
        color_log[color] = cnt+1 # Add one to the pixel count for that color


# Finished! But before we exit pygame and show the final counts,
# let's see how the pixel array can be used to change individual pixels.
# We could draw images or set random pixels to random colors, but let's
# change all of the GhostWhite pixels to black, then move the image
# across the screen.

for row in range(len(my_pixel_array)):
    for pix in range(len(my_pixel_array[row])):
        a,r,g,b = Color(my_pixel_array[row][pix])
        color = (r,g,b)
        if color == Color("GhostWhite"):
            my_pixel_array[row][pix] = Color("BLACK")

del my_pixel_array  # <-- THIS releases our image back to us!

screen.fill(Color("CornflowerBlue")) # Start with a fresh screen.

for c in range(-100,screen_rec.height+100,2): # Let's move image diagonally
    pygame.draw.rect(screen,Color("CornflowerBlue"),image_rec,0) # Erase previous image.
    image_rec.topleft = (c,c)    # Use rectangle to move the image.
    screen.blit(image,image_rec) # Draw image in new location.
    pygame.display.update()      # Show image in new location.

pygame.quit()

print("\n\n Finished.\n\n Here is the final tally:\n")

# Now show the results of our earlier pixel count and exit program.
for key,value in color_log.items():
    print(" Color: "+str(key).ljust(20," "),"Number of Pixels with that color:",value)


input("\n Press [Enter] to Quit: ")        
print("\n\n ",end="")
exit()

Последний сюрприз, о котором вы должны знать. Иногда, если у вас есть какая-то ошибка кодирования в блоке, использующем массив пикселей, блок может прерваться, если исходное изображение все еще заблокировано. Затем, в следующий раз, когда вы попытаетесь отобразить или использовать свое изображение, вы получите сообщение об ошибке. что-то вроде «Вы не можете отобразить заблокированную поверхность». Go вернитесь и проверьте свой код, работающий с массивом пикселей. Вероятно, это и произошло.

Надеюсь, вы найдете мой пример интересным. Удачного кодирования !!

-Science_1

0 голосов
/ 20 марта 2020

Альтернативный метод, если изображения маленькие или если вас не беспокоит скорость, может заключаться в использовании массива пикселей. Насколько я понимаю, цвета, обнаруженные Python, являются выборочными приближениями только цветов; но пиксельные массивы у меня хорошо работали в прошлом, и я не сталкивался с проблемами неточности с возвращенными цветами. Пиксельные массивы имеют несколько особенностей, о которых нужно знать, когда вы их используете. Дайте мне знать, если вы заинтересованы и вам нужен рабочий пример. Я мог бы что-то взбить.

...