I am trying to develop a device that changes the RGB led strips according to the colour of my display. To this, I am planning on screenshotting the screen and normalising/taking the mean of the colours of individual pixels in the display. I am having trouble normalising the image and taking out the average colour of the image. Here's the code I am using.
import numpy as np
import cv2
import mss
import time
def getAverageColor(frame):
(B, G, R) = 0, 0, 0
for i in frame:
for j in i:
B = j[0]
G = j[1]
R = j[2]
B /= len(frame) * len(frame[0])
G /= len(frame) * len(frame[0])
R /= len(frame) * len(frame[0])
return (B, G, R)
with mss.mss() as sct:
# Grab frames in an endless lopp until q key is pressed
time.sleep(2)
# Itterate the list of monitors, and grab one frame from each monitor (ignore index 0)
for monitor_number, mon in enumerate(sct.monitors[1:]):
monitor = {"top": mon["top"], "left": mon["left"], "width": mon["width"], "height": mon["height"], "mon": monitor_number} # Not used in the example
# Grab the data
img = np.array(sct.grab(mon)) # BGRA Image (the format BGRA, at leat in Wiqndows 10).
print(getAverageColor(img))
# Show down-scaled image for testing
# The window name is img0, img1... applying different monitors.
cv2.imshow(f'img{monitor_number}', cv2.resize(img, (img.shape[1]//4, img.shape[0]//4)))
key = cv2.waitKey(1)
if key == ord('q'):
break
cv2.destroyAllWindows()
The program works fine but I would like to ask if there is any way o take out the average colour in openCV itself as my method is not very well recommended as it can be very slow in processing. Not to add this but the code is not very accurate as well.
CodePudding user response:
Image processing with lists and for
loops is inefficient, slow, and error-prone in Python. try to use Numpy, or a vectorised library such as OpenCV, scikit-image, wand or PIL/Pillow.
Make a sample gradient image image from lime green to yellow, i.e. with no blue, solid green and a gradient in red which will give us simple, easily checked means:
magick -size 256x256 gradient:lime-yellow png24:start.png
Now get means of all three channels with Numpy:
import cv2
import numpy as np
# Load image
im = cv2.imread('start.png')
print(im.shape) # prints (256, 256, 3)
meanBlue = np.mean(im[...,0]) # gets value=0
meanGreen = np.mean(im[...,1]) # gets value=255.0
meanRed = np.mean(im[...,2]) # gets value=127.5
CodePudding user response:
I have updated the function getAverageColor, idea is to find the dominant color. I believe this gives better color options. Time is an issue, I will update in case I could find a way to make it faster.
import pandas as pd
from scipy.cluster.vq import whiten
from scipy.cluster.vq import kmeans
# import matplotlib.pyplot as plt
def getAverageColor(frame):
r = []
g = []
b = []
print("Frames")
for row in frame:
for temp_r, temp_g, temp_b, temp in row:
r.append(temp_r)
g.append(temp_g)
b.append(temp_b)
df = pd.DataFrame({'r' : r, 'g' : g, 'b' : b})
df['scaled_r'] = whiten(df['r'])
df['scaled_b'] = whiten(df['b'])
df['scaled_g'] = whiten(df['g'])
cluster_centers, _ = kmeans(df[['scaled_r', 'scaled_b', 'scaled_g']], 3)
dominant_colors = []
r_std, g_std, b_std = df[['r', 'g', 'b']].std()
for cluster_center in cluster_centers:
red_scaled, green_scaled, blue_scaled = cluster_center
dominant_colors.append((red_scaled * r_std / 255, green_scaled * g_std / 255, blue_scaled * b_std / 255))
print("Dominant", dominant_colors)
return(dominant_colors[0])
# plt.imshow([dominant_colors])
# plt.show()