This is a continuation of my first attempt of optimizing a cheap HDMI grabber.
After my fiery failure with the first cheap USB grabber, I decided to give it another shot. This time I selected a more promising looking capture card with a metal casing. A friend of mine owns the same card, so I knew it uses the same chipset but another board design.
My goal was still to optimize the output colors. To put my research on more stable feet, I built myself a program that calculates the Delta E of colors in a custom color card that I created.
A custom script overlays the color card with the captured image to immediately show the color differences. The script also calculates the Delta E for each color as well as the average value. With the manufacturer settings, the color seems oversaturated, and the average Delta E is 5.6. This value is a pretty significant deviation.
import random, os, time
import cv2
from PIL import Image, ImageDraw
from colormath.color_objects import sRGBColor, LabColor
from colormath.color_conversions import convert_color
from colormath.color_diff import delta_e_cie2000
orig = Image.open("testcard.png")
cap = cv2.VideoCapture(3)
cap.set(cv2.CAP_PROP_FOURCC , cv2.VideoWriter_fourcc(*"MJPG") )
cap.set(3, 1920)
cap.set(4, 1080)
ret, captured = cap.read()
cap.release()
cv2.imwrite("captured.png", captured)
copy = Image.open("captured.png")
draw = ImageDraw.Draw(orig)
total_delta_e = 0
taken_measures = 0
for x in range(50, 1921-100, 200):
for y in range(50, 1080-100, 200):
copy_part = copy.crop(box=(x+75,y,x+151, y+151))
orig.paste(copy_part, box=(x+75,y,x+151, y+151))
orig_color = orig.getpixel((x+20, y+20))
copy_color = orig.getpixel((x+20+75, y+20))
orig_color = sRGBColor(orig_color[0], orig_color[1], orig_color[2], is_upscaled=True)
copy_color = sRGBColor(copy_color[0], copy_color[1], copy_color[2], is_upscaled=True)
orig_color_lab = convert_color(orig_color, LabColor)
copy_color_lab = convert_color(copy_color, LabColor)
delta_e = delta_e_cie2000(orig_color_lab, copy_color_lab)
total_delta_e += delta_e
taken_measures += 1
draw.text((x+100, y+160), str(round(delta_e,2)), fill="white")
draw.text((50, 20), "Avg delta_e: " + str(round(total_delta_e/taken_measures, 2)), fill="white")
orig.save("overlay.png")
I used a script to iterate sensible value areas for brightness, contrast, saturation, and hue independently from each other. For each step, I calculated the average Delta E. The results are shown in the four diagrams below. The green line indicates the original Delta E of 5.6.
The script shows that even individual tweaks can already reduce the Delta E to about 4.4. With a bit of manual tweaking, I achieved a Delta E of 3.02 - close to the 3.0 that is the standard for displays used in image editing. Using another script, I searched for even better settings and finally achieved a Delta E of 2.88 using the parameters:
Brightness -9
Saturation 133
Contrast 137
Hue 0
The difference between manufacturer settings and the optimized values is visible in real capture scenarios. All in all, I am pretty happy with the results. Also, I enjoy that this capture card did not catch fire during my testing.