2024-04-21 05:15:52 +00:00
|
|
|
import argparse
|
2024-05-07 17:40:44 +00:00
|
|
|
import time
|
2024-04-21 05:15:52 +00:00
|
|
|
import cv2
|
|
|
|
import numpy as np
|
|
|
|
from creedsolo import RSCodec
|
|
|
|
from raptorq import Decoder
|
|
|
|
|
|
|
|
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
2024-04-22 18:36:57 +00:00
|
|
|
parser.add_argument("-i", "--input", help="camera device index or input video file", default=0)
|
2024-04-29 01:35:46 +00:00
|
|
|
parser.add_argument("-o", "--output", help="output file for decoded data", default="out")
|
2024-04-22 18:36:57 +00:00
|
|
|
parser.add_argument("-x", "--height", help="grid height", default=100, type=int)
|
|
|
|
parser.add_argument("-y", "--width", help="grid width", default=100, type=int)
|
|
|
|
parser.add_argument("-l", "--level", help="error correction level", default=0.1, type=float)
|
2024-04-28 23:25:38 +00:00
|
|
|
parser.add_argument("-s", "--size", help="number of bytes to decode", type=int)
|
|
|
|
parser.add_argument("-p", "--psize", help="packet size", type=int)
|
2024-04-21 05:15:52 +00:00
|
|
|
args = parser.parse_args()
|
|
|
|
|
2024-05-14 01:37:57 +00:00
|
|
|
cheight = cwidth = max(args.height // 15, args.width // 15)
|
2024-04-21 05:15:52 +00:00
|
|
|
frame_size = args.height * args.width - 4 * cheight * cwidth
|
2024-04-28 19:52:52 +00:00
|
|
|
frame_bytes = frame_size * 3 // 8
|
|
|
|
frame_xor = np.arange(frame_bytes, dtype=np.uint8)
|
2024-04-28 20:29:35 +00:00
|
|
|
rs_bytes = frame_bytes - (frame_bytes + 254) // 255 * int(args.level * 255) - 4
|
2024-04-21 05:15:52 +00:00
|
|
|
|
2024-04-22 04:13:46 +00:00
|
|
|
rsc = RSCodec(int(args.level * 255))
|
2024-04-28 19:52:52 +00:00
|
|
|
decoder = Decoder.with_defaults(args.size, rs_bytes)
|
2024-04-22 18:36:57 +00:00
|
|
|
|
2024-04-23 17:23:56 +00:00
|
|
|
|
|
|
|
def find_corner(A, f):
|
|
|
|
cx, cy = A.shape[:2]
|
2024-04-28 18:28:04 +00:00
|
|
|
# Resize so smaller dim is 8
|
|
|
|
scale = min(cx // 8, cy // 8)
|
2024-04-23 17:23:56 +00:00
|
|
|
B = cv2.resize(A, (cy // scale, cx // scale), interpolation=cv2.INTER_AREA)
|
2024-04-28 18:28:04 +00:00
|
|
|
guess = np.array(np.unravel_index(np.argmax(f(B.astype(np.float64))), B.shape[:2])) * scale + scale // 2
|
|
|
|
mask = cv2.floodFill(
|
|
|
|
A,
|
|
|
|
np.empty(0),
|
2024-04-29 01:35:46 +00:00
|
|
|
tuple(np.flip(guess)),
|
2024-04-28 18:28:04 +00:00
|
|
|
0,
|
2024-04-28 19:52:52 +00:00
|
|
|
(100, 100, 100),
|
|
|
|
(100, 100, 100),
|
2024-04-28 18:28:04 +00:00
|
|
|
cv2.FLOODFILL_MASK_ONLY + cv2.FLOODFILL_FIXED_RANGE,
|
|
|
|
)[2][1:-1, 1:-1].astype(bool)
|
2024-04-23 17:23:56 +00:00
|
|
|
return np.average(np.where(mask), axis=1), np.average(A[mask], axis=0).astype(np.float64)
|
|
|
|
|
|
|
|
|
2024-05-14 15:22:33 +00:00
|
|
|
if isinstance(args.input, str) and args.input.isdecimal():
|
2024-04-22 18:36:57 +00:00
|
|
|
args.input = int(args.input)
|
|
|
|
cap = cv2.VideoCapture(args.input)
|
2024-04-21 05:15:52 +00:00
|
|
|
data = None
|
2024-05-12 20:33:34 +00:00
|
|
|
start_time = 0
|
|
|
|
status = 0
|
|
|
|
decoded = 0
|
2024-04-21 05:15:52 +00:00
|
|
|
while data is None:
|
2024-04-22 18:36:57 +00:00
|
|
|
try:
|
|
|
|
ret, raw_frame = cap.read()
|
|
|
|
if not ret:
|
|
|
|
print("End of stream")
|
2024-04-23 18:17:26 +00:00
|
|
|
break
|
2024-05-12 20:33:34 +00:00
|
|
|
if isinstance(args.input, int) and (status == 1 or (status == 0 and np.random.rand() < 0.5)):
|
|
|
|
status = 2
|
|
|
|
print("Skipped")
|
|
|
|
continue
|
2024-04-28 23:25:38 +00:00
|
|
|
# raw_frame is a uint8 BE CAREFUL
|
2024-04-23 18:17:26 +00:00
|
|
|
cv2.imshow("", raw_frame)
|
|
|
|
cv2.waitKey(1)
|
2024-04-23 05:42:51 +00:00
|
|
|
raw_frame = cv2.cvtColor(raw_frame, cv2.COLOR_BGR2RGB)
|
2024-04-22 04:13:46 +00:00
|
|
|
|
2024-04-30 17:19:20 +00:00
|
|
|
# Find positions and colors of corners
|
2024-04-22 18:36:57 +00:00
|
|
|
X, Y = raw_frame.shape[:2]
|
2024-05-13 03:39:18 +00:00
|
|
|
cx, cy = X // 4, Y // 4
|
2024-05-07 17:40:44 +00:00
|
|
|
widx, wcol = find_corner(raw_frame[:cx, :cy], lambda B: np.sum(B, axis=2) - 2 * np.std(B, axis=2))
|
2024-04-23 05:42:51 +00:00
|
|
|
ridx, rcol = find_corner(raw_frame[:cx, Y - cy :], lambda B: B[:, :, 0] - B[:, :, 1] - B[:, :, 2])
|
|
|
|
ridx[1] += Y - cy
|
|
|
|
gidx, gcol = find_corner(raw_frame[X - cx :, :cy], lambda B: B[:, :, 1] - B[:, :, 2] - B[:, :, 0])
|
|
|
|
gidx[0] += X - cx
|
|
|
|
bidx, bcol = find_corner(raw_frame[X - cx :, Y - cy :], lambda B: B[:, :, 2] - B[:, :, 0] - B[:, :, 1])
|
|
|
|
bidx[0] += X - cx
|
|
|
|
bidx[1] += Y - cy
|
2024-04-22 04:13:46 +00:00
|
|
|
|
2024-04-22 18:36:57 +00:00
|
|
|
# Find basis of color space
|
|
|
|
origin = (rcol + gcol + bcol - wcol) / 2
|
|
|
|
rcol -= origin
|
|
|
|
gcol -= origin
|
|
|
|
bcol -= origin
|
|
|
|
F = 255 * np.linalg.inv(np.stack((rcol, gcol, bcol)).T)
|
|
|
|
|
2024-04-29 01:35:46 +00:00
|
|
|
cch = cheight / 2 - 1
|
|
|
|
ccw = cwidth / 2 - 1
|
|
|
|
M = cv2.getPerspectiveTransform(
|
|
|
|
np.float32([np.flip(widx), np.flip(ridx), np.flip(gidx), np.flip(bidx)]),
|
2024-04-29 18:31:38 +00:00
|
|
|
np.float32(
|
|
|
|
[
|
|
|
|
[ccw, cch],
|
|
|
|
[args.width - ccw - 1, cch],
|
2024-05-07 17:40:44 +00:00
|
|
|
[ccw, args.height - cch - 1],
|
2024-04-29 18:31:38 +00:00
|
|
|
[args.width - ccw - 1, args.height - cch - 1],
|
|
|
|
]
|
|
|
|
),
|
2024-04-22 18:36:57 +00:00
|
|
|
)
|
2024-04-29 01:35:46 +00:00
|
|
|
frame = cv2.warpPerspective(raw_frame, M, (args.width, args.height))
|
2024-04-30 17:19:20 +00:00
|
|
|
# Convert to new color space
|
2024-05-13 23:45:14 +00:00
|
|
|
frame = (np.squeeze(F @ (frame - origin)[..., np.newaxis]) >= 160).astype(np.uint8)
|
2024-05-07 17:40:44 +00:00
|
|
|
frame = np.packbits(
|
|
|
|
np.concatenate(
|
|
|
|
(
|
|
|
|
frame[:cheight, cwidth : args.width - cwidth].flatten(),
|
|
|
|
frame[cheight : args.height - cheight].flatten(),
|
|
|
|
frame[args.height - cheight :, cwidth : args.width - cwidth].flatten(),
|
|
|
|
)
|
2024-04-22 18:36:57 +00:00
|
|
|
)
|
2024-05-12 20:33:34 +00:00
|
|
|
)[:frame_bytes]
|
2024-05-07 17:40:44 +00:00
|
|
|
reshape_len = frame_bytes // 255 * 255
|
|
|
|
frame[:reshape_len] = np.ravel(frame[:reshape_len].reshape(255, reshape_len // 255), "F")
|
|
|
|
data = decoder.decode(bytes(rsc.decode(bytearray(frame ^ frame_xor))[0][: args.psize]))
|
2024-05-12 20:33:34 +00:00
|
|
|
decoded += 1
|
|
|
|
status = 1
|
|
|
|
if start_time == 0:
|
|
|
|
start_time = time.time()
|
2024-04-22 18:36:57 +00:00
|
|
|
print("Decoded frame")
|
2024-04-23 05:42:51 +00:00
|
|
|
except KeyboardInterrupt:
|
2024-04-29 01:35:46 +00:00
|
|
|
break
|
2024-05-07 17:40:44 +00:00
|
|
|
except Exception as e:
|
2024-05-12 20:33:34 +00:00
|
|
|
status = 0
|
2024-05-07 17:40:44 +00:00
|
|
|
print(e)
|
|
|
|
cap.release()
|
2024-05-12 20:33:34 +00:00
|
|
|
print(decoded)
|
2024-04-22 18:36:57 +00:00
|
|
|
with open(args.output, "wb") as f:
|
2024-04-21 05:15:52 +00:00
|
|
|
f.write(data)
|
2024-05-07 17:40:44 +00:00
|
|
|
print(8 * len(data) / (time.time() - start_time))
|