반응형

other_parameters에 있는 exif 값들은 일반적으로 아래와 같이 출력됩니다

 

Steps: 30
Sampler: Euler a
CFG scale: 6
Seed: 1234567890
Size: 1024x1536
Model hash: 7890abcdef
Model: Model_name
Denoising strength: 0.5
Clip skip: 2
Version: v9.9.9
Mask blur: 2

실제로는 줄바꿈 없이 한 줄로 출력되지만 보기 좋으라고 줄바꿈을 임의로 넣어뒀고 이렇게 줄바꿈이 포함되어서 나오는것이 목표입니다

이거는 정규 표현식을 사용할 필요 없이 나누기(split)만 사용해줘도 됩니다

그러나 other_parameters의 값들을 변경해주기 때문에 이전 글에서 작성한 key_array, value_array에 입력되는 값들을 조금 변경해줘야 합니다

 

def ExtractEXIFParameters(image):
    # 앞 내용 생략
    
    key_array = ['Prompt', 'Negative prompt']
    value_array = [positive_prompt, negative_prompt]

    other_key_array = []
    other_value_array = []
    
    for exif in other_parameters.split(','):
        exif = exif.strip()
        if ': ' in exif:
            key, value = exif.split(': ', 1)
            other_key_array.append(key)
            other_value_array.append(value)
    
    key_array += other_key_array
    value_array += other_value_array
    
    return key_array, value_array

이렇게 해준다면 맨 위에 있던 내용과 거의 비슷하게, 정확하게는 줄바꿈을 추가하고 쉼표(,)를 제거했습니다

그러나 일부 이미지의 exif는 별도의 중괄호를 이용해서 표시하기도 합니다

따라서 아래와 같이 표시되는 경우도 나올 수 있습니다

 

Steps: 30
Sampler: Euler a
CFG scale: 6
Seed: 1234567890
Size: 512x768
Model hash: 7890abcdef
Model: Model_name
Denoising strength: 0.5
Hires upscale: 2
Hires upscaler: Latent
Hashes: {"vae": "7890abcdef"
"embed:embed_name": "7890abcdef"
"lora:lora_name": "7890abcdef"
"model": "7890abcdef"}

이렇게 해도 보는데는 전혀 문제가 없지만 보기에 좋지 않습니다

이 부분은 다음에 해결해보도록 하겠습니다

 

Steps: 30, 
Sampler: Euler a,
CFG scale: 6, 
Seed: 1234567890, 
Size: 512x768, 
Model hash: 7890abcdef, 
Model: Model_name, 
Denoising strength: 0.5, 
Hires upscale: 2, 
Hires upscaler: Latent, 
Hashes: {"vae": "7890abcdef", "embed:embed_name": "7890abcdef", "lora:lora_name": "7890abcdef", "model": "7890abcdef"}

 

 

전체 코드는 아래와 같습니다

 

import re
import chardet
import piexif
from PIL import Image

def ExtractEXIFParameters(image):
    try:
        img = Image.open(image)
        img.verify()
    except(IOError, SyntaxError):
        print('This is not image file.')
        return [], []

    if (img.format == 'PNG'):
        metadata = list(img.info.values())[0]

    elif (img.format == 'JPEG'):
        # check exist exif data
        try:
            exif_dict = piexif.load(img.info['exif'])
        except:
            return [], []
        
        user_comment = exif_dict['Exif'].get(piexif.ExifIFD.UserComment)
        if user_comment:
            # remove encoding header
            metadata = user_comment[8:]

            # detecting encoding
            detected = chardet.detect(metadata)
            encoding = detected['encoding'] or 'utf-8'

            # decoding exif data
            metadata = metadata.decode(encoding, errors = 'ignore')

    else:
        print('Not support format, support *.png or *.jpg image format.')
        return [], []
    
    parameters = re.search(r'^(.*?)\nNegative prompt: (.*?)\n(.*)', metadata, re.DOTALL)
    if (parameters is not None):
        positive_prompt = parameters.group(1).strip()
        negative_prompt = parameters.group(2).strip()
        other_parameters = parameters.group(3).strip()
        
        positive_prompt = re.sub(r'\n+', ', ', positive_prompt)
        negative_prompt = re.sub(r'\n+', ', ', negative_prompt)
    else:
        return [], []

    key_array = ['Prompt', 'Negative prompt']
    value_array = [positive_prompt, negative_prompt]

    parameters = re.search(r'(.+?:) (\{.*?\}),? ?(.*)', other_parameters, re.DOTALL)
    if (parameters is not None):
        before_parameters = parameters.group(1).strip()
        inner_parameters = parameters.group(2).strip()
        after_parameters = parameters.group(3).strip()
    
        # remove {, } and " mark
        inner_parameters = re.sub(r'[{}"]', '', inner_parameters)
        for prompt in before_parameters.split(', '):
            if (': ' in prompt):
                key, value = prompt.split(': ', 1)
                key_array.append(key.strip())
                value_array.append(value.strip())
            else:
                key_array.append(prompt.replace(':', ''))
                value_array.append(inner_parameters)
        
        for prompt in after_parameters.split(', '):
            if (': ' in prompt):
                key, value = prompt.split(': ', 1)
                key_array.append(key.strip())
                value_array.append(value.strip())

    else:
        for prompt in other_parameters.split(', '):
            if (': ' in prompt):
                key, value = prompt.split(': ', 1)
                key_array.append(key.strip())
                value_array.append(value.strip())
    
    return key_array, value_array

image = './image_file.png'
keys, values = ExtractEXIFParameters(image)

for key, value in zip(keys, values):
    print(key + ': ' + value)

여기까지 했다면 기능적으로 부족함은 없을겁니다

다만 이 코드를 다른 기기에서 실행하기 위해서는 Python을 설치하고, 라이브러리도 설치하고 꽤나 번거롭고 특히 모바일 기기가 이에 해당합니다

그래서 웹 사이트를 만들고 Docker 이미지로 만들고 홈 서버를 이용해서 어디에서나 EXIF를 추출할 수 있도록 하겠습니다

반응형
반응형

이미지 파일이 아닌 경우를 걸러내주도록 하겠습니다

 

def ExtractEXIFParameters(image):
    try:
        img = Image.open(image)
        img.verify()
    except(IOError, SyntaxError):
        print('This is not image file.')
        return [], []
        # 이하 내용 동일

예외문(try, except)을 사용해서 이미지 파일이 아니거나 손상된 이미지 파일들은 예외문으로 실행됩니다

여기에는 docx 파일과 같이 이미지 파일이 아닌 파일들의 확장자를 이미지 파일 확장자인 png로 변경한 것도 해당됩니다

하지만 여전히 png 파일만 가능한데 이것을 다른 확장자명으로도 가능하도록 변경해보겠습니다

 

import chardet # 5.2.0
import piexif # 1.1.3

# 중간 내용 생략

def ExtractEXIFParameters(image):
    # 중간 내용 생략
    if (img.format == 'PNG'):
        metadata = list(img.info.values())[0]

    elif (img.format == 'JPEG'):
        # check exist exif data
        try:
            exif_dict = piexif.load(img.info['exif'])
        except:
            return [], []
        
        user_comment = exif_dict['Exif'].get(piexif.ExifIFD.UserComment)
        if user_comment:
            # remove encoding header
            metadata = user_comment[8:]

            # detecting encoding
            detected = chardet.detect(metadata)
            encoding = detected['encoding'] or 'utf-8'

            # decoding exif data
            metadata = metadata.decode(encoding, errors = 'ignore')

    else:
        print('Not support format, support *.png or *.jpg image format.')
        return [], []

이미지 포맷으로 png인지 jpg인지 확인하고 각 포맷에 맞는 방식으로 진행하면 됩니다

png는 그대로 출력해도 문제가 없지만 jpg는 인코딩을 별도로 설정해줘야 하는데 자동 인코딩을 설정하도록 chardet를 사용했습니다

여기까지 하게 된다면 전체적인 코드는 아래와 같을겁니다

 

import re
import chardet
import piexif
from PIL import Image

def ExtractEXIFParameters(image):
    try:
        img = Image.open(image)
        img.verify()
    except(IOError, SyntaxError):
        print('This is not image file.')
        return [], []

    if (img.format == 'PNG'):
        metadata = list(img.info.values())[0]

    elif (img.format == 'JPEG'):
        # check exist exif data
        try:
            exif_dict = piexif.load(img.info['exif'])
        except:
            return [], []
        
        user_comment = exif_dict['Exif'].get(piexif.ExifIFD.UserComment)
        if user_comment:
            # remove encoding header
            metadata = user_comment[8:]

            # detecting encoding
            detected = chardet.detect(metadata)
            encoding = detected['encoding'] or 'utf-8'

            # decoding exif data
            metadata = metadata.decode(encoding, errors = 'ignore')

    else:
        print('Not support format, support *.png or *.jpg image format.')
        return [], []
    
    parameters = re.search(r'^(.*?)\nNegative prompt: (.*?)\n(.*)', metadata, re.DOTALL)
    if (parameters is not None):
        positive_prompt = parameters.group(1).strip()
        negative_prompt = parameters.group(2).strip()
        other_parameters = parameters.group(3).strip()
        
        positive_prompt = re.sub(r'\n+', ', ', positive_prompt)
        negative_prompt = re.sub(r'\n+', ', ', negative_prompt)
        
        key_array = ['Prompt', 'Negative prompt', 'Other parameters']
    	value_array = [positive_prompt, negative_prompt, other_parameters]
        
        return key_array, value_array
    else:
        return [], []

image = './image_file.jpg'
keys, values = ExtractEXIFParameters(image)

for key, value in zip(keys, values):
    print(key + ': ' + value + '\n')

그러나 여전히 출력된 exif가 보기 좋지 않다는 문제점이 있습니다

이 부분은 다음에 해결해보도록 하겠습니

반응형
반응형

Stable Diffusion으로 생성한 이미지의 프롬프트(EXIF) 를 확인하려면 Python의 pillow 라이브러리를 사용해야 합니다

pip install pillow

다른 라이브러리들과 같이 사용하는 경우 모든 라이브러리들의 출시 일자를 거의 비슷하게 맞춰주는 것이 오류가 발생할 확률을 줄이는데 도움이 됩니다

필자의 경우 250102에 나온 11.1.0 버전을 사용했습니다

 

출시 일자와 버전은 아래의 페이지에서 확인할 수 있습니다

https://pypi.org/project/pillow/#history

import re
from PIL import Image

필요한 라이브러리들을 가져옵니다

re 라이브러리는 정규 표현식(Regular Expressions) 을 줄인것입니다

프롬프트를 추출하는데 사용합니다

 

def ExtractEXIFParameters(image):
    img = Image.open(image)
    metadata = list(img.info.values())[0]
    print(metadata)

image = './image_file.png'
ExtractEXIFParameters(image)
# {문자열}\nNegative prompt: {문자열}\nSteps: {숫자}, Sampler: {문자열}, CFG scale: {숫자}, 이하 생략

사용하기 편하도록 함수로 만들어줍니다

실행하면 주석에 나온것과 비슷하게 출력될겁니다

만약에 아무것도 출력되지 않는다면 둘 중 하나입니다: png 이미지가 아니거나, 해당 이미지에 EXIF가 없거나

이렇게만 해도 다 확인할 수 있지만 보기에는 상당히 좋지 않습니다

그래서 보기 좋게 바꿔주도록 하겠습니다

 

나누려고 하는데 어디를 기준으로 해야할지 정하기 어렵습니다

필자의 경우 일단은 Prompt, Negative prompt, 그 이외의 값 이렇게 셋으로 나누려고 합니다

Negative prompt에서 항상 있는 문자열은 '\nNegative prompt: ' 가 해당됩니다

그러면 이 문자열을 기준으로 나눈다면 값을 쉽게 나눌 수 있을것입니다

def ExtractEXIFParameters(image):
    # print() 함수를 제외한 이전 코드 이어서 작성
    parameters = re.search(r'^(.*?)\nNegative prompt: (.*?)\n(.*)', metadata, re.DOTALL)
    if (parameters is not None):
        positive_prompt = parameters.group(1).strip()
        negative_prompt = parameters.group(2).strip()
        other_parameters = parameters.group(3).strip()
        
        positive_prompt = re.sub(r'\n+', ', ', positive_prompt)
        negative_prompt = re.sub(r'\n+', ', ', negative_prompt)
        
        key_array = ['Prompt', 'Negative prompt', 'Other parameters']
    	value_array = [positive_prompt, negative_prompt, other_parameters]
        
        return key_array, value_array
    else:
        return [], []

