This script takes an image copied from the clipboard and analyzes the n darkest pixels of the image. It will loop through each found value, prints out the value information and the quantity, then displays the visual location of the pixels in a tkinter window.
There are two modes for reading the pixels. One is by simply looping through the darkest pixels given the default 0-255 value range. The other is by compressing the range to 0-100 to fit the convention of mainstream image-editing programs.
The script seems to work through casual testing, but many parts of the script feels very brittle, awkward and hacked together. A few of my concerns:
My script finds the darkest pixels, removes them, then finds the next darkest pixels. But my method of 'removal' is by assigning the value of the found pixels to '999'. Is there a better method?
My method of visually marking the pixels is by looping through the found pixels manually. Is there a faster way?
Is
min(gray.flatten())
a good way to find the darkest pixels?Any other inaccuracies regarding image handling or other bad practices
General performance
import platform
if (platform.system() != "Windows"):
print("Only Windows is supported for now.")
raise SystemExit()
import cv2
import math
import argparse
import numpy as np
import tkinter as tk
import win32clipboard
from io import BytesIO
from PIL import ImageTk, Image, ImageGrab
parser = argparse.ArgumentParser(description='Finds the darkest pixels of a grayscaled image pasted from the clipboard. It will output the value information, quantity and a visual pixel map. The pixel map will be copied to your clipboard.')
parser.add_argument('-n', '--num', dest='num', metavar='NUM', type=int, default=5,
help='The number of darkest values to find. Default=5')
parser.add_argument('-a', '--acc', dest='acc', default=False, action='store_true',
help='Detects values using an accurate 0-255 range, instead of a compressed 0-100 range.')
parser.add_argument('-p', '--pix', dest='pix', default=False, action='store_true',
help='Colorizes detected pixels, instead of drawing a circle around it.')
parser.add_argument('-t', '--threshold', dest='threshold', metavar='VALUE', type=int, default=False,
help='Detects only values lighter or as light as the specified threshold (0-100 range).')
parser.add_argument('-c', '--col', dest='color', default=False, action='store_true',
help='Outputs pixel map in the original color, instead of the grayscaled version.')
args = parser.parse_args()
def ordinal(n):
n = int(n)
suffix = ['th', 'st', 'nd', 'rd', 'th'][min(n % 10, 4)]
if 11 <= (n % 100) <= 13:
suffix = 'th'
return str(n) + suffix
def bmp_process(im):
output = BytesIO()
im.save(output, "BMP")
data = output.getvalue()[14:]
output.close()
return data
def clip_send(clip_type, data):
win32clipboard.OpenClipboard()
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardData(clip_type, data)
win32clipboard.CloseClipboard()
def show_img(im, size):
thumb = im.copy()
thumb.thumbnail(size, Image.ANTIALIAS)
window = tk.Tk()
w = size[0]
h = size[1]
ws = window.winfo_screenwidth()
hs = window.winfo_screenheight()
x = (ws/2) - (w/2)
y = (hs/2) - (h/2)
window.geometry('%dx%d+%d+%d' % (w, h, x, y))
img = ImageTk.PhotoImage(thumb)
panel = tk.Label(window, image=img)
panel.pack(side="bottom", fill="both", expand="yes")
window.mainloop()
try:
clip = ImageGrab.grabclipboard().convert('RGB')
clip.copy().verify()
except:
print("Invalid image data!")
raise SystemExit()
gray = cv2.cvtColor(np.array(clip.copy()), cv2.COLOR_RGB2GRAY)
if args.color:
img = cv2.cvtColor(np.array(clip.copy()), cv2.COLOR_RGB2BGR)
else:
img = cv2.cvtColor(gray.copy(), cv2.COLOR_GRAY2BGR)
if args.threshold:
threshold = math.ceil(( args.threshold / 100 ) * 255)
while True:
rounded = int(round((threshold / 255) * 100))
if rounded < args.threshold:
break
threshold -= 1
mask = gray <= threshold
gray[mask] = 999
for i in range(args.num):
raw = min(gray.flatten())
value_f = (raw / 255) * 100
value = int(round(value_f))
cnt = 0
n = raw
marked = img.copy()
while True:
rounded = int(round((n / 255) * 100))
if rounded == value:
points = np.argwhere(gray == n)
mask = gray == n
gray[mask] = 999
for point in points:
if args.pix:
marked[point[0], point[1]] = [0, 0, 255]
else:
cv2.circle(marked, (point[1], point[0]), 5, (0, 0, 255), 2)
cnt += 1
else:
break
n += 1
if args.acc or n > 255:
break
if args.acc:
print("The {0} darkest grayscale value is {1}% ({2}/255 or {3:.2f}%), quantity is {4}".format(str(ordinal(i + 1)), str(value), str(raw), value_f, str(cnt)))
else:
print("The {0} darkest grayscale value is {1}%, quantity is {2}".format(str(ordinal(i + 1)), str(value), str(cnt)))
display = Image.fromarray(cv2.cvtColor(marked, cv2.COLOR_BGR2RGB))
clip_out = bmp_process(display)
clip_send(win32clipboard.CF_DIB, clip_out)
show_img(display, (500, 500))
if n > 255:
raise SystemExit()