fix(stereo_depth): negative disparity window + percentile normalize — movie stereo works
Movie SBS converges on the screen plane, so real content sits at NEGATIVE disparity; minDisparity=0 clipped everything behind convergence to black. Window is now ±num_disp/2 with 2-98 percentile normalization and a validity mask. Verified on Big Buck Bunny (half-OU) + One Night In Hell (half-SBS): structured depth + colored clouds that render in Blender. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
95708ad220
commit
cf402e322c
@ -50,8 +50,9 @@ subprocess.run([FFMPEG, "-y", "-loglevel", "error", "-i", src,
|
|||||||
frames = sorted(glob.glob(os.path.join(frames_dir, "*.png")))
|
frames = sorted(glob.glob(os.path.join(frames_dir, "*.png")))
|
||||||
print(f"[stereo] {len(frames)} frames, num_disp={num_disp} block={block}", flush=True)
|
print(f"[stereo] {len(frames)} frames, num_disp={num_disp} block={block}", flush=True)
|
||||||
|
|
||||||
sgbm = cv2.StereoSGBM_create(
|
min_disp = -(num_disp // 2) # movie stereo converges on the screen plane — content
|
||||||
minDisparity=0, numDisparities=num_disp, blockSize=block,
|
sgbm = cv2.StereoSGBM_create( # lives BOTH sides of it, so the window must go negative
|
||||||
|
minDisparity=min_disp, numDisparities=num_disp, blockSize=block,
|
||||||
P1=8 * 3 * block * block, P2=32 * 3 * block * block,
|
P1=8 * 3 * block * block, P2=32 * 3 * block * block,
|
||||||
disp12MaxDiff=1, uniquenessRatio=10, speckleWindowSize=100, speckleRange=2,
|
disp12MaxDiff=1, uniquenessRatio=10, speckleWindowSize=100, speckleRange=2,
|
||||||
mode=cv2.STEREO_SGBM_MODE_SGBM_3WAY)
|
mode=cv2.STEREO_SGBM_MODE_SGBM_3WAY)
|
||||||
@ -66,11 +67,16 @@ for i, f in enumerate(frames):
|
|||||||
L, R = img[:, : w // 2], img[:, w // 2:]
|
L, R = img[:, : w // 2], img[:, w // 2:]
|
||||||
gl, gr = cv2.cvtColor(L, cv2.COLOR_BGR2GRAY), cv2.cvtColor(R, cv2.COLOR_BGR2GRAY)
|
gl, gr = cv2.cvtColor(L, cv2.COLOR_BGR2GRAY), cv2.cvtColor(R, cv2.COLOR_BGR2GRAY)
|
||||||
disp = sgbm.compute(gl, gr).astype(np.float32) / 16.0
|
disp = sgbm.compute(gl, gr).astype(np.float32) / 16.0
|
||||||
disp[disp < 0] = 0
|
valid = disp > (min_disp - 0.5) # cv2 marks unmatched pixels min_disp-1
|
||||||
norm = (disp / max(1e-6, disp.max()) * 255).astype(np.uint8)
|
if valid.sum() > 100:
|
||||||
|
lo, hi = np.percentile(disp[valid], [2, 98])
|
||||||
|
else:
|
||||||
|
lo, hi = 0.0, 1.0
|
||||||
|
norm = np.zeros(disp.shape, np.uint8)
|
||||||
|
norm[valid] = (np.clip((disp[valid] - lo) / max(1e-6, hi - lo), 0, 1) * 255).astype(np.uint8)
|
||||||
cv2.imwrite(os.path.join(depth_dir, os.path.basename(f)), norm)
|
cv2.imwrite(os.path.join(depth_dir, os.path.basename(f)), norm)
|
||||||
if f == mid: # colored point cloud of the middle frame
|
if f == mid: # colored point cloud of the middle frame
|
||||||
ys, xs = np.where(disp > 1)
|
ys, xs = np.where(valid & (norm > 2))
|
||||||
step = max(1, len(ys) // 400_000) # cap cloud size
|
step = max(1, len(ys) // 400_000) # cap cloud size
|
||||||
ys, xs = ys[::step], xs[::step]
|
ys, xs = ys[::step], xs[::step]
|
||||||
z = disp[ys, xs]
|
z = disp[ys, xs]
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user