GPT Image 마스크 편집: 마스크와 변경되지 않아야 할 픽셀 확인

GPT Image 마스크 편집을 실행하고 대상 영역 밖의 픽셀을 확인합니다. 실제 결과, 알파 마스크 검사, 픽셀 비교와 재현 가능한 입력을 담았습니다.

옅은 카드 위에 검은 잉크로 그린 스텐실 시트, 세이지색 배경, 코랄색 점과 ‘GPT Image Masks’라는 제목.

마스크는 이미지 편집 요청에서 변경할 영역을 지정하지만, 반환된 이미지의 나머지 부분도 확인해야 합니다. GPT Image 2.5 Flare 테스트에서 마스크를 적용한 배경 편집 결과는 대상 띠 영역 밖에서 원본과 비슷해 보였지만, 픽셀을 비교하자 해당 영역에서도 차이가 발견됐습니다. 출력은 무손실 패치가 아니었습니다.

이 튜토리얼에서는 원본, 마스크 규칙, 실제 반환된 이미지와 간단한 픽셀 검사 방법을 보여줍니다. 제품 이미지의 일부를 변경하면서 제품 자체의 변경을 모르는 사이에 받아들이지 않으려는 개발자와 크리에이터를 위한 내용입니다. 모든 마스크 편집이 같은 방식으로 작동한다거나, 예시 하나로 모델의 전반적인 품질을 측정할 수 있다고 주장하지 않습니다.

생성 전용 인터페이스라는 안내가 표시된 Ofox의 영어 이미지 플레이그라운드

10월 12일에 캡처한 공개 임베디드 플레이그라운드에는 해당 인터페이스에서 이미지 편집을 사용할 수 없다는 안내가 표시돼 있었습니다. 이 글의 편집 결과는 아래에 나온 API 경로를 사용해 얻은 것이며, 이 플레이그라운드에서 생성한 것이 아닙니다. 캡처된 인터페이스에서 마스크나 참조 이미지 업로드 컨트롤을 찾지 마세요.

요청 전에 편집 영역 지정하기

9월 13일에 Ofox에서 생성한 참조 이미지를 재사용했습니다. 상표가 없는 호박색 병에 아이보리색 뚜껑이 달린 이미지입니다. 이는 고객 제품의 사진이 아니라 합성 제품 이미지입니다. 원본은 1024 × 1024 PNG입니다. 라벨 텍스트가 없으므로 이 테스트로는 로고, 성분 표기 또는 규정 문구의 보존 여부를 알 수 없습니다.

편집 입력으로 사용한 합성 호박색 병 원본

대상 영역은 의도적으로 단순하게 정했습니다. 가장 왼쪽의 256개 열, 즉 x = 0부터 x = 255까지입니다. 병은 이 띠 영역 바깥에 있습니다. 병, 뚜껑, 위치와 다른 영역은 그대로 두고 띠 영역 안쪽에만 차분한 세이지색 배경을 적용하도록 요청합니다.

마스크는 원본과 크기가 같은 RGBA PNG입니다. 편집 가능한 띠 영역의 알파값은 0이고, 나머지는 완전히 불투명합니다. 겉보기만 흑백인 그레이스케일 이미지는 이 알파 마스크와 같지 않습니다. OpenAI의 이미지 편집 참고 문서에서는 투명한 마스크 영역과 마스크와 입력 이미지의 관계를 설명합니다. 엔드포인트의 현재 파일 제약도 항상 확인하세요.

마스크를 로컬에서 검증하기

업로드 전에 파일 모드, 크기, 알파 분포를 검사하세요. 다음 코드는 테스트용 파일을 검증합니다. 실제 편집하려는 영역에 맞게 수정하세요.

from PIL import Image

source = Image.open("source.png")
mask = Image.open("mask-left.png")
assert source.size == mask.size == (1024, 1024)
assert mask.mode == "RGBA"
alpha = mask.getchannel("A")
assert alpha.crop((0, 0, 256, 1024)).getextrema() == (0, 0)
assert alpha.crop((256, 0, 1024, 1024)).getextrema() == (255, 255)

이 어서션은 과금이 발생할 수 있는 요청을 보내기 전에 알파 채널이 뒤집혔거나 평탄화된 문제를 잡아냅니다. 일반적인 편집기에서는 미리보기 색상만 믿지 말고 실제 투명도를 확인하세요. 출력 이미지가 원본이나 마스크를 덮어쓰지 않도록 각각 따로 저장하세요.

참조 이미지가 여러 개라면 엔드포인트가 마스크를 어느 이미지에 적용하는지 확인하세요. 의도한 기본 이미지를 첫 번째에 두고, 마스크가 모든 참조 이미지에 대한 전역 지시라고 가정하지 마세요. 이번 마스크 테스트에는 이미지를 정확히 하나만 보냈습니다.

실제 편집 요청 보내기

성공한 테스트에서는 Ofox의 /v1/images/edits 엔드포인트에서 openai/gpt-image-2.5-flare를 사용했습니다. size=1024x1024와 n=1을 보냈고, 품질 설정은 명시적으로 지정하지 않았습니다. 응답에는 quality: low가 보고됐습니다. 이는 기록된 응답 필드일 뿐 모든 요청의 기본값을 보장하지 않습니다.

curl --silent --show-error --fail-with-body \
  --connect-timeout 15 --max-time 120 \
  'https://api.ofox.ai/v1/images/edits' \
  -H "Authorization: Bearer $OFOX_API_KEY" \
  -F 'model=openai/gpt-image-2.5-flare' \
  -F 'image=@source.png;type=image/png' \
  -F 'mask=@mask-left.png;type=image/png' \
  -F 'size=1024x1024' -F 'n=1' \
  -F 'prompt=Change only the background strip at the far left quarter to muted sage green. Keep the amber bottle, cap, its position, and every other area unchanged.' \
  -o edit-response.json

실행 전에 OFOX_API_KEY를 안전하게 설정하세요. 이 명령은 API 사용량을 발생시킬 수 있습니다. 디코딩하기 전에 저장된 응답을 확인하세요. 오류 응답에는 디코딩할 이미지가 없습니다. 성공한 응답에는 data[0].b64_json이 포함돼 있었습니다. 해당 필드를 base64로 디코딩하고 바이트를 저장한 다음, 이미지가 예상한 크기로 열리는지 확인하세요.

다운로드 가능한 키트에는 원본 입력, 마스크, 반환된 파일과 이 작업을 수행하는 범위가 제한된 스크립트가 들어 있습니다. 일반적인 GPT Image API 가이드에서는 처음 액세스하는 방법과 이미지 응답을 저장하는 방법을 다룹니다.

실제 출력 두 개 비교하기

마스크 없이 같은 텍스트 편집 요청을 한 번 보내 성공적인 출력을 얻었습니다. 첫 번째 마스크 적용 시도는 클라이언트에서 시간 초과됐고, 이후 마스크를 적용한 요청은 성공했습니다. 클라이언트 시간 초과만으로는 생성이나 과금이 발생하지 않았다고 단정할 수 없으므로, 이후 과금 내역을 대조할 때 이를 제외하지 마세요.

실제 마스크 편집 결과: 병 이미지 왼쪽에 평평한 세이지색 띠가 있음

마스크를 적용해 성공한 결과에서는 왼쪽에 평평한 세이지색 띠가 선명하게 보입니다. 병의 전체적인 정체성과 위치는 원본과 비슷해 보이지만, 외관만으로는 보호 영역이 변경되지 않았다고 입증할 수 없습니다.

실제 마스크 미적용 편집 결과: 왼쪽에 질감이 있는 세이지색 띠가 있음

마스크 없이 요청했을 때도 모델은 왼쪽에 세이지색 띠를 넣었으며, 이 샘플에서는 질감이 더 있었습니다. 두 요청 모두 프롬프트를 그럴듯하게 해석한 결과를 생성했습니다. 그렇다고 정확한 보존이 필요한 워크플로에서 두 출력을 서로 바꿔 써도 된다는 뜻은 아닙니다.

x = 256부터 1023까지, 1024개 행 전체에서 디코딩된 RGB 값을 비교했습니다. 이 영역은 픽셀 786,432개로 구성됩니다. 세 채널 중 어느 하나라도 1단계만큼 차이가 나면 해당 픽셀을 변경된 것으로 계산합니다.

성공한 출력대상 띠 영역 밖에서 변경된 픽셀평균 절대 채널 차이, 0–255 기준
마스크 적용786,432개 중 784,316개3.493
마스크 미적용786,432개 중 785,921개5.464

이 결과 한 쌍에서는 마스크를 적용한 결과의 평균 차이가 더 작았습니다. 하지만 표본이 너무 작아 그 정도의 차이가 일반적으로 나타난다고 주장할 수는 없습니다. 더 중요한 점은 어느 결과도 픽셀을 정확히 보존해야 하는 요건을 충족하지 못했다는 것입니다. 변경된 픽셀 수가 많더라도 그 차이가 매우 작은 경우가 포함될 수 있습니다. 변경된 픽셀이 모두 눈에 띄게 손상됐다는 뜻은 아닙니다.

보호 영역 검사 반복하기

스크린샷, JPEG 미리보기 또는 크기가 조정된 썸네일이 아니라 다운로드한 원본 파일을 사용하세요. 그런 변환은 모델 편집과 무관한 차이를 만들어냅니다.

from PIL import Image, ImageChops, ImageStat

a = Image.open("source.png").convert("RGB")
b = Image.open("edited.png").convert("RGB")
if a.size != b.size:
    raise ValueError("Output dimensions changed; inspect before comparing")
region = (256, 0, 1024, 1024)
diff = ImageChops.difference(a.crop(region), b.crop(region))
changed = sum(any(pixel) for pixel in diff.getdata())
mean_difference = sum(ImageStat.Stat(diff).mean) / 3
print(changed, mean_difference)

이 검사는 픽셀 차이를 측정할 뿐, 브랜드 정확도, 지각 품질 또는 제품의 기하학적 형태를 측정하지 않습니다. 뚜껑, 윤곽, 재질, 가장자리, 텍스트와 그림자는 각각 따로 확인하세요. 제품 위치가 달라지면 단순 픽셀 차이는 지각된 유사성을 제대로 나타내지 못할 수 있지만, 고정 위치의 픽셀 보존 조건을 위반했다는 사실은 여전히 정확히 보여줍니다.

정확한 보존이 필요하다면 일반적인 합성 워크플로를 사용해 검수에 통과한 편집 영역만 원본 위에 얹을 수 있습니다. 경계선과 조명을 살펴보세요. 딱딱한 경계는 허용되지 않을 수 있습니다. 이는 별도의 후처리 단계이며, 모델 자체가 해당 픽셀을 보호했다는 뜻은 아닙니다. 공개된 모델 비교에서 변경 사항을 감추기 위해 이 방법을 사용하지 마세요.

다시 실행하기 전에 실패 원인 진단하기

잘못된 쪽이 바뀌었다면 먼저 알파값과 좌표를 확인하세요. 이미지 전체가 바뀌었다면 요청에 마스크가 실제로 첨부됐는지, 해당 경로가 그 매개변수를 받는지, 이미지 순서가 마스크의 대상 원본과 일치하는지 확인하세요. 요청 메타데이터를 출력과 함께 보관하세요.

서버가 파일을 거부하면 파일 형식, 바이트 크기, 이미지 크기와 현재 엔드포인트 제한을 확인하세요. JPEG의 이름을 .png로 바꿔도 형식이 변환되지는 않습니다. 요청이 시간 초과되면 다시 시도하기 전에 요청 기록을 확인하세요. 재시도도 과금될 수 있는 별도 작업으로 간주하세요.

썸네일 크기에서 결과가 좋아 보여도 승인하기 전에 원본을 전체 크기로 검토하세요. 라벨이 있는 제품이라면 텍스트와 로고를 명시적으로 검사하세요. 라벨이 없는 이 병 테스트 이미지만으로는 그런 특성을 보증할 수 없습니다. 제품은 그대로 두고 전체 장면을 바꾸는 방법은 참조 이미지 하나와 두 개를 사용한 테스트를 참고하세요.

자주 묻는 질문

마스크를 사용하면 나머지 모든 픽셀이 동일하게 유지되나요?
성공한 마스크 편집 샘플에서는 대상 영역 밖의 RGB 값이 정확히 보존되지 않았습니다. 정확한 보존이 납품 조건이라면 픽셀을 직접 확인하세요.
마스크를 사용한 결과가 항상 더 나은가요?
이 결과 한 쌍만으로는 그렇게 결론 내릴 수 없습니다. 보호 영역의 평균 차이는 더 작았지만, 두 결과 모두 작업별 시각적 검수를 거쳐야 합니다.
흑백 JPEG를 알파 마스크로 사용할 수 있나요?
여기서 사용한 투명 PNG 마스크를 대신할 수는 없습니다. 알파 채널을 확인하고 선택한 엔드포인트의 문서에 명시된 입력 요건을 따르세요.