tune for dq6a to reach 30 fps for 2 sensors

This commit is contained in:
radxa-d6a
2026-04-02 16:44:02 +08:00
parent 51c259e523
commit 80e951d6cd
15 changed files with 474 additions and 70 deletions
+79 -13
View File
@@ -406,7 +406,7 @@ class Stitcher:
wx[-m:] = ramp[::-1]
return wy[:, None] * wx[None, :]
def _blend_tile_inplace(self, blend, wsum, cov, src, weight, y0, x0):
def _blend_tile_inplace(self, blend, wsum, cov, src, weight, y0, x0, scratch=None):
"""Copy non-overlap tiles directly; blend only when this tile touches existing content."""
h, w = src.shape[:2]
H, W = blend.shape[:2]
@@ -426,6 +426,19 @@ class Stitcher:
if not np.any(cov_patch):
blend_patch[:] = src_patch
wsum_patch[:] = w_patch
elif scratch is not None:
s_src, s_alpha = scratch
sf = s_src[:sh, :sw]
sa = s_alpha[:sh, :sw]
sf[:] = src_patch
np.add(wsum_patch, w_patch, out=wsum_patch)
np.maximum(wsum_patch, 1e-6, out=sa)
np.divide(w_patch, sa, out=sa)
np.subtract(sf, blend_patch, out=sf)
sf[:, :, 0] *= sa
sf[:, :, 1] *= sa
sf[:, :, 2] *= sa
np.add(blend_patch, sf, out=blend_patch)
else:
total = wsum_patch + w_patch
alpha = w_patch / np.maximum(total, 1e-6)
@@ -436,13 +449,15 @@ class Stitcher:
cov_patch[:] = 1
def _mean_gray_bgr(self, bgr):
return float(cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY).mean())
ch = cv2.mean(bgr)
return (ch[0] + ch[1] + ch[2]) / 3.0
def _match_bgr_brightness(self, tile_bgr, ref_mean_gray):
delta = int(round(ref_mean_gray - self._mean_gray_bgr(tile_bgr)))
if delta == 0:
return tile_bgr
return np.clip(tile_bgr.astype(np.int16) + delta, 0, 255).astype(np.uint8)
lut = np.clip(np.arange(256, dtype=np.int16) + delta, 0, 255).astype(np.uint8)
return cv2.LUT(tile_bgr, lut)
def _largest_fully_covered_rect(self, cov):
if cov.size == 0:
@@ -519,13 +534,22 @@ class Stitcher:
do_profile = verbose and frame_idx % print_every == 0
t_profile0 = cv2.getTickCount() / cv2.getTickFrequency() if do_profile else 0.0
t_ref_bgr = self._tile_to_bgr(t_ref_bgr)
if t_ref_bgr.ndim == 2:
t_ref_bgr = cv2.cvtColor(t_ref_bgr, cv2.COLOR_GRAY2BGR)
h0c, w0c = t_ref_bgr.shape[:2]
if match_brightness:
ref_mean_gray = self._mean_gray_bgr(t_ref_bgr)
tiles_bgr_c = [self._match_bgr_brightness(self._tile_to_bgr(tb), ref_mean_gray) for tb in tiles_bgr]
tiles_bgr_c = [
self._match_bgr_brightness(
tb if tb.ndim == 3 else cv2.cvtColor(tb, cv2.COLOR_GRAY2BGR),
ref_mean_gray,
)
for tb in tiles_bgr
]
elif any(tb.ndim == 2 for tb in tiles_bgr):
tiles_bgr_c = [tb if tb.ndim == 3 else cv2.cvtColor(tb, cv2.COLOR_GRAY2BGR) for tb in tiles_bgr]
else:
tiles_bgr_c = [self._tile_to_bgr(tb) for tb in tiles_bgr]
tiles_bgr_c = tiles_bgr
t_after_brightness = cv2.getTickCount() / cv2.getTickFrequency() if do_profile else 0.0
geom_key = self._geom_cache_key(t_ref_bgr, tiles_bgr_c, pad)
@@ -563,6 +587,20 @@ class Stitcher:
tx, ty = self._translation_from_H_to_ref(H_use)
pastes.append((int(np.floor(ty - y_min_f)), int(np.floor(tx - x_min_f))))
_dr_y0 = max(0, ref_y0)
_dr_x0 = max(0, ref_x0)
_dr_y1 = min(out_h, ref_y0 + h0c)
_dr_x1 = min(out_w, ref_x0 + w0c)
for _tb, _pa in zip(tiles_bgr_c, pastes):
if _pa is None:
continue
_py, _px = _pa
_th, _tw = _tb.shape[:2]
_dr_y0 = min(_dr_y0, max(0, _py))
_dr_x0 = min(_dr_x0, max(0, _px))
_dr_y1 = max(_dr_y1, min(out_h, _py + _th))
_dr_x1 = max(_dr_x1, min(out_w, _px + _tw))
canvas_cache["geom_key"] = geom_key
canvas_cache["out_w"] = out_w
canvas_cache["out_h"] = out_h
@@ -572,6 +610,7 @@ class Stitcher:
canvas_cache["x_min_f"] = x_min_f
canvas_cache["y_min_f"] = y_min_f
canvas_cache["crop_rect"] = None
canvas_cache["dirty_rect"] = (_dr_y0, _dr_y1, _dr_x0, _dr_x1)
canvas_cache["blend_buf"] = np.zeros((out_h, out_w, 3), dtype=np.float32)
canvas_cache["wsum_buf"] = np.zeros((out_h, out_w), dtype=np.float32)
canvas_cache["cov_buf"] = np.zeros((out_h, out_w), dtype=np.uint8)
@@ -588,12 +627,32 @@ class Stitcher:
wsum = canvas_cache["wsum_buf"]
cov = canvas_cache["cov_buf"]
weight_maps = canvas_cache["weight_maps"]
blend_buf.fill(0)
wsum.fill(0)
cov.fill(0)
if geom_cache_hit:
_dr = canvas_cache.get("dirty_rect")
if _dr is not None:
_dy0, _dy1, _dx0, _dx1 = _dr
blend_buf[_dy0:_dy1, _dx0:_dx1] = 0
wsum[_dy0:_dy1, _dx0:_dx1] = 0
cov[_dy0:_dy1, _dx0:_dx1] = 0
else:
blend_buf.fill(0)
wsum.fill(0)
cov.fill(0)
t_after_reset = cv2.getTickCount() / cv2.getTickFrequency() if do_profile else 0.0
blend_margin = 16 if blend else 0
max_th = max(h0c, *(tb.shape[0] for tb in tiles_bgr_c)) if tiles_bgr_c else h0c
max_tw = max(w0c, *(tb.shape[1] for tb in tiles_bgr_c)) if tiles_bgr_c else w0c
_ss = canvas_cache.get("scratch_src")
_sa = canvas_cache.get("scratch_alpha")
if _ss is None or _ss.shape[0] < max_th or _ss.shape[1] < max_tw:
_ss = np.empty((max_th, max_tw, 3), dtype=np.float32)
canvas_cache["scratch_src"] = _ss
if _sa is None or _sa.shape[0] < max_th or _sa.shape[1] < max_tw:
_sa = np.empty((max_th, max_tw), dtype=np.float32)
canvas_cache["scratch_alpha"] = _sa
_scratch = (_ss, _sa)
blended_tiles = 0
for tile_bgr, H_use, paste in zip(tiles_bgr_c, H_use_list, pastes):
if H_use is None or paste is None:
@@ -603,13 +662,13 @@ class Stitcher:
wkey = (th, tw, blend_margin)
if wkey not in weight_maps:
weight_maps[wkey] = self._tile_weight_map(th, tw, blend_margin)
self._blend_tile_inplace(blend_buf, wsum, cov, tile_bgr, weight_maps[wkey], y0, x0)
self._blend_tile_inplace(blend_buf, wsum, cov, tile_bgr, weight_maps[wkey], y0, x0, _scratch)
blended_tiles += 1
ref_wkey = (h0c, w0c, blend_margin)
if ref_wkey not in weight_maps:
weight_maps[ref_wkey] = self._tile_weight_map(h0c, w0c, blend_margin)
self._blend_tile_inplace(blend_buf, wsum, cov, t_ref_bgr, weight_maps[ref_wkey], ref_y0, ref_x0)
self._blend_tile_inplace(blend_buf, wsum, cov, t_ref_bgr, weight_maps[ref_wkey], ref_y0, ref_x0, _scratch)
t_after_blend = cv2.getTickCount() / cv2.getTickFrequency() if do_profile else 0.0
crop_rect = canvas_cache.get("crop_rect")
@@ -618,9 +677,16 @@ class Stitcher:
canvas_cache["crop_rect"] = crop_rect
if crop_rect is not None:
y0, y1, x0, x1 = crop_rect
stitched = np.clip(blend_buf[y0:y1, x0:x1], 0, 255).astype(np.uint8)
roi = blend_buf[y0:y1, x0:x1]
else:
stitched = np.clip(blend_buf, 0, 255).astype(np.uint8)
roi = blend_buf
rh, rw = roi.shape[:2]
stitch_buf = canvas_cache.get("stitch_buf")
if stitch_buf is None or stitch_buf.shape[0] != rh or stitch_buf.shape[1] != rw:
stitch_buf = np.empty((rh, rw, 3), dtype=np.uint8)
canvas_cache["stitch_buf"] = stitch_buf
cv2.convertScaleAbs(roi, dst=stitch_buf)
stitched = stitch_buf
t_after_finalize = cv2.getTickCount() / cv2.getTickFrequency() if do_profile else 0.0
if do_profile: