fix: sync DB sessions in Celery, letterbox YuNet, dedupe detections
- Rewrite all vision tasks to use sync psycopg2 sessions instead of asyncpg — fixes 'another operation in progress' and event loop errors when Celery forks workers sharing the async connection pool - Letterbox-pad images to exactly 640x640 for YuNet face detector (was crashing on non-square thumbnails) - Deduplicate object detections per label per photo — keep highest confidence only to avoid photo_tags PK violation on multiple detections of the same class - Add all queues (-Q default,high,low,vision) to worker command Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -68,7 +68,7 @@ class YuNetSFaceProcessor(FaceProcessor):
|
||||
def process(self, image: np.ndarray) -> list[FaceDetection]:
|
||||
orig_h, orig_w = image.shape[:2]
|
||||
|
||||
# Scale image for YuNet (expects fixed input size)
|
||||
# Scale + letterbox to exactly 640x640 (YuNet fixed input)
|
||||
scale = min(_YUNET_INPUT_SIZE / orig_w, _YUNET_INPUT_SIZE / orig_h)
|
||||
new_w = int(orig_w * scale)
|
||||
new_h = int(orig_h * scale)
|
||||
@@ -79,12 +79,17 @@ class YuNetSFaceProcessor(FaceProcessor):
|
||||
dtype=np.uint8,
|
||||
)
|
||||
|
||||
# YuNet expects BGR, uint8, NHWC
|
||||
bgr = resized[:, :, ::-1].copy()
|
||||
# Letterbox pad to 640x640
|
||||
canvas = np.full((_YUNET_INPUT_SIZE, _YUNET_INPUT_SIZE, 3), 128, dtype=np.uint8)
|
||||
pad_y = (_YUNET_INPUT_SIZE - new_h) // 2
|
||||
pad_x = (_YUNET_INPUT_SIZE - new_w) // 2
|
||||
canvas[pad_y:pad_y + new_h, pad_x:pad_x + new_w] = resized
|
||||
|
||||
# YuNet expects BGR
|
||||
bgr = canvas[:, :, ::-1].copy()
|
||||
|
||||
# Run detection
|
||||
det_input = self._detector.get_inputs()[0]
|
||||
# YuNet uses dynamic input — reshape
|
||||
blob = bgr.astype(np.float32)[np.newaxis] # (1, H, W, 3)
|
||||
# Some YuNet ONNX exports expect (1, 3, H, W)
|
||||
if det_input.shape and len(det_input.shape) == 4 and det_input.shape[1] == 3:
|
||||
@@ -109,11 +114,11 @@ class YuNetSFaceProcessor(FaceProcessor):
|
||||
if face_size < self._min_face_size:
|
||||
continue
|
||||
|
||||
# Rescale to original image coords
|
||||
x1 = x / scale
|
||||
y1 = y / scale
|
||||
x2 = (x + w) / scale
|
||||
y2 = (y + h) / scale
|
||||
# Remove letterbox padding and rescale to original image coords
|
||||
x1 = (x - pad_x) / scale
|
||||
y1 = (y - pad_y) / scale
|
||||
x2 = (x + w - pad_x) / scale
|
||||
y2 = (y + h - pad_y) / scale
|
||||
|
||||
bbox = [
|
||||
max(0, x1 / orig_w),
|
||||
@@ -124,7 +129,9 @@ class YuNetSFaceProcessor(FaceProcessor):
|
||||
|
||||
# Extract landmarks (5 points) for alignment
|
||||
if len(det) >= 15:
|
||||
landmarks = det[5:15].reshape(5, 2) / scale
|
||||
landmarks = det[5:15].reshape(5, 2)
|
||||
landmarks[:, 0] = (landmarks[:, 0] - pad_x) / scale
|
||||
landmarks[:, 1] = (landmarks[:, 1] - pad_y) / scale
|
||||
else:
|
||||
# Fallback: no landmarks, skip recognition
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user