문자열을 나누기 위해서 정규 표현식을 사용했습니다

정규 표현식과 관련해서는 아래의 글들을 보시면 도움이 될겁니다

 

re.search() 함수 https://docs.python.org/3/library/re.html#re.Pattern.search

re.sub() 함수 https://docs.python.org/3/library/re.html#re.Pattern.sub

08-1 정규 표현식 살펴보기 https://wikidocs.net/1642
08-2 정규 표현식 시작하기 https://wikidocs.net/4308
08-3 강력한 정규 표현식의 세계로 https://wikidocs.net/4309

 

위의 글들을 봐도 이해가 잘 안간다면 ChatGPT, Claude와 같은 LLM을 사용하시는걸 추천드립니다

 

이미지에 EXIF가 있다면 parameters에 어떤 값이 들어있게 됩니다

그러면 if() 문은 True가 되어서 실행이 되고 positive_prompt, negative_prompt, other_parameters에 각각에 해당하는 값들이 들어가게 됩니다

만약에 이미지에 EXIF가 없다면 parameters에 어떠한 값도 없어서 if() 문은 False가 되어서 else를 실행하게 됩니다

이 경우에는 이미지에 EXIF가 없기 때문에 빈 배열 2개를 반환해줍니다

 

추가로 가끔 Prompt, Negative prompt에 줄바꿈(\n) 이 있는 경우도 있습니다

Stable Diffusion에서 줄바꿈은 쉼표(, ) 와 동일하게 작동하므로 이를 바꿔주기 위해서 마찬가지로 정규 표현식을 사용해줬습니다

image = './image_file.png'
keys, values = ExtractEXIFParameters(image)

for key, value in zip(keys, values):
    print(key + ': ' + value)

다 입력했으면 한번 실행해봅니다

실행하면 Prompt, Negative prompt, 그 외 기타 값 이렇게 셋으로 나눠져서 잘 출력됩니다

import re
from PIL import Image

def ExtractEXIFParameters(image):
    img = Image.open(image)
    metadata = list(img.info.values())[0]
    
    parameters = re.search(r'^(.*?)\nNegative prompt: (.*?)\n(.*)', metadata, re.DOTALL)
    if (parameters is not None):
        positive_prompt = parameters.group(1).strip()
        negative_prompt = parameters.group(2).strip()
        other_parameters = parameters.group(3).strip()
        
        positive_prompt = re.sub(r'\n+', ', ', positive_prompt)
        negative_prompt = re.sub(r'\n+', ', ', negative_prompt)
        
        key_array = ['Prompt', 'Negative prompt', 'Other parameters']
    	value_array = [positive_prompt, negative_prompt, other_parameters]
        
        return key_array, value_array
    else:
        return [], []

image = './image_file.png'
keys, values = ExtractEXIFParameters(image)

for key, value in zip(keys, values):
    print(key + ': ' + value)

전체적인 코드는 위와 같습니다

이렇게 하면 Stable Diffusion으로 생성한 이미지의 EXIF를 출력할 수 있습니다

다만 몇 가지 문제점이 있습니다

 

1. png 이미지만 사용 가능

2. 예외문이 없어서 오류가 발생할 수 있음(이미지 파일이 아닌 다른 파일을 넣었을 때 등)

3. 출력된 exif가 보기 좋지 않음

 

이 부분들은 다음에 해결해보도록 하겠습니다

반응형
반응형

필자가 사용한 라이브러리의 버전과 날짜는 다음과 같습니다

Python 3.13.1, 241203
chardet 5.2.0, 230802
piexif 1.1.3, 190702
pillow 11.1.0, 250102

 

1. Stable Diffusion으로 생성된 이미지 EXIF 추출하기(1)

 

Stabel Diffusion으로 생성된 이미지를 웹 페이지에 넣으면 해당 이미지의 EXIF를 출력하게 하려 합니다

 

반응형

+ Recent posts