MaskWork::maskPanos | script cv2.error: OpenCV(4.10.0) /io/opencv/modules/imgproc/src/color.cpp:196:
RCA: MaskWork::maskPanos | cv2.error cvtColor assertion failed
Error Log#
MaskWork::maskPanos | script cv2.error: OpenCV(4.10.0) /io/opencv/modules/imgproc/src/color.cpp:196: error: (-215:Assertion failed) !_src.empty() in function 'cvtColor'
Impact#
- Service:
cupixworks-pano-postprocessor-instance - Team: digitaltwinmarine
- 발생 횟수: 3
- 최초 발생: 2026-04-09T21:24:22.601Z
- 최근 발생: 2026-04-09T21:24:22.601Z
Root Cause Summary#
S3에서 pano 이미지 다운로드 시 "The specified key does not exist." 오류가 발생했으나, AwsS3Manager::downloadParallel의 catch 블록이 에러를 로깅만 하고 삼켜버림(swallow). 이로 인해 이미지 파일이 디스크에 존재하지 않는 상태에서 마스킹 파이프라인이 계속 진행됨. Python 스크립트 cupix_inference.py에서 cv2.imread()가 존재하지 않는 파일에 대해 None을 반환했고, null 체크 없이 바로 cv2.cvtColor()를 호출하여 !_src.empty() assertion failure가 발생함.
Technical Analysis#
Code Path#
- Entry point:
pano-postprocessor-service.ts:43—PanoPostprocessorService::init()이 인증 후run()을 호출 - S3 다운로드:
pano-postprocessor-service.ts:117—awsS3Manager.downloadParallel(cpPanos)로 모든 pano 이미지를 병렬 다운로드 - 다운로드 실패 (에러 삼킴):
aws-s3.manager.ts:75-77— S3NoSuchKey에러가 catch 블록에서 로깅만 되고 무시됨
// aws-s3.manager.ts:62-77
try {
// ... download logic
if (cpPano.panoBucketEndpoint) {
await this.downloadByPanoApi(cpPano);
} else {
await this.download(
cpPano.panoBucketRegion!,
cpPano.panoBucketName!,
cpPano.panoBucketKey!,
cpPano.downloadImagePath!
);
}
} catch (err) {
logger.error('AwsS3Manager::downloadParallel | download failed - pano: %s, error: %s',
cpPano.downloadImagePath, (err as Error).message);
// 에러가 삼켜짐 — re-throw 없음, 실패 추적 없음
}
- 마스킹 실행:
pano-postprocessor-service.ts:126— 다운로드 실패 여부와 무관하게maskWork.maskPanos(downloadDir, maskDir, optionPath)호출 - Python 스크립트 실행:
mask-work.ts:29-33—spawn('stdbuf', ['-oL', 'bash', BlurScriptPath])로 face-body-detector 실행
// mask-work.ts:29-33
const process: ChildProcessWithoutNullStreams = spawn('stdbuf', ['-oL', 'bash', BlurScriptPath], {
cwd: path.dirname(BlurScriptPath),
stdio: 'pipe',
env: { INPUT_DIRPATH: inputDir, OUTPUT_DIRPATH: outputDir, OPTION_FILE_PATH: optionPath }
});
- Failure point:
cupix_inference.py:151-152(in-repo 버전 기준; 런타임 버전은 line 334) —cv2.imread()가None을 반환한 후, null 체크 없이cv2.cvtColor()또는check_and_get_computable_image()호출
# cupix_inference.py:151-152 (in-repo older version)
img_original = cv2.imread(image_filepath) # 파일 없으면 None 반환
img_original = check_and_get_computable_image(img_original, model_stride) # None.shape → crash
런타임 face-body-detector v1.2.3 (line 334)에서는 cv2.cvtColor(img_original, cv2.COLOR_BGR2RGB) 호출이 추가되어, None 입력 시 !_src.empty() assertion이 먼저 발생함.
- 기대 동작: S3 다운로드 실패 시 해당 pano를 에러로 마킹하고 마스킹 단계에서 제외
- 실제 동작: 다운로드 실패가 무시되어 존재하지 않는 이미지 파일로 Python 추론 스크립트가 실행되고 crash 발생
Log Evidence#
Datadog 쿼리 1 — Job 실행 타임라인:
service:cupixworks-pano-postprocessor-instance @job.id:1006116
Time: 2026-04-09T21:00:00Z to 2026-04-09T22:00:00Z, sort: timestamp asc
핵심 로그 타임라인:
[21:24:08.404Z] [info] PanoPostprocessorService::init
[21:24:08.405Z] [info] PanoPostprocessorService::authenticate | begin
[21:24:08.456Z] [info] CupixAuth::setSession | session_id: 4e0d5965ca1f9b14c4b96f1af0d07afa32e8d8a1
[21:24:08.457Z] [info] PanoPostprocessorService::run | begin
[21:24:09.737Z] [ERROR] AwsS3Manager::downloadParallel | begin - "The specified key does not exist."
[21:24:10.635Z] [info] PanoPostprocessorService Elapsed Time - dd_step:download_panos&dd_elapsed_time:1136ms
[21:24:22.601Z] [ERROR] MaskWork::maskPanos | script Traceback (most recent call last):
File "/tmp/lib/face-body-detector/cupix_inference.py", line 453, in <module>
detect(args)
File "/tmp/lib/face-body-detector/cupix_inference.py", line 334, in detect
img_original = cv2.cvtColor(img_original, cv2.COLOR_BGR2RGB)
[21:24:22.601Z] [ERROR] MaskWork::maskPanos | script cv2.error: OpenCV(4.10.0) ...color.cpp:196: error: (-215:Assertion failed) !_src.empty() in function 'cvtColor'
[21:24:23.371Z] [ERROR] MaskWork::maskPanos | error code:1
[21:24:23.609Z] [ERROR] PanoPostprocessorService::run | end - false
Datadog 쿼리 2 — 동일 에러 패턴:
service:cupixworks-pano-postprocessor-instance status:error "cvtColor" OR "cv2.error" OR "color.cpp"
Time: 2026-04-09T20:24:22Z to 2026-04-09T21:54:22Z
결과: 동일한 에러가 3개의 filebeat 호스트(cbea887adc6f, 58f281671bed, 641ca277ea55)에서 중복 수집됨 — 동일한 컨테이너 로그 파일을 여러 에이전트가 읽은 것으로, 실제 발생은 1건임.
영향 범위:
- Job 1006116, Capture 678511 — team digitaltwinmarine, user tbruger@digitaltwinmarine.com
- 동일 시간대에 다른 캡처(678513, 678514)도 동일 사용자가 처리 중이었음
Fix Recommendation#
즉시 조치 (Critical)#
aws-s3.manager.ts:75-77 — downloadParallel의 catch 블록에서 다운로드 실패한 pano를 추적하도록 수정. 실패한 pano ID 목록을 반환하거나, 실패 시 에러를 re-throw하여 호출자가 인지할 수 있도록 해야 함.
pano-postprocessor-service.ts:117-126 — 다운로드 완료 후 maskPanos 호출 전에 실제 다운로드된 파일 존재 여부를 검증하는 로직 추가. 다운로드 실패한 pano는 erroredPanoIds에 추가하고 마스킹 대상에서 제외해야 함.
단기 개선 (1주 이내)#
Python 스크립트 방어 코드 추가: cupix_inference.py의 detect() 함수에서 cv2.imread() 후 반환값이 None인지 검사하고, None이면 해당 이미지를 skip하고 경고 로그를 출력하도록 수정. 이는 S3 다운로드 외에 이미지 손상 등 다른 원인의 실패도 방어함.
장기 개선 (재발 방지)#
파이프라인 전체에서 각 단계(download → mask → infer → resize) 간 전달되는 pano 목록을 이전 단계의 성공/실패에 따라 필터링하는 구조로 변경. 현재는 모든 단계가 동일한 cpPanos 배열을 사용하므로, 한 단계의 실패가 다음 단계에 전파됨.
Monitoring#
- S3 다운로드 실패율 모니터링:
service:cupixworks-pano-postprocessor-instance "downloadParallel" "download failed"
cvtColorassertion 에러 재발 감지:
service:cupixworks-pano-postprocessor-instance status:error "cvtColor" OR "_src.empty"
Risk Assessment#
- Risk level: medium — 특정 S3 키가 없는 pano에 대해서만 발생하며, 전체 서비스 장애는 아님. 그러나 해당 캡처의 전체 postprocessing이 실패함.
- 예상 복잡도: standard — TypeScript 측 다운로드 실패 추적 로직 추가와 Python 측 null 체크 추가가 필요. face-body-detector는 외부 빌드 의존성이므로 Python 수정은 별도 빌드/배포 사이클이 필요함.