70 lines
2.8 KiB
Python
70 lines
2.8 KiB
Python
#!/usr/bin/env python3
|
|
"""make_synthetic.py - a sharp, detailed stand-in frame for encode_bench.
|
|
|
|
The tower cameras have so far only seen blurred, dim indoor scenes, which
|
|
compress far faster and smaller than a sharp outdoor view. This writes a
|
|
"dead leaves" texture - overlapping discs with power-law radii, the texture
|
|
chart of ISO 19567-2 - with mild sensor noise, in the three formats
|
|
camera_bench.py `save` produces (RGB8, BayerRG8, BayerRG12p), so the encode
|
|
numbers can be bounded from above.
|
|
|
|
python3 make_synthetic.py <out_dir> [width] [height]
|
|
|
|
Needs numpy and OpenCV (the tower's system python3 has both).
|
|
"""
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
import cv2
|
|
import numpy as np
|
|
|
|
out = sys.argv[1]
|
|
w = int(sys.argv[2]) if len(sys.argv) > 2 else 5328
|
|
h = int(sys.argv[3]) if len(sys.argv) > 3 else 3040
|
|
rng = np.random.default_rng(20261001)
|
|
|
|
img = np.full((h, w, 3), 128, np.uint8)
|
|
rmin, rmax = 2.0, 600.0
|
|
# Enough discs to cover the frame several times over: with this radius
|
|
# distribution one disc covers ~143 px on average, the frame 16.2 M px.
|
|
n = 600000
|
|
# Radii with density ~ r^-3 (scale-invariant, as in natural images).
|
|
u = rng.random(n)
|
|
radii = 1.0 / np.sqrt(u * (1 / rmin**2 - 1 / rmax**2) + 1 / rmax**2)
|
|
for r in radii:
|
|
c = (int(rng.integers(0, w)), int(rng.integers(0, h)))
|
|
col = tuple(int(x) for x in rng.integers(10, 246, 3))
|
|
cv2.circle(img, c, int(r), col, -1, lineType=cv2.LINE_AA)
|
|
|
|
# Mild sensor noise, in 12-bit units, then the three delivered formats.
|
|
lin = img.astype(np.float32) * 16 + rng.normal(0, 24, img.shape).astype(np.float32)
|
|
lin12 = np.clip(lin, 0, 4095).astype(np.uint16)
|
|
rgb8 = (lin12 >> 4).astype(np.uint8)
|
|
|
|
# The camera's BayerRG: R at (even row, even col), B at (odd, odd).
|
|
m12 = np.empty((h, w), np.uint16)
|
|
m12[0::2, 0::2] = lin12[0::2, 0::2, 0]
|
|
m12[0::2, 1::2] = lin12[0::2, 1::2, 1]
|
|
m12[1::2, 0::2] = lin12[1::2, 0::2, 1]
|
|
m12[1::2, 1::2] = lin12[1::2, 1::2, 2]
|
|
m8 = (m12 >> 4).astype(np.uint8)
|
|
|
|
# GenICam 12p: two pixels in three bytes, little-endian.
|
|
flat = m12.reshape(-1)
|
|
p0, p1 = flat[0::2].astype(np.uint32), flat[1::2].astype(np.uint32)
|
|
packed = np.empty((p0.size, 3), np.uint8)
|
|
packed[:, 0] = p0 & 0xFF
|
|
packed[:, 1] = ((p0 >> 8) & 0x0F) | ((p1 & 0x0F) << 4)
|
|
packed[:, 2] = (p1 >> 4) & 0xFF
|
|
|
|
os.makedirs(out, exist_ok=True)
|
|
for fmt, data in (("RGB8", rgb8), ("BayerRG8", m8), ("BayerRG12p", packed)):
|
|
data.tofile(os.path.join(out, f"{fmt}_0.bin"))
|
|
with open(os.path.join(out, f"{fmt}_0.json"), "w") as f:
|
|
json.dump(dict(fmt=fmt, width=w, height=h, synthetic="dead leaves + noise"), f)
|
|
cv2.imwrite(os.path.join(out, "preview.jpg"),
|
|
cv2.cvtColor(cv2.resize(rgb8, (w // 4, h // 4), interpolation=cv2.INTER_AREA),
|
|
cv2.COLOR_RGB2BGR), [cv2.IMWRITE_JPEG_QUALITY, 85])
|
|
print(json.dumps(dict(out=out, width=w, height=h, mean=float(rgb8.mean()))))
|