region matched
This commit is contained in:
parent
3f935a59bb
commit
c8695c1216
1 changed files with 228 additions and 123 deletions
|
|
@ -6,30 +6,36 @@ No complex visual matching - just track displacement and append strips.
|
|||
Continuous alignment correction for gear slippage compensation.
|
||||
|
||||
FIXES:
|
||||
- Row-start alignment now compares with TRANSITION STRIPS, not old row 1
|
||||
- Correct X position tracking after row transition
|
||||
- Fixed LEFT scan stop condition
|
||||
- Y comparison: Compare frame's BOTTOM with ROW 1's TOP (at Y=transition_height)
|
||||
- X comparison (LEFT scan): Compare frame's RIGHT with transition strip's LEFT edge
|
||||
- row_start_y now positions row 2 so its bottom overlaps with row 1's top
|
||||
- _detect_strip_alignment now uses SAME bounding boxes as UI draws
|
||||
|
||||
DEBUG BORDER COLORS (for debugging row 2 placement):
|
||||
=================================================
|
||||
In _detect_row_start_alignment():
|
||||
- WHITE (thick): Outline of where TRANSITION STRIPS are in mosaic (X=x_offset, Y=0 to transition_height)
|
||||
- BLUE line: Y=transition_height (boundary between transition strips and old row 1)
|
||||
- CYAN: Mosaic region used for Y alignment comparison (bottom of transition strips)
|
||||
- WHITE (thick): Outline of TRANSITION STRIPS (X=x_offset, Y=0 to transition_height)
|
||||
- BLUE line: Y=transition_height (boundary between transition strips and row 1)
|
||||
- CYAN: Y comparison region - TOP of ROW 1 (just below blue line)
|
||||
- YELLOW: X comparison region - at expected_x position
|
||||
- MAGENTA: Where the new frame is EXPECTED to be placed
|
||||
- YELLOW: Mosaic region used for X alignment comparison (edge of transition strips)
|
||||
|
||||
In _detect_strip_alignment():
|
||||
- Uses SAME regions as _detect_row_start_alignment for consistency
|
||||
- For LEFT scan: Y = CYAN region (frame top vs row 1 top)
|
||||
- For LEFT scan: X = YELLOW region (frame right vs mosaic at expected_x)
|
||||
|
||||
In _blend_horizontal_at_y (append_left):
|
||||
- RED (thick): Where strip WOULD have been placed (ORIGINAL position before alignment)
|
||||
- GREEN: Where strip was ACTUALLY placed (ADJUSTED position after alignment)
|
||||
- RED (thick): Where strip WOULD have been placed (ORIGINAL position)
|
||||
- GREEN: Where strip was ACTUALLY placed (ADJUSTED position)
|
||||
|
||||
MOSAIC LAYOUT AFTER DOWN TRANSITION:
|
||||
====================================
|
||||
Y=0 to Y=transition_height: TRANSITION STRIPS (at X=x_offset to X=x_offset+fw)
|
||||
Y=transition_height to Y=mh: OLD ROW 1 (shifted down, at X=0 to X=mw)
|
||||
Y=0 to Y=transition_height: TRANSITION STRIPS (at X=x_offset)
|
||||
Y=transition_height to Y=mh: ROW 1 (shifted down)
|
||||
|
||||
Where x_offset = mosaic_width - initial_frame_width
|
||||
And transition_height = mosaic_height - initial_frame_height
|
||||
Row 2 placement: Y = transition_height - fh + overlap
|
||||
(so row 2's bottom overlaps with row 1's top)
|
||||
"""
|
||||
|
||||
import cv2
|
||||
|
|
@ -212,11 +218,11 @@ class StitchingScanner:
|
|||
Detect alignment at the START of a new row.
|
||||
|
||||
After a DOWN transition with prepend, the mosaic layout is:
|
||||
- Y=0 to Y≈transition_height: TRANSITION STRIPS (placed at x_offset)
|
||||
- Y≈transition_height to Y=mosaic_height: OLD row 1 content (shifted down)
|
||||
- Y=0 to Y=transition_height: TRANSITION STRIPS (at X=x_offset)
|
||||
- Y=transition_height to Y=mh: OLD ROW 1 (shifted down)
|
||||
|
||||
The current frame should overlap with the TRANSITION STRIPS, not old row 1.
|
||||
The camera is at the X position where transition strips were placed.
|
||||
For Y alignment: Compare frame's BOTTOM with ROW 1's TOP (at Y=transition_height)
|
||||
For X alignment (LEFT scan): Compare frame's RIGHT with transition strip's LEFT edge
|
||||
"""
|
||||
offset = AlignmentOffset()
|
||||
|
||||
|
|
@ -232,16 +238,14 @@ class StitchingScanner:
|
|||
x_offset = max(0, mw - self.state.mosaic_init_width)
|
||||
|
||||
# Transition height is how much was added during DOWN transition
|
||||
# transition_height = mh - original_height_before_transition
|
||||
# But since row 1 was just fh tall, transition_height = mh - fh
|
||||
transition_height = mh - fh
|
||||
|
||||
self.log(f" Row-start alignment: mosaic {mw}x{mh}, frame {fw}x{fh}")
|
||||
self.log(f" Transition strips at: X={x_offset}, Y=0 to Y={transition_height}")
|
||||
self.log(f" Old row 1 shifted to: Y={transition_height} to Y={mh}")
|
||||
self.log(f" Old row 1 at: Y={transition_height} to Y={mh}")
|
||||
|
||||
# ========== DEBUG: Draw borders showing layout ==========
|
||||
# WHITE border: Where TRANSITION STRIPS are (this is what we should compare with)
|
||||
# WHITE border: Where TRANSITION STRIPS are
|
||||
cv2.rectangle(self.mosaic,
|
||||
(x_offset, 0),
|
||||
(min(x_offset + fw, mw), transition_height),
|
||||
|
|
@ -253,81 +257,84 @@ class StitchingScanner:
|
|||
self.log(f" DEBUG: BLUE line at Y={transition_height} (transition/row1 boundary)")
|
||||
|
||||
vertical_overlap = min(200, fh // 3)
|
||||
horizontal_overlap = min(200, fw // 3)
|
||||
min_overlap = 50
|
||||
|
||||
# =============================================
|
||||
# Step 1: Detect Y alignment
|
||||
# =============================================
|
||||
# Compare frame's TOP with the BOTTOM of transition strips
|
||||
# Frame's top portion overlaps with mosaic's transition bottom
|
||||
# Compare frame's BOTTOM with ROW 1's TOP (at Y=transition_height)
|
||||
# Frame's bottom should overlap with the area just below the blue line
|
||||
#
|
||||
# Transition strips are at Y=0 to Y=transition_height
|
||||
# So transition bottom is around Y=(transition_height - overlap) to Y=transition_height
|
||||
# Row 1's top is at Y=transition_height
|
||||
# So compare mosaic[transition_height : transition_height+overlap]
|
||||
|
||||
transition_compare_y_start = transition_height
|
||||
transition_compare_y_end = max(0, transition_height + vertical_overlap)
|
||||
row1_top_start = transition_height
|
||||
row1_top_end = min(transition_height + vertical_overlap, mh)
|
||||
|
||||
# Frame's TOP should overlap with transition BOTTOM
|
||||
frame_top = frame[:vertical_overlap, :]
|
||||
# Frame's BOTTOM portion (will overlap with row 1's top)
|
||||
frame_bottom = frame[fh - vertical_overlap:fh, :]
|
||||
|
||||
# Get the X range - centered on where transition strips are
|
||||
x_end = x_offset
|
||||
x_start = min(x_offset - fw, mw)
|
||||
x_start = x_offset
|
||||
x_end = min(x_offset + fw, mw)
|
||||
compare_width = x_end - x_start
|
||||
|
||||
if transition_compare_y_start >= 0 and compare_width >= min_overlap:
|
||||
# Mosaic region: bottom of transition strips
|
||||
mosaic_transition_bottom = self.mosaic[transition_compare_y_start:transition_compare_y_end,
|
||||
x_start:x_end]
|
||||
if row1_top_end > row1_top_start and compare_width >= min_overlap:
|
||||
# Mosaic region: TOP of row 1 (just below the transition)
|
||||
mosaic_row1_top = self.mosaic[row1_top_start:row1_top_end, x_start:x_end]
|
||||
|
||||
# Frame region: top portion (what overlaps with transition)
|
||||
frame_compare = frame_top[:, :compare_width]
|
||||
# Frame region: bottom portion
|
||||
frame_compare = frame_bottom[:, :compare_width]
|
||||
|
||||
# ========== DEBUG: Save frame with comparison region marked ==========
|
||||
debug_frame = frame.copy()
|
||||
cv2.rectangle(debug_frame, (0, 0), (compare_width, vertical_overlap),
|
||||
(255, 255, 0), 2) # CYAN - frame's top region used for Y comparison
|
||||
self.log(f" DEBUG: Frame Y comparison region: X=0:{compare_width}, Y=0:{vertical_overlap}")
|
||||
cv2.rectangle(debug_frame, (0, fh - vertical_overlap), (compare_width, fh),
|
||||
(255, 255, 0), 2) # CYAN - frame's bottom region used for Y comparison
|
||||
self.log(f" DEBUG: Frame Y comparison region: X=0:{compare_width}, Y={fh - vertical_overlap}:{fh}")
|
||||
try:
|
||||
cv2.imwrite('/tmp/debug_frame_row2_start.png', debug_frame)
|
||||
except:
|
||||
pass
|
||||
|
||||
# ========== DEBUG: Draw CYAN border on mosaic for Y comparison region ==========
|
||||
# CYAN at TOP of row 1 (just below blue line)
|
||||
cv2.rectangle(self.mosaic,
|
||||
(x_start, transition_compare_y_start),
|
||||
(x_end, transition_compare_y_end),
|
||||
(x_start, row1_top_start),
|
||||
(x_end, row1_top_end),
|
||||
(255, 255, 0), 2) # CYAN - mosaic comparison region for Y
|
||||
self.log(f" DEBUG: CYAN border - mosaic Y comparison region X={x_start}:{x_end}, Y={transition_compare_y_start}:{transition_compare_y_end}")
|
||||
self.log(f" DEBUG: CYAN border - mosaic Y comparison region X={x_start}:{x_end}, Y={row1_top_start}:{row1_top_end}")
|
||||
|
||||
# MAGENTA border: Where frame is EXPECTED to be placed (at transition position)
|
||||
# MAGENTA border: Where frame is EXPECTED to be placed
|
||||
expected_y_start = transition_height - fh + vertical_overlap # Frame overlaps with row 1
|
||||
expected_y_start = max(0, expected_y_start)
|
||||
cv2.rectangle(self.mosaic,
|
||||
(x_start, 0),
|
||||
(x_end, fh),
|
||||
(x_start, expected_y_start),
|
||||
(x_end, expected_y_start + fh),
|
||||
(255, 0, 255), 2) # MAGENTA - expected frame position
|
||||
self.log(f" DEBUG: MAGENTA border - expected frame position X={x_start}:{x_end}, Y=0:{fh}")
|
||||
self.log(f" DEBUG: MAGENTA border - expected frame position X={x_start}:{x_end}, Y={expected_y_start}:{expected_y_start + fh}")
|
||||
|
||||
min_w = min(frame_compare.shape[1], mosaic_transition_bottom.shape[1])
|
||||
min_h = min(frame_compare.shape[0], mosaic_transition_bottom.shape[0])
|
||||
min_w = min(frame_compare.shape[1], mosaic_row1_top.shape[1])
|
||||
min_h = min(frame_compare.shape[0], mosaic_row1_top.shape[0])
|
||||
|
||||
if min_w >= min_overlap and min_h >= min_overlap:
|
||||
frame_compare = frame_compare[:min_h, :min_w]
|
||||
mosaic_transition_bottom = mosaic_transition_bottom[:min_h, :min_w]
|
||||
mosaic_row1_top = mosaic_row1_top[:min_h, :min_w]
|
||||
|
||||
# ========== DEBUG: Save the comparison regions as images ==========
|
||||
try:
|
||||
cv2.imwrite('/tmp/debug_frame_top_region.png', frame_compare)
|
||||
cv2.imwrite('/tmp/debug_mosaic_transition_region.png', mosaic_transition_bottom)
|
||||
cv2.imwrite('/tmp/debug_frame_bottom_region.png', frame_compare)
|
||||
cv2.imwrite('/tmp/debug_mosaic_row1top_region.png', mosaic_row1_top)
|
||||
self.log(f" DEBUG: Saved comparison regions to /tmp/debug_*.png")
|
||||
except:
|
||||
pass
|
||||
|
||||
# Detect displacement
|
||||
dx_v, dy_v, conf_v = self._detect_displacement_with_confidence(
|
||||
mosaic_transition_bottom, frame_compare)
|
||||
mosaic_row1_top, frame_compare)
|
||||
|
||||
self.log(f" Row-start Y alignment: dx={dx_v:.1f}, dy={dy_v:.1f}, conf={conf_v:.3f}")
|
||||
self.log(f" Compared frame[0:{vertical_overlap}] with mosaic[{transition_compare_y_start}:{transition_compare_y_end}]")
|
||||
self.log(f" Compared frame[{fh - vertical_overlap}:{fh}] with mosaic[{row1_top_start}:{row1_top_end}]")
|
||||
|
||||
if conf_v > 0.1:
|
||||
offset.y_offset = dy_v
|
||||
|
|
@ -336,29 +343,34 @@ class StitchingScanner:
|
|||
# =============================================
|
||||
# Step 2: Detect X alignment
|
||||
# =============================================
|
||||
horizontal_overlap = min(200, fw // 3)
|
||||
|
||||
if direction == ScanDirection.LEFT:
|
||||
# For LEFT scan: frame starts at the transition X position
|
||||
# Compare frame's RIGHT edge with mosaic's transition strip RIGHT edge
|
||||
# For LEFT scan: frame will move LEFT
|
||||
# Frame's RIGHT edge will overlap with existing content's LEFT edge
|
||||
# The LEFT edge of transition strips is at X=x_offset
|
||||
# Compare frame's RIGHT with mosaic's LEFT edge of transition strips
|
||||
|
||||
transition_x_start = x_offset
|
||||
transition_x_end = max(x_offset, x_offset + horizontal_overlap)
|
||||
# Mosaic region: LEFT edge of transition strips
|
||||
mosaic_x_start = x_offset
|
||||
mosaic_x_end = min(x_offset + horizontal_overlap, mw)
|
||||
|
||||
# Y range: within the transition strip area
|
||||
y_start = 0
|
||||
y_end = min(transition_height, fh)
|
||||
# Y range: within the transition strip area AND row 1 top
|
||||
y_start = max(0, transition_height - fh // 2)
|
||||
y_end = min(transition_height + fh // 2, mh)
|
||||
|
||||
if transition_x_end - transition_x_start >= min_overlap:
|
||||
mosaic_edge = self.mosaic[y_start:y_end, transition_x_start:transition_x_end]
|
||||
frame_edge = frame[:y_end, fw - (transition_x_end - transition_x_start):fw]
|
||||
if mosaic_x_end > mosaic_x_start and y_end > y_start:
|
||||
mosaic_edge = self.mosaic[y_start:y_end, mosaic_x_start:mosaic_x_end]
|
||||
|
||||
# Frame's RIGHT edge
|
||||
frame_edge = frame[:min(y_end - y_start, fh), fw - (mosaic_x_end - mosaic_x_start):fw]
|
||||
|
||||
# ========== DEBUG: Draw YELLOW border for X comparison region ==========
|
||||
# YELLOW on LEFT side of transition strips
|
||||
cv2.rectangle(self.mosaic,
|
||||
(transition_x_start, y_start),
|
||||
(transition_x_end, y_end),
|
||||
(mosaic_x_start, y_start),
|
||||
(mosaic_x_end, y_end),
|
||||
(0, 255, 255), 2) # YELLOW - mosaic comparison region for X
|
||||
self.log(f" DEBUG: YELLOW border - mosaic X comparison region X={transition_x_start}:{transition_x_end}, Y={y_start}:{y_end}")
|
||||
self.log(f" DEBUG: YELLOW border - mosaic X comparison region X={mosaic_x_start}:{mosaic_x_end}, Y={y_start}:{y_end}")
|
||||
|
||||
min_h = min(mosaic_edge.shape[0], frame_edge.shape[0])
|
||||
min_w = min(mosaic_edge.shape[1], frame_edge.shape[1])
|
||||
|
|
@ -377,22 +389,23 @@ class StitchingScanner:
|
|||
if conf_h > offset.confidence:
|
||||
offset.confidence = conf_h
|
||||
else:
|
||||
# For RIGHT scan at row start (similar logic but for left edge)
|
||||
transition_x_start = x_offset
|
||||
transition_x_end = min(x_offset + horizontal_overlap, mw)
|
||||
# For RIGHT scan at row start
|
||||
# Frame's LEFT edge will overlap with existing content's RIGHT edge
|
||||
mosaic_x_end = min(x_offset + fw, mw)
|
||||
mosaic_x_start = max(mosaic_x_end - horizontal_overlap, x_offset)
|
||||
|
||||
y_start = 0
|
||||
y_end = min(transition_height, fh)
|
||||
y_start = max(0, transition_height - fh // 2)
|
||||
y_end = min(transition_height + fh // 2, mh)
|
||||
|
||||
if transition_x_end - transition_x_start >= min_overlap:
|
||||
mosaic_edge = self.mosaic[y_start:y_end, transition_x_start:transition_x_end]
|
||||
frame_edge = frame[:y_end, :transition_x_end - transition_x_start]
|
||||
if mosaic_x_end > mosaic_x_start and y_end > y_start:
|
||||
mosaic_edge = self.mosaic[y_start:y_end, mosaic_x_start:mosaic_x_end]
|
||||
frame_edge = frame[:min(y_end - y_start, fh), :mosaic_x_end - mosaic_x_start]
|
||||
|
||||
cv2.rectangle(self.mosaic,
|
||||
(transition_x_start, y_start),
|
||||
(transition_x_end, y_end),
|
||||
(mosaic_x_start, y_start),
|
||||
(mosaic_x_end, y_end),
|
||||
(0, 255, 255), 2) # YELLOW
|
||||
self.log(f" DEBUG: YELLOW border - mosaic X comparison region X={transition_x_start}:{transition_x_end}, Y={y_start}:{y_end}")
|
||||
self.log(f" DEBUG: YELLOW border - mosaic X comparison region X={mosaic_x_start}:{mosaic_x_end}, Y={y_start}:{y_end}")
|
||||
|
||||
min_h = min(mosaic_edge.shape[0], frame_edge.shape[0])
|
||||
min_w = min(mosaic_edge.shape[1], frame_edge.shape[1])
|
||||
|
|
@ -434,6 +447,7 @@ class StitchingScanner:
|
|||
with the expected overlap region of the mosaic.
|
||||
|
||||
This provides continuous correction for gear slippage during scanning.
|
||||
Uses the SAME bounding boxes as drawn in the UI for consistency.
|
||||
|
||||
Args:
|
||||
frame: Current camera frame
|
||||
|
|
@ -453,67 +467,147 @@ class StitchingScanner:
|
|||
mh, mw = self.mosaic.shape[:2]
|
||||
fh, fw = frame.shape[:2]
|
||||
|
||||
# Clamp expected positions
|
||||
expected_y = max(0, min(expected_y, mh - fh))
|
||||
expected_x = max(0, min(expected_x, mw - fw))
|
||||
# Calculate key positions - SAME as _detect_row_start_alignment
|
||||
x_offset = max(0, mw - self.state.mosaic_init_width)
|
||||
transition_height = mh - fh
|
||||
|
||||
# Increased overlap for better detection
|
||||
max_overlap = 250 # Increased from 200
|
||||
min_overlap = 40 # Increased from 30
|
||||
# Overlap sizes - SAME as UI
|
||||
vertical_overlap = min(200, fh // 3)
|
||||
horizontal_overlap = min(200, fw // 3)
|
||||
min_overlap = 50
|
||||
|
||||
if direction == ScanDirection.RIGHT:
|
||||
# We're appending to the right
|
||||
# Row 1: Appending to the right
|
||||
# Compare left portion of frame with right edge of mosaic
|
||||
overlap_width = min(fw // 2, mw - expected_x, max_overlap)
|
||||
overlap_width = min(fw // 2, mw, horizontal_overlap)
|
||||
|
||||
if overlap_width < min_overlap:
|
||||
return offset
|
||||
|
||||
# Y region: full frame height at expected_y
|
||||
y_start = max(0, expected_y)
|
||||
y_end = min(expected_y + fh, mh)
|
||||
|
||||
# Extract regions
|
||||
mosaic_region = self.mosaic[expected_y:expected_y + fh, mw - overlap_width:mw]
|
||||
frame_region = frame[:, :overlap_width]
|
||||
mosaic_region = self.mosaic[y_start:y_end, mw - overlap_width:mw]
|
||||
frame_region = frame[:y_end - y_start, :overlap_width]
|
||||
|
||||
elif direction == ScanDirection.LEFT:
|
||||
# We're placing within existing mosaic, moving left
|
||||
# Compare right portion of frame with mosaic at expected position
|
||||
overlap_width = min(fw // 2, mw - expected_x, max_overlap)
|
||||
# Row 2+: Scanning left within existing mosaic
|
||||
# Use SAME regions as UI (CYAN for Y, YELLOW for X)
|
||||
|
||||
if overlap_width < min_overlap:
|
||||
return offset
|
||||
# =============================================
|
||||
# Y comparison: CYAN region
|
||||
# Compare frame's TOP with ROW 1's TOP (at Y=transition_height)
|
||||
# =============================================
|
||||
y_compare_start = transition_height
|
||||
y_compare_end = min(transition_height + vertical_overlap, mh)
|
||||
|
||||
# The frame's right edge should align with mosaic at expected_x + fw
|
||||
mosaic_x_end = min(expected_x + fw, mw)
|
||||
mosaic_x_start = max(mosaic_x_end - overlap_width, 0)
|
||||
actual_overlap = mosaic_x_end - mosaic_x_start
|
||||
# X range for Y comparison - at the current expected position
|
||||
x_start = max(0, int(expected_x))
|
||||
x_end = min(int(expected_x) + fw, mw)
|
||||
compare_width = x_end - x_start
|
||||
|
||||
if actual_overlap < min_overlap:
|
||||
return offset
|
||||
if y_compare_end > y_compare_start and compare_width >= min_overlap:
|
||||
# Frame's TOP portion
|
||||
frame_top = frame[:vertical_overlap, :compare_width]
|
||||
|
||||
mosaic_region = self.mosaic[expected_y:expected_y + fh, mosaic_x_start:mosaic_x_end]
|
||||
frame_region = frame[:, fw - actual_overlap:]
|
||||
# Mosaic region: TOP of row 1 (CYAN region)
|
||||
mosaic_y_region = self.mosaic[y_compare_start:y_compare_end, x_start:x_end]
|
||||
|
||||
# Match sizes
|
||||
min_w = min(frame_top.shape[1], mosaic_y_region.shape[1])
|
||||
min_h = min(frame_top.shape[0], mosaic_y_region.shape[0])
|
||||
|
||||
if min_w >= min_overlap and min_h >= min_overlap:
|
||||
frame_compare = frame_top[:min_h, :min_w]
|
||||
mosaic_compare = mosaic_y_region[:min_h, :min_w]
|
||||
|
||||
dx_v, dy_v, conf_v = self._detect_displacement_with_confidence(
|
||||
mosaic_compare, frame_compare)
|
||||
|
||||
if conf_v > 0.1:
|
||||
offset.y_offset = dy_v
|
||||
offset.confidence = conf_v
|
||||
|
||||
# =============================================
|
||||
# X comparison: YELLOW region
|
||||
# Compare frame's RIGHT with mosaic content at expected_x + fw
|
||||
# =============================================
|
||||
# For LEFT scan: frame's RIGHT overlaps with mosaic content to the right
|
||||
mosaic_x_start = min(int(expected_x) + fw - horizontal_overlap, mw - horizontal_overlap)
|
||||
mosaic_x_start = max(0, mosaic_x_start)
|
||||
mosaic_x_end = min(mosaic_x_start + horizontal_overlap, mw)
|
||||
|
||||
# Y range spans transition area AND row 1
|
||||
y_start = max(0, transition_height - fh // 2)
|
||||
y_end = min(transition_height + fh // 2, mh)
|
||||
|
||||
if mosaic_x_end > mosaic_x_start and y_end > y_start:
|
||||
mosaic_x_region = self.mosaic[y_start:y_end, mosaic_x_start:mosaic_x_end]
|
||||
|
||||
# Frame's RIGHT edge
|
||||
frame_right_width = mosaic_x_end - mosaic_x_start
|
||||
frame_x_region = frame[:min(y_end - y_start, fh), fw - frame_right_width:fw]
|
||||
|
||||
min_h = min(mosaic_x_region.shape[0], frame_x_region.shape[0])
|
||||
min_w = min(mosaic_x_region.shape[1], frame_x_region.shape[1])
|
||||
|
||||
if min_h >= min_overlap and min_w >= min_overlap:
|
||||
mosaic_x_region = mosaic_x_region[:min_h, :min_w]
|
||||
frame_x_region = frame_x_region[:min_h, :min_w]
|
||||
|
||||
dx_h, dy_h, conf_h = self._detect_displacement_with_confidence(
|
||||
mosaic_x_region, frame_x_region)
|
||||
|
||||
if conf_h > 0.1:
|
||||
offset.x_offset = -dx_h
|
||||
if conf_h > offset.confidence:
|
||||
offset.confidence = conf_h
|
||||
|
||||
# Sanity check - reject large displacements
|
||||
max_adjust = 80
|
||||
if abs(offset.x_offset) > max_adjust:
|
||||
offset.x_offset = max(-max_adjust, min(max_adjust, offset.x_offset))
|
||||
if abs(offset.y_offset) > max_adjust:
|
||||
offset.y_offset = max(-max_adjust, min(max_adjust, offset.y_offset))
|
||||
|
||||
offset.valid = offset.confidence > 0.1
|
||||
|
||||
if offset.valid:
|
||||
self.log(f" Strip alignment (LEFT): X={offset.x_offset:.1f}, Y={offset.y_offset:.1f}, conf={offset.confidence:.3f}")
|
||||
|
||||
return offset
|
||||
|
||||
elif direction == ScanDirection.DOWN:
|
||||
# We're appending below
|
||||
# Compare top portion of frame with bottom edge of mosaic
|
||||
overlap_height = min(fh // 2, mh - expected_y, max_overlap)
|
||||
# Vertical transition: appending above (prepend)
|
||||
# Compare top portion of frame with bottom edge of existing content
|
||||
overlap_height = min(fh // 2, mh, vertical_overlap)
|
||||
|
||||
if overlap_height < min_overlap:
|
||||
return offset
|
||||
|
||||
mosaic_region = self.mosaic[mh - overlap_height:mh, expected_x:expected_x + fw]
|
||||
frame_region = frame[:overlap_height, :]
|
||||
# X position at x_offset (where transition strips go)
|
||||
x_start = x_offset
|
||||
x_end = min(x_offset + fw, mw)
|
||||
|
||||
mosaic_region = self.mosaic[mh - overlap_height:mh, x_start:x_end]
|
||||
frame_region = frame[:overlap_height, :x_end - x_start]
|
||||
|
||||
else: # UP
|
||||
# Compare bottom portion of frame with top edge of mosaic
|
||||
overlap_height = min(fh // 2, expected_y, max_overlap)
|
||||
overlap_height = min(fh // 2, mh, vertical_overlap)
|
||||
|
||||
if overlap_height < min_overlap:
|
||||
return offset
|
||||
|
||||
mosaic_region = self.mosaic[:overlap_height, expected_x:expected_x + fw]
|
||||
frame_region = frame[fh - overlap_height:, :]
|
||||
x_start = x_offset
|
||||
x_end = min(x_offset + fw, mw)
|
||||
|
||||
# Ensure regions have the same size
|
||||
mosaic_region = self.mosaic[:overlap_height, x_start:x_end]
|
||||
frame_region = frame[fh - overlap_height:, :x_end - x_start]
|
||||
|
||||
# Ensure regions have the same size (for RIGHT, DOWN, UP)
|
||||
min_h = min(mosaic_region.shape[0], frame_region.shape[0])
|
||||
min_w = min(mosaic_region.shape[1], frame_region.shape[1])
|
||||
|
||||
|
|
@ -528,15 +622,16 @@ class StitchingScanner:
|
|||
dx, dy, confidence = self._detect_displacement_with_confidence(mosaic_region, frame_region)
|
||||
|
||||
# Sanity check - reject large displacements
|
||||
max_adjust = 500 # Max pixels to adjust
|
||||
max_adjust = 80
|
||||
if abs(dx) > max_adjust or abs(dy) > max_adjust:
|
||||
self.log(f"Strip alignment: displacement too large ({dx:.1f}, {dy:.1f}), ignoring")
|
||||
return offset
|
||||
self.log(f"Strip alignment: displacement too large ({dx:.1f}, {dy:.1f}), limiting")
|
||||
dx = max(-max_adjust, min(max_adjust, dx))
|
||||
dy = max(-max_adjust, min(max_adjust, dy))
|
||||
|
||||
offset.x_offset = dx * -1
|
||||
offset.x_offset = dx
|
||||
offset.y_offset = dy
|
||||
offset.confidence = confidence
|
||||
offset.valid = confidence > 0.1 # Require minimum confidence
|
||||
offset.valid = confidence > 0.1
|
||||
|
||||
if offset.valid:
|
||||
self.log(f" Strip alignment: X={dx:.1f}, Y={dy:.1f}, conf={confidence:.3f}")
|
||||
|
|
@ -1301,22 +1396,32 @@ class StitchingScanner:
|
|||
self.log(f"Row transition complete: {abs(total_y):.1f}px")
|
||||
self.log(f"Alignment after row transition: X={self._cumulative_align_x:.1f}, Y={self._cumulative_align_y:.1f}")
|
||||
|
||||
# Calculate the Y position in the mosaic where the new row starts
|
||||
# Since we use append_below=False during DOWN movement, new content
|
||||
# is PREPENDED to the TOP of the mosaic. So the new row starts at Y=0
|
||||
self._row_start_y = 0 # New row is at the TOP after prepending
|
||||
# Calculate the Y position in the mosaic where the new row should be placed
|
||||
# After prepending, the layout is:
|
||||
# Y=0 to Y=transition_height: transition strips
|
||||
# Y=transition_height to Y=mh: old row 1 (shifted down)
|
||||
#
|
||||
# Row 2 should be placed so its BOTTOM overlaps with row 1's TOP
|
||||
# transition_height = mh - fh (height added during transition)
|
||||
# overlap_pixels = h * row_overlap
|
||||
#
|
||||
# Row 2 Y position: transition_height - fh + overlap_pixels
|
||||
# This puts row 2's bottom at: (transition_height - fh + overlap) + fh = transition_height + overlap
|
||||
# Which overlaps with row 1's top at Y=transition_height
|
||||
|
||||
overlap_pixels = int(h * self.config.row_overlap)
|
||||
transition_height = self.state.mosaic_height - h
|
||||
self._row_start_y = max(0, transition_height - h + overlap_pixels)
|
||||
|
||||
# Calculate the X position where transition strips were placed
|
||||
# This is where the camera is now (ready to start row 2)
|
||||
x_offset = max(0, self.state.mosaic_width - self.state.mosaic_init_width)
|
||||
|
||||
self.log(f"New row Y position: {self._row_start_y} (mosaic height: {self.state.mosaic_height})")
|
||||
self.log(f"New row Y position: {self._row_start_y} (transition_height={transition_height}, overlap={overlap_pixels})")
|
||||
self.log(f"New row X position: {x_offset} (transition strip location)")
|
||||
|
||||
with self._state_lock:
|
||||
self.state.current_y = 0
|
||||
# Set current_x to the x_offset where transition strips are
|
||||
# This is where row 2 will START (then scan LEFT from here)
|
||||
self.state.current_x = x_offset
|
||||
|
||||
self.motion.send_command('s')
|
||||
|
|
|
|||
Loading…
Reference in a new issue