ES /docs

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#

Datadog Logs

text
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:43PanoPostprocessorService::init()이 인증 후 run()을 호출
  • S3 다운로드: pano-postprocessor-service.ts:117awsS3Manager.downloadParallel(cpPanos)로 모든 pano 이미지를 병렬 다운로드
  • 다운로드 실패 (에러 삼킴): aws-s3.manager.ts:75-77 — S3 NoSuchKey 에러가 catch 블록에서 로깅만 되고 무시됨
typescript
// 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-33spawn('stdbuf', ['-oL', 'bash', BlurScriptPath])로 face-body-detector 실행
typescript
// 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() 호출
python
# 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 실행 타임라인:

text
service:cupixworks-pano-postprocessor-instance @job.id:1006116
Time: 2026-04-09T21:00:00Z to 2026-04-09T22:00:00Z, sort: timestamp asc

핵심 로그 타임라인:

text
[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 — 동일 에러 패턴:

text
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-77downloadParallel의 catch 블록에서 다운로드 실패한 pano를 추적하도록 수정. 실패한 pano ID 목록을 반환하거나, 실패 시 에러를 re-throw하여 호출자가 인지할 수 있도록 해야 함.

pano-postprocessor-service.ts:117-126 — 다운로드 완료 후 maskPanos 호출 전에 실제 다운로드된 파일 존재 여부를 검증하는 로직 추가. 다운로드 실패한 pano는 erroredPanoIds에 추가하고 마스킹 대상에서 제외해야 함.

단기 개선 (1주 이내)#

Python 스크립트 방어 코드 추가: cupix_inference.pydetect() 함수에서 cv2.imread() 후 반환값이 None인지 검사하고, None이면 해당 이미지를 skip하고 경고 로그를 출력하도록 수정. 이는 S3 다운로드 외에 이미지 손상 등 다른 원인의 실패도 방어함.

장기 개선 (재발 방지)#

파이프라인 전체에서 각 단계(download → mask → infer → resize) 간 전달되는 pano 목록을 이전 단계의 성공/실패에 따라 필터링하는 구조로 변경. 현재는 모든 단계가 동일한 cpPanos 배열을 사용하므로, 한 단계의 실패가 다음 단계에 전파됨.

Monitoring#

  • S3 다운로드 실패율 모니터링:
text
service:cupixworks-pano-postprocessor-instance "downloadParallel" "download failed"
  • cvtColor assertion 에러 재발 감지:
text
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 수정은 별도 빌드/배포 사이클이 필요함.