스테이블 디퓨전 시작하기
스테이블 디퓨전은 깃허브 리포지토리에서 받을 수 있다. 호스팅되는 스테이블 디퓨전 설치본과 상호작용하기 위한 엔드포인트를 제공하는 여러 프로젝트가 있으므로 직접 모델을 설정하고 학습시키지 않아도 된다. 가장 좋은 스테이블 디퓨전 인터페이스 중 하나는 무료 평가판을 제공하는 스테이블 디퓨전 API다. 이 API를 사용해 무료 오픈소스 프론트 엔드 자바스크립트 라이브러리인 리액트로 스테이블 디퓨전과 상호작용할 수 있다.먼저 create-react-app 명령줄 툴을 사용해 새 리액트 애플리케이션을 시작한다. 이를 위해서는 노드(Node)와 NPM을 미리 설치해야 한다. 그런 다음 $ npm i -g create-react-app으로 create-react-app을 설치한다. 이제 $ create-react-app react-sd 명령으로 간단한 애플리케이션을 만들 수 있다. 테스트하려면 react-sd 디렉터리로 이동해 $ npm start를 입력한 다음 시작 페이지인 localhost:3000을 방문한다.
리액트 UI 빌드
리액트 및 스테이블 디퓨전을 설정했으니 이제 텍스트 프롬프트를 입력해 스테이블 디퓨전 API 엔드포인트로 보내고 결과 이미지를 표시하는 UI를 만들 차례다. 스크롤 가능한 간단한 창을 사용해 생성된 이미지 목록을 표시하는 방식으로 작동한다.먼저, <예시 1>과 같이 프롬프트를 입력 받는 텍스트 상자와 여기 붙일 제출(Submit) 버튼을 만든다. 이 코드는 리액트의 기본 App.js 파일을 대체한다.
<예시 1> 기본 App.js 파일에 텍스트 상자 및 제출 옵션 추가
const App = () => {
const [textPrompt, setTextPrompt] = useState("");
const [prompts, setPrompts] = useState([]);
const generateImage = async () => {
};
const handleClick = () => {
setPrompts([...prompts, textPrompt]);
setTextPrompt("");
generateImage();
};
return (
<div>
<input
type="text"
placeholder="Enter a text prompt"
onChange={(e) => setTextPrompt(e.target.value)}
/>
<button onClick={handleClick}>Generate Image</button>
<ul>
{prompts.map((prompt) => (
<li key={prompt}>{prompt}</li>
))}
</ul>
</div>
);
};
export default App;
이제 애플리케이션은 텍스트 입력을 받아 이를 UI에 정렬되지 않은 목록으로 표시되는 배열에 저장한다. 프롬프트 입력과 제출된 프롬프트의 배열은 모두 useState 후크 변수다.
API 엔드포인트에 연결
여기까지 generateImage() 함수는 아무 일도 하지 않는다. 작업을 본격적으로 구현해 보자. 첫 번째 단계는 스테이블 디퓨전 엔드포인트에 텍스트 프롬프트를 제출하는 것이다. 스테이블 디퓨전 API는 여기에 설명된 대로 JSON 요청과 응답을 기다리는 RESTful 스타일이다. 이 문서에는 여기에 설명된 텍스트-투-이미지 엔드포인트를 포함한 API 엔드포인트 목록이 포함된다.한 가지 유의할 점은 스테이블 디퓨전 API가 브라우저 요청을 좋아하지 않는다는 것이다. 이는 보안과 관련된 사항이므로(API 키를 브라우저에 노출), 이 예제에서는 공개 프록시를 사용해 우회한다. 애플리케이션이 노출되는 실제 애플리케이션에서는 이 방법을 사용하면 안 된다. 실제 환경에서는 백엔드 애플리케이션을 사용해 키를 보관하고 프론트 엔드 요청에 집어넣으면 된다. 여기서는 편의를 위해 헤로쿠(Heroku)의 CORS 프록시를 사용한다.
전체적으로는 사용자의 텍스트를 받아서 이를 API 엔드포인트에 POST 요청으로 제출하는 것이다. JSON 본문에 API 키와 사용자 텍스트를 위한 필드가 있다. 응답은 JSON 응답 본문으로 돌아온다(형식에 대한 자세한 내용은 여기를 참조). 여기서 우리가 보는 핵심은 단일 요소, 즉 생성된 이미지의 URL이 포함되는 출력 배열이다.
사용자 인터페이스에서 이 URL을 가져와 순서 없는 목록으로 된 텍스트 프롬프트 옆의 이미지 구성요소에 넣는다. <예시 2>는 <예시 1>을 발전시켜 이러한 작업을 수행하는 코드를 보여준다.
<예시 2> API 호출 및 생성된 이미지 표시
const App = () => {
const [textPrompt, setTextPrompt] = useState("");
const [prompts, setPrompts] = useState([]);
const generateImage = async () => {
const apiKey = "YOUR KEY HERE";
const url = "https://stablediffusionapi.com/api/v3/text2img";
const proxyUrl = "https://cors-anywhere.herokuapp.com/";
const requestOptions = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ key: apiKey, prompt: textPrompt }),
};
try {
const response = await fetch(proxyUrl + url, requestOptions);
const data = await response.json();
// Get the image URL from the response
const imageUrl = data.output[0];
// Update the prompts array with the generated image URL
setPrompts([...prompts, { prompt: textPrompt, imageUrl }]);
} catch (error) {
console.error("Error generating image:", error);
}
};
const handleClick = async () => {
setPrompts([...prompts, { prompt: textPrompt, imageUrl: "" }]);
setTextPrompt("");
await generateImage();
};
return (
<div className="container">
<input className="input-container"
type="text"
placeholder="Enter a text prompt"
value={textPrompt}
onChange={(e) => setTextPrompt(e.target.value)}
/>
<button onClick={handleClick}>Generate Image</button>
<ul className="prompts-list">
{prompts.map((item, index) => (
<li key={index} className="prompt-item">
<p className="prompt-text">{item.prompt}</p>
{item.imageUrl && <img onerror="removeImage($(this));" src={item.imageUrl} alt="Generated Image" className="generated-image"/>}
</li>
))}
</ul>
</div>
);
};
export default App;
여기서 두 가지가 중요하다. 첫째, “YOUR API KEY HERE” 부분에 실제 API 키를 입력해야 한다. 둘째, (앞서도 말했지만) 공개 인터넷에 접한 웹 애플리케이션에서는 키가 노출되므로 이렇게 하면 안 된다. 다음으로, 생성된 이미지를 추적하고 표시할 수 있도록 프롬프트의 useState 변수에 imageUrl 필드를 추가한 것을 볼 수 있다. 또한 생성된 이미지가 표시되도록 handleClick() 함수에서 generateImage()가 완료될 때까지 기다리기 위한 await도 있다.
npm start로 이 애플리케이션을 시작하고 브라우저에서 보면, 프롬프트를 처음 제출할 때 헤로쿠 CORS 프록시가 권한을 요청할 수 있다. 자바스크립트 콘솔에서 누락된 권한(상태 401)에 대한 메시지와 링크가 표시된다. 링크를 클릭해서 페이지가 열리면 임시 액세스 버튼을 클릭한다(<그림 1> 참조).
이제 프롬프트를 제출하면 애플리케이션이 <그림 2>와 같이 프롬프트와 이미지 목록을 표시한다.
애플리케이션 미세 조정
여기까지만 해도 모두 잘 작동하지만 몇 가지 개선할 수 있는 부분이 있다. 우선 모든 이미지에 대한 로딩 상태 표시가 있다. 또 다른 부분은 스테이블 디퓨전은 이미지가 언제 완료되어야 하는지에 대한 ETA 필드와 함께 처리 상태를 반환하는 경우가 종종 있다는 점이다. 더 정교한 UI로 처리 상태를 표시할 수 있다.첫 번째 우선 순위는 스타일 개선이다. CSS를 추가해 이미지를 더 조밀한 형식으로 표시하고 텍스트에 스타일을 더해 보기 좋게 해보자.
<예시 3> 스타일을 개선한 애플리케이션
text-align: center;
margin-top: 50px;
}
.input-container {
margin-bottom: 20px;
}
input[type="text"] {
padding: 10px;
font-size: 16px;
border: none;
border-radius: 4px;
}
button {
padding: 10px 20px;
font-size: 16px;
background-color: #007bff;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
}
.prompts-list {
list-style-type: none;
padding: 0;
margin: 0;
}
.prompt-item {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.prompt-text {
flex: 1;
font-size: 18px;
}
.generated-image {
width: 200px;
height: 200px;
object-fit: cover;
border-radius: 4px;
}
.loading-text {
font-size: 14px;
font-style: italic;
color: #aaa;
}
이제 이 라인을 App.js 파일의 앞부분에 추가해 새 스타일을 가져올 수 있다.
이제 <그림 3>과 같이 입력 텍스트, 버튼 프롬프트, 이미지 내역이 더 보기 좋게 표시된다.
마지막 개선으로, 이미지를 클릭해 프롬프트와 함께 스테이블 디퓨전의 img2img 엔드포인트로 보내는 기능을 추가해 보자. 이렇게 하면 기존 이미지를 클릭해서 새 출력 이미지 URL을 위한 프롬프트와 결합할 수 있다. 클릭하면 이미지는 JSON 본문에 클릭한 이미지의 URL과 추가 필드 init_image가 포함된 요청을 생성한다. <예시 4>에서 이미지 클릭 핸들러와 업데이트된 generateImage() 함수가 있는 업데이트된 코드를 볼 수 있다.
<예시 4> 업데이트된 코드
const apiKey = "YOUR KEY HERE";
let url = "https://stablediffusionapi.com/api/v3/text2img";
const proxyUrl = "https://cors-anywhere.herokuapp.com/";
const requestBody = {
key: apiKey,
prompt: textPrompt,
};
if (imageUrl) {
requestBody.init_image = imageUrl;
requestBody.samples = 1;
requestBody.width = 800;
requestBody.height = 800;
url = "https://stablediffusionapi.com/api/v3/img2img";
}
const requestOptions = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(requestBody),
};
try {
setIsLoading(true);
const response = await fetch(proxyUrl + url, requestOptions);
const data = await response.json();
if (data.status === "error") {
console.error("Error generating image: " + data.message);
alert(data.message);
return;
}
// Get the image URL from the response
const generatedImageUrl = data.output[0];
// Update the prompts array with the generated image URL
setPrompts([...prompts, { prompt: textPrompt + (imageUrl ? " (image2image)" : ""), imageUrl: generatedImageUrl }]);
} catch (error) {
console.error("Error generating image:", error);
} finally {
setIsLoading(false);
}
};
//...
const handleImageClick = (imageUrl) => {
console.log("Clicked on image:", imageUrl);
generateImage(imageUrl);
};
// ...
<img onClick={() => handleImageClick(item.imageUrl)} onerror="removeImage($(this));" src={item.imageUrl} alt="Generated Image" className="generated-image"/>
여기서 우리가 하는 주된 작업은 이미지에 클릭 핸들러를 추가하고 generateImage()에 imageUrl 인수가 있으면 image2image URL을 사용하고 필요한 인수를 JSON 본문에 추가하는 것이다(이 엔드포인트에는 샘플, 너비, 높이 매개변수가 필요함). 이제 이미지를 클릭해서 프롬프트를 추가해 <그림 4>와 같이 이미지를 발전시킬 수 있다.
결론
생성형 AI는 지난 1년에 걸쳐 큰 인기를 끌었고 스테이블 디퓨전은 텍스트 프롬프트를 기반으로 이미지를 생성하는 강력한 기능을 통해 중요한 모델 중 하나로 올라섰다. 지금까지 살펴본 것처럼 호스팅되는 API를 사용해 모델을 직접 설치하고 학습시키지 않고도 웹 애플리케이션에서 AI가 생성한 이미지를 이용할 수 있다.editor@itworld.co.kr
Sponsored
Surfshark
“유료 VPN, 분명한 가치 있다” VPN 선택 가이드
ⓒ Surfshark VPN(가상 사설 네트워크, Virtual Private Network)은 인터넷 사용자에게 개인 정보 보호와 보안을 제공하는 중요한 도구로 널리 인정받고 있다. VPN은 공공 와이파이 환경에서도 데이터를 안전하게 전송할 수 있고, 개인 정보를 보호하는 데 도움을 준다. VPN 서비스의 수요가 증가하는 것도 같은 이유에서다. 동시에 유료와 무료 중 어떤 VPN을 선택해야 할지 많은 관심을 가지고 살펴보는 사용자가 많다. 가장 먼저 사용자의 관심을 끄는 것은 별도의 예산 부담이 없는 무료 VPN이지만, 그만큼의 한계도 있다. 무료 VPN, 정말 괜찮을까? 무료 VPN 서비스는 편리하고 경제적 부담도 없지만 고려할 점이 아예 없는 것은 아니다. 보안 우려 대부분의 무료 VPN 서비스는 유료 서비스에 비해 보안 수준이 낮을 수 있다. 일부 무료 VPN은 사용자 데이터를 수집해 광고주나 서드파티 업체에 판매하는 경우도 있다. 이러한 상황에서 개인 정보가 유출될 우려가 있다. 속도와 대역폭 제한 무료 VPN 서비스는 종종 속도와 대역폭에 제한을 생긴다. 따라서 사용자는 느린 인터넷 속도를 경험할 수 있으며, 높은 대역폭이 필요한 작업을 수행하는 데 제약을 받을 수 있다. 서비스 제한 무료 VPN 서비스는 종종 서버 위치가 적거나 특정 서비스 또는 웹사이트에 액세스하지 못하는 경우가 생긴다. 또한 사용자 수가 늘어나 서버 부하가 증가하면 서비스의 안정성이 저하될 수 있다. 광고 및 추적 위험 일부 무료 VPN은 광고를 삽입하거나 사용자의 온라인 활동을 추적하여 광고주에게 판매할 수 있다. 이 경우 사용자가 광고를 보아야 하거나 개인 정보를 노출해야 할 수도 있다. 제한된 기능 무료 VPN은 유료 버전에 비해 기능이 제한될 수 있다. 예를 들어, 특정 프로토콜이나 고급 보안 기능을 지원하지 않는 경우가 그렇다. 유료 VPN의 필요성 최근 유행하는 로맨스 스캠은 인터넷 사기의 일종으로, 온라인 데이트나 소셜 미디어를 통해 가짜 프로필을 만들어 상대를 속이는 행위다. 이러한 상황에서 VPN은 사용자가 안전한 연결을 유지하고 사기 행위를 방지하는 데 도움이 된다. VPN을 통해 사용자는 상대방의 신원을 확인하고 의심스러운 활동을 감지할 수 있다. 서프샤크 VPN은 구독 요금제 가입 후 7일간의 무료 체험을 제공하고 있다. ⓒ Surfshark 그 외에도 유료 VPN만의 강점을 적극 이용해야 하는 이유는 다음 3가지로 요약할 수 있다. 보안 강화 해외 여행객이 증가함에 따라 공공 와이파이를 사용하는 경우가 늘어나고 있다. 그러나 공공 와이파이는 보안이 취약해 개인 정보를 노출할 위험이 있다. 따라서 VPN을 사용하여 데이터를 암호화하고 개인 정보를 보호하는 것이 중요하다. 서프샤크 VPN은 사용자의 개인 정보를 안전하게 유지하고 해킹을 방지하는 데 유용하다. 개인정보 보호 인터넷 사용자의 검색 기록과 콘텐츠 소비 패턴은 플랫폼에 의해 추적될 수 있다. VPN을 사용하면 사용자의 IP 주소와 로그를 숨길 수 있으며, 개인 정보를 보호할 수 있다. 또한 VPN은 사용자의 위치를 숨기고 인터넷 활동을 익명으로 유지하는 데 도움이 된다. 지역 제한 해제 해외 여행 중에도 한국에서 송금이 필요한 경우가 생길 수 있다. 그러나 IP가 해외 주소이므로 은행 앱에 접근하는 것이 제한될 수 있다. VPN을 사용하면 지역 제한을 해제해 해외에서도 한국 인터넷 서비스를 이용할 수 있다. 따라서 해외에서도 안전하고 편리하게 인터넷을 이용할 수 있다. 빠르고 안전한 유료 VPN, 서프샤크 VPN ⓒ Surfshark 뛰어난 보안 서프샤크 VPN은 강력한 암호화 기술을 사용하여 사용자의 인터넷 연결을 안전하게 보호한다. 이는 사용자의 개인 정보와 데이터를 보호하고 외부 공격으로부터 사용자를 보호하는 데 도움이 된다. 다양한 서버 위치 서프샤크 VPN은 전 세계 곳곳에 여러 서버가 위치하고 있어, 사용자가 지역 제한된 콘텐츠에 액세스할 수 있다. 해외에서도 로컬 콘텐츠에 손쉽게 접근할 수 있음은 물론이다. 속도와 대역폭 서프샤크 VPN은 빠른 속도와 무제한 대역폭을 제공하여 사용자가 원활한 인터넷 경험을 누릴 수 있도록 지원한다. 온라인 게임, 스트리밍, 다운로드 등 대역폭이 필요한 활동에 이상적이다. 다양한 플랫폼 지원 서프샤크 VPN은 다양한 플랫폼 및 디바이스에서 사용할 수 있다. 윈도우, 맥OS, iOS, 안드로이드 등 다양한 운영체제 및 디바이스에서 호환되어 사용자가 어디서나 안전한 인터넷을 즐길 수 있다. 디바이스 무제한 연결 서프샤크 VPN은 무제한 연결을 제공하여 사용자가 필요할 때 언제든지 디바이스의 갯수에 상관없이 VPN을 사용할 수 있다.