ComfyUI 및 WebUI를 이용해 스테이블 디퓨전으로 생성한 이미지의 경우 흔히들 말하는 PNG EXIF 정보를 볼 수 있습니다. 즉, 이미지의 프롬프트 및 설정값들을 확인할 수 있다는 것인데요, 과연 이 값들은 어디에 저장되는 것이며 어디서 확인할 수 있을까요? 본 글에서는 WebUI PNG Info 탭을 거치지 않고 파이썬(Python) 및 자바스크립트(Javascript) 코드로 직접 AI 이미지의 프롬프트를 추출하는 방법에 대해 살펴보도록 하겠습니다.
PNG EXIF 메타데이터란
사실 스테이블 디퓨전으로 생성한 AI 이미지의 메타데이터(프롬프트 및 모델 정보)가 PNG 파일의 어느 부분에 저장되어 있는지 찾다 보면 PNG EXIF란 기술적으로 틀린 말이라는 것을 알 수 있습니다. 실제로 PNG 그림 파일에는 EXIF 정보가 없다고 알려져 있는데요, 보통 EXIF는 디지털 카메라로 촬영한 사진의 사진 촬영 설정값(노출, 조리개, 셔터스피드, ISO감도, 위치, 시간 등)이 저장되어 있습니다. 그럼 도대체 AI 그림 생성 정보는 어디에 저장될까요?
이를 확인해보기 위해 WebUI의 Settings 탭의 Saving images/grids으로 가면 기본적으로 PNG Chunk 형태로 이미지생성 설정값 문자 정보로 저장하기 옵션(Save text information about generation parameters as chunks to png files)이 활성화 되어 있습니다.
즉, 스테이블 디퓨젼으로 생성한 AI 그림의 그림 생성 파라미터값은 PNG 파일에 chunk 형태로 저장된다는 것을 알 수 있습니다. 그렇다면 PNG Chunk란 무엇일까요?
PNG Chunk란
우선 PNG란 이름 자체를 보면 Portable Network Graphics로, 네트워크 상에서 주고받을 수 있는 포멧의 정적인 이미지를 의미합니다. 따라서 PNG 파일은 다른 이미지 포멧과는 다른 정형화된 구조를 가지는데요, PNG 파일의 디지털 데이터 구조를 시각화해서 보면 아래와 같습니다.
위 구조에서 볼 수 있듯 PNG 파일은 Magic number, IHDR, IDAT, IEND 이 네가지 정보를 필수적으로 가지고 있으며, 추가적으로 iEXt라는 정보를 더 가질 수도 있습니다. 이때 Magic number는 PNG 파일의 시그니처를 담고 있으며 그 뒤를 따르는 각 정보 영역을 PNG Chunk(청크)라고 하는데요, 각 청크에 담기는 데이터는 다음과 같습니다.
- Magic number : PNG 시그니처로 해당 파일이 PNG 포멧임을 나타내는 곳.
- IHDR : 가장 최초에 위치해야 하는 청크, 헤더 영역으로 PNG 파일의 가로, 세로 해상도, 색상 타입, 압축 방식 등을 기록한 정보.
- IDAT : 실제 이미지 데이터가 담긴 곳.
- IEND : 가장 마지막에 위치해야 하는 청크로 실제 데이터는 없지만 파일 데이터의 종점을 명시함.
- iEXt : IHDR~IEND 사이에 위치할 수 있는 텍스트 정보를 담는 청크로, 주로 이미지 데이터 외 각종 메타데이터를 기록하는 곳. 스테이블 디퓨전 프롬프트 및 모델, 샘플러 정보 등도 여기에 저장됨.
즉, 흔히 PNG EXIF 데이터라고 불리는 정보는 사실 PNG 청크 중 iEXt에 해당하는 텍스트 메타 정보를 의미합니다. 따라서 PNG 파일로부터 스테이블 디퓨전 프롬프트를 추출하기 위해서는 이 iEXt를 추출해낼 수 있어야 합니다.
PNG EXIF 추출 – Chunk 파헤치기
그럼 이제 PNG EXIF 추출, 즉, AI 그림의 프롬프트, 모델, 샘플러 등의 생성 정보 추출을 하려면 PNG 파일의 iEXt 청크로부터 시작해야한다는 것을 알았으니 본격적으로 코드로 한 번 살펴보겠습니다.
파이썬
파이썬에서 PNG EXIF 추출하는 방법은 매우 간단합니다. PIL package를 임포트하기만 하면 아래와 같이 Image.info라는 아주 간단한 메소드를 이용하는 코드로 PNG의 텍스트 메타데이터(iEXt chunk)를 쉽게 확인할 수 있습니다.
from PIL import Image
from pprint import pprint
def exif_reader(image):
img = Image.open(image)
metadata = img.info
pprint(metadata)
if __name__ == ‘__main__’ :
exif_reader(“testimg.png”)
아래 이미지는 테스트를 위해 WebUI에서 이미지를 하나 생성한 뒤 이를 WebUI PNG Info 탭에 다시 로드하여 결과값을 파이썬과 비교한 결과입니다.
이미지에서 볼 수 있듯 PNG Info 탭과 Pycharm 터미널에 print된 값이 완벽하게 동일하다는 것을 알 수 있습니다. 따라서 파이썬에서 PNG EXIF 추출, 즉 프롬프트, 모델 등 AI 그림 생성 정보를 추출하여 대량으로 확인하거나 추가적인 확장 기능을 구현해야할 경우 위에 소개드린 코드를 베이스로 활용할 수 있습니다.
자바스크립트
이번에는 자바스크립트로 PNG EXIF 즉, PNG iEXt chunk를 추출하는 코드를 한 번 살펴보겠습니다.
function pngParser(buffer) {
var view = new DataView(buffer),
len = buffer.byteLength,
magic1, magic2,
chunks = [],
size, fourCC, crc, offset,
pos = 0; // current offset in buffer ("file")
// check header
magic1 = view.getUint32(pos); pos += 4;
magic2 = view.getUint32(pos); pos += 4;
if (magic1 === 0x89504E47 && magic2 === 0x0D0A1A0A) {
// parse chunks
while (pos < len) {
// chunk header
size = view.getUint32(pos);
fourCC = getFourCC(view.getUint32(pos + 4));
// data offset
offset = pos + 8;
pos = offset + size;
// crc
crc = view.getUint32(pos);
pos += 4;
// store chunk
chunks.push({
fourCC: fourCC,
size: size,
offset: offset,
crc: crc
})
}
return {chunks: chunks}
}
else {
return {error: "Not a PNG file."}
}
function getFourCC(int) {
var c = String.fromCharCode;
return c(int >>> 24) + c(int >>> 16 & 0xff) + c(int >>> 8 & 0xff) + c(int & 0xff);
}
}
위 코드를 보면 pngParser라는 function에 png이미지를 ArrayBuffer 형태로 집어넣게 되어 있습니다. 기본적인 원리는 이렇게 ArrayBuffer 형태로 받은 데이터를 기준으로 앞단부터 순차적으로 데이터를 검증하면서 PNG Chunk를 판별하는 것입니다.
먼저 magic1과 magic2는 앞서 PNG 구조에서 살펴본 바와같이 PNG 시그니쳐를 의미합니다. 즉, 이 파일이 PNG 포멧인지 여부를 확인하는 작업으로, PNG 파일이라면 magic1과 magic2가 항상 위 코드에 작성된 특정 값을 가져야만 합니다. 따라서 if(magic1 === 0x89504E47 && magic2 === 0x0D0A1A0A)를 만족하는 경우 이제 순서대로 chunk 정보를 파싱하게 됩니다.
PNG 파일의 구조가 chunk들의 구성으로 이뤄진 것처럼, 각 chunk의 구조도 정형화되어 있습니다. 모든 chunk는 아래와 같이 4개의 구성으로 구조가 짜여져 있습니다.
- Length (4 byte) : Chunk의 크기
- Chunk Type (4 byte) : Chunk의 타입
- Chunk Data (length byte) : Chunk 내용
- CRC (4byte) : 오류 검사를 위한 값
위 코드에서는 chunk의 시작점을 offset으로 설정하고, pos를 마치 ArrayBuffer 내에 위치한 커서처럼 이용해 pos의 늘어난 길이만큼 이동하면서 데이터를 확인하는 작업을 하고 있습니다. 따라서 코드를 따라 해석해보시면 chunk에 진입하여 첫 4byte를 통해 해당 chunk의 총 길이를 파악하고, pos+4를 주어 그 다음 4byte를 통해 해당 chunk의 타입을 알아옵니다. 이 때 chunk의 타입이 iEXt라면 우리가 원하는 텍스트 형태의 메타정보를 확인할 수 있을 것입니다.
따라서 chunk type이 iEXt라면, PNG 전체에 해당하는 ArrayBuffer를 iEXt chunk에 해당하는 부분 데이터만큼만 slice한 뒤 이를 다시 textDecoder로 해석하여 우리가 볼 수 있는 문자열로 받아오면 됩니다.
var slicedBuffer = ArrayBuffer.slice(offset, offset+size);
const dataByteArray = new Unit8Array(slicedBuffer);
var txtdecoder = new TextDecoder(“utf-8”);
var txt = txtdecoder.decde(dataByteArray);
이렇게 파싱한 텍스트 값을 잘 가공하면 아래와 같이 postive 및 negative 프롬프트와, 생성에 사용한 모델, 샘플러, 스텝 등의 정보를 확인해볼 수 있습니다.
상기 이미지는 위에서 설명드린 코드를 기반으로 본 사이트에서 구현한 프롬프트 추출 기능입니다. 궁금하신 분들은 아래에서 테스트 해보시기 바랍니다.