-from .config import IMAGE_WIDTH, IMAGE_HEIGHT
+from .config import IMAGE_WIDTH, IMAGE_HEIGHT, CAP_WAIT
import numpy as np
import cv2 as cv
-CAP_WAIT = 1
class Camera():
def __init__(self, device):
#self.calibrate()
+ # get image from camera and fix perspective
def get(self, image):
cv.imshow("LUT Calibration", image)
cv.waitKey(CAP_WAIT)
- #_, capture = self.camera.read()
- #capture = cv.warpPerspective(capture, self.homography, (IMAGE_WIDTH, IMAGE_HEIGHT))
+ """
+ _, capture = self.camera.read()
+ capture = cv.warpPerspective(
+ capture,
+ self.homography,
+ (IMAGE_WIDTH, IMAGE_HEIGHT)
+ )
+ """
return image
return capture
+ # standard calibration function
def calibrate(self):
calibration_image = cv.imread("../calibration.jpg")
+"""
+edge length of LUT
+
+very light testing in python outside of the real world has shown 12 to be
+optimal, conventially 33 is used. This will need to be tested with a camera.
+"""
LUT_SIZE = 12
-IMAGE_WIDTH = 640#1920
-IMAGE_HEIGHT = 360#1080
+IMAGE_WIDTH = 640 #1920
+IMAGE_HEIGHT = 360 #1080
+# size of the qr codes and the space around them (px)
QR_SIZE = 100
QR_PADDING = 10
+
+# the wait time between each camera check after we stop generating
+WAIT_TIME = 0.1 #s
+# number of times to wait
+WAIT_TICKS = 100 #s
+
+# how long to wait before capturing image (1 minimum)
+CAP_WAIT = 1
-#!.venv/bin/python
-
from .frame import blank, generate, result
-from .config import LUT_SIZE
+from .config import LUT_SIZE, WAIT_TIME, WAIT_TICKS
from scipy.interpolate import make_interp_spline
from tqdm import tqdm
import cv2 as cv
import time
-WAIT_TIME = 0.1
-WAIT_TICKS = 100
-
class Cube():
def __init__(self, camera):
self.camera = camera
self.size = LUT_SIZE
self.lut = np.zeros((LUT_SIZE, LUT_SIZE, LUT_SIZE, 3), dtype=np.uint8)
+
+ # the scaling factor of each color to turn it into a index
self.s = 255 / (self.size-1)
print(f"""
colors:\t{self.size**3}
""")
+ # go through each color and put pass it through the screen/camera
def process(self):
seq = [i * self.s for i in range(0,self.size)]
for r in tqdm(seq):
self.invert()
+ """
+ the rust program needs to be able to index easy into the LUT, this means
+ getting the color you have, scaling it, and using it as the index. at
+ the moment we have a cube that was indexed by its inputs not by its
+ outputs. this is easy if you have a cube of 255 width but we dont.
+
+ the solution to this is to take each contour of each channel, and
+ interpolate it at the regular inteval of the size of the LUT. this means
+ that the quality of the interpolation algorithim is the quality of the
+ LUT. see below.
+ """
def invert(self):
print("inverting LUT...")
+ # helper function to rotate and resample each channel
def iter_channels(a):
r = self.lut[:,:,:,0]
g = self.lut[:,:,:,1]
return np.stack((r, g, b), axis=-1)
+ # helper function to resample a channel
def resample_channel(c):
c = np.reshape(c, (self.size * self.size, self.size))
self.lut = iter_channels(self.lut)
+ """
+ 'check' what a color looks like through the camera. color returned may
+ not be the transformed version of the color you put in due to the lag
+ between the display and the capture. waiting a long time, clearing the
+ cap etc.. all proved ineffective. therefore all images are tagged with a
+ qr code of their color.
+ """
def check(self, color=None):
-
if color is None:
image = blank()
else:
-#!.venv/bin/python
-
from .config import QR_SIZE, QR_PADDING, IMAGE_WIDTH, IMAGE_HEIGHT
from pyzbar.pyzbar import decode
import qrcode
import os
+# helper function to apply non-linear error to each channel of image
def cast(a):
a = np.array(a, dtype=np.float64)
a[...,0] = ((a[...,0] / 255.) ** 2) * 255.
return a
+# generate image display color tagged with qr code
def generate(color):
# make qr code
qr = qrcode.QRCode(
return c_image
+# make blank image
def blank():
image = np.zeros((IMAGE_HEIGHT,IMAGE_WIDTH,3), dtype=np.uint8)
return image
+# decode captured image to origional color and new color
def result(capture):
l = QR_SIZE + QR_PADDING
+ """
+ get the mean across a rectangular section of the image described by the
+ innermost corners of all the qr codes.
+ """
new = np.mean(capture[l:-l,l:-l], axis=(0,1)).astype(np.uint8)
codes = decode(capture)
if codes == []: return None, None
+ # always use the code which has the best quality capture
codes.sort(key=lambda x:x.quality)
data = codes[0].data
data = [int(x) for x in data.split()]
import matplotlib.pyplot as plt
import numpy as np
+# make dual plot showing the curves and the 3d plot
def show(a):
size = a.shape[0]
fig = plt.figure(figsize=plt.figaspect(1.))
b = a.astype(np.float64)
+ # get slice of the LUT to graph as the curves
ax.plot(b[1,1,...,2]+50, c="blue")
ax.plot(b[1,...,1,1]+25, c="green")
ax.plot(b[...,1,1,0], c="red")
ax.set_zlabel("b")
plt.show()
+# unused
def compare(a, b):
plt.hist(a.flat, bins=range(100), fc='k', ec='k', color="red")
plt.hist(b.flat, bins=range(100), fc='k', ec='k', color="blue")
import numpy as np
import time
+# monolithic validator
def validate(cube):
print("testing LUT...")
+ # get test image and apply cast to it
image = cv.imread("src/calibration.jpg")
height, width, _ = image.shape
a = cast(np.flip(image, axis=-1)).astype(np.uint8)
casted = cast(np.flip(image, axis=-1)).astype(np.uint8)
+ # track the application time
start = time.time()
+ # scale cube
a = np.divide(a, cube.s)
+ # calculate the two points enclosing the real point as well as the remainder
c1 = np.floor(a).astype(np.uint8)
c2 = np.ceil(a).astype(np.uint8)
rem = np.remainder(a, 1)
+ # helper function to index at location
def index_lut(a, i):
for ih in range(height):
for iw in range(width):
return i
+ # get the real values for each of the enclosing colors
c1 = index_lut(cube.lut, c1)
c2 = index_lut(cube.lut, c2)
+ # average between the two colors weighted with the remainder
a = c1 + np.array((c2 - c1) * rem, dtype=np.uint8)
a = np.flip(a, axis=-1)
dur = time.time() - start
+ # flip to bgr for display
casted = np.flip(casted, axis=-1)
# do the diff
-
diff = np.abs(image.sum(axis=-1, dtype=np.int16) - a.sum(axis=-1, dtype=np.int16))
diff = np.clip(diff, 0, 255).astype(np.uint8)
diff = np.stack((diff, diff, diff), axis=-1)
""")
# make the composite image
-
left = np.vstack((image, a), dtype=np.uint8)
right = np.vstack((casted, diff), dtype=np.uint8)
import matplotlib.pyplot as plt
if __name__ == "__main__":
+ # make camera object
eye = Camera(0)
+ # make the LUT and process through the camera
lut = Cube(eye)
lut.process()
+ # use graphing func to show the curves for debug
show(lut.lut)
+ # validate error of LUT
validate(lut)
save("./cube.npy", lut.lut)