๐ ๏ธ Method:
์ด๋ฏธ์ง ์์ฑ ํ๋ก์ธ์ค
์ด๋ ํ ์ฌ์ง๋ค์ ๋ง๋ค ๊ฒ์ธ๊ฐ?
- ์ค๋ฆฝ(Neutral) ์ฌ์ง - ๊ธฐ์ค ์ด๋ฏธ์ง๋ฅผ ์ ์
- ๊ธฐํ ํ์ ์ผ๋ก ๋ณํ - Angry, Sad, Fear ๋ฑ์ ํ์ ์ผ๋ก ๋ณํ.
์ฌ์ฉ ๋๊ตฌ
์ด๋ป๊ฒ ๋ง๋ค ๊ฒ์ธ๊ฐ?
- ์จ๋ผ์ธ Tools: ์ธํฐ๋ท์์ ์ ๊ณตํ๋ ์ ์ฉ ํด๋ค์ ํ์ฉ
- ๋ก์ปฌ: ์ง์ Stable Diffusion์ ๋ก์ปฌ์์ ์คํ
- nano-banana
๐ง Procedure:
1. Stable Diffusion Settings
์ผ๊ตด ํ์ ์ ๋ณํํ๊ธฐ ์ํ ๋ชจ๋ธ
- Model: Juggernaut XL Ragnarok.safetensors (dd08fa32f9)
- Mode: Inpainting
- LoRA: Tuning Face Detailer v2.0
- LoRA Weight: 0.5
Prompt
๐ธ Stable Diffusion prompt ์คํฌ๋ฆฐ์ท
Positive-Prompt (Token: ??/150)
A close-up portrait of the same male with a mildly angry expression. The person is frowning slightly, eyebrows gently furrowed, lips pressed, showing a hint of annoyance. All other features remain unchanged โ same person, same background, same lighting โ resulting in a photorealistic image that looks like an authentic subtle frown. <lora:Tuning Face Detailer v2.0 Rank 256:1>
Negative-Prompt (Token: 44~46/75)
smile, neutral expression, lowres, blurry, cartoon, painting, overexposed, underexposed, background distortion, identity change, extra artifacts, watermark, text, deformed eyebrows, plastic skin, exaggerated wrinkles, different eye colors, mismatched eye colors, averted gaze, looking away
Caution!
์ฑ๋ณ ์ ๋ณด ๊ฐ ๋ด๊ธด ๋ถ๋ถ์์ ์ฃผ์!!
ex) his, male, etc
๋, smile ํ์ ์ ๋ง๋ค ๋์๋ neg-prompt์์ smile์ ์ ๊ฑฐ.
server option : โopt-sdp-attention
1. Angry face
Positive-Prompt (Token: 94/150)
A close-up portrait of the same ==[male] showing a moderate expression of anger==. [His] eyebrows are drawn downward and inward with a noticeable crease between them. [His] eyes are slightly narrowed, adding tension to his gaze. The lips are pressed firmly together, and the jaw appears set, suggesting controlled frustration. All other features remain unchanged โ same person, same background, same lighting โ resulting in a photorealistic image that captures a restrained but clearly visible anger. <lora:Tuning Face Detailer v2.0 Rank 256:0.5>
Positive-Prompt (Token: ??/150) - refining
A close-up portrait of the same ==[male] showing a moderate expression of anger==. [His] eyebrows are drawn downward and inward with a noticeable crease between them. [His] eyes are slightly narrowed, adding tension to his gaze. The lips are pressed firmly together, and the jaw appears set, suggesting controlled frustration. All other features remain unchanged โ same person, same background, same lighting โ resulting in a photorealistic image that captures a restrained but clearly visible anger. <lora:Tuning Face Detailer v2.0 Rank 256:0.5>
2. Happy face
Positive-Prompt (Token: 94/150)
A close-up portrait of the same [male] with a moderately intense smile. The corners of his lips are clearly lifted into a confident curve, and the muscles around his eyes engage enough to cause slight crowโs feet. [His] cheeks are noticeably raised, giving his face a warm, energized appearance. All other features remain unchanged โ same person, same background, same lighting โ resulting in a photorealistic image that conveys a sincere and emotionally present smile. <lora:Tuning Face Detailer v2.0 Rank 256:1>
3. Sad face
Positive-Prompt (Token: 93/150)
A close-up portrait of the same [male] with a moderate expression of sadness. The inner corners of his eyebrows are raised and knitted slightly, forming a soft wrinkle at the top of his nose. His eyelids droop subtly, and the corners of his lips are turned down with visible tension in the mouth. All other features remain unchanged โ same person, same background, same lighting โ resulting in a photorealistic image that communicates genuine emotional heaviness. <lora:Tuning Face Detailer v2.0 Rank 256:1>
1.ย ย ย ย ย ย A close-up portrait of the same male frozen in a moment of intense fear. His eyebrows are raised high and pulled together tightly, forming strong vertical wrinkles above the nose. His upper eyelids are stretched wide, fully exposing the whites of his eyes, and the lower lids are tensed. His mouth is open and jaw dropped, as if gasping or about to scream. The entire face is taut with alarm. All other features remain unchanged โ same person, same background, same lighting โ resulting in a photorealistic image that conveys a sudden, powerful surge of panic.
4. Fear face
Positive-Prompt (Token: 117/150)
A close-up portrait of the same male with a moderately fearful expression. His eyebrows are lifted and drawn together, forming vertical lines between them. His upper eyelids are raised enough to expose more of the whites of his eyes, and the lower eyelids are slightly tensed. His lips are stretched back horizontally with a slight parting of the mouth, suggesting a controlled but noticeable startle response. All other features remain unchanged โ same person, same background, same lighting โ resulting in a photorealistic image that conveys alertness and unease without exaggeration. <lora:Tuning Face Detailer v2.0 Rank 256:1>
๐ธ Stable Diffusion parameter setting ์คํฌ๋ฆฐ์ท
Inpaint area: Only masked(mask๋ ์์ญ๋ง SD๋ก ์์ ํ๊ฒ ๋ค.)
Mask blur: 4
Sampling Method: DPM++ 2M
Schedule type: Karras
Sampling step: 30
CFG Scale: 7
Denoising strength: 0.55
Batch size: 5
Batch count: 2Env Setting:
Nvidia L4 GPU 1์ฅ
Mask blur: 4
Sampling Method: DPM++ 2M
Schedule type: Karras
Sampling step: 30
CFG Scale: 7
Denoising strength: 0.6
Batch size: 5
Batch count: 2
OpenPose โ IP-Adapter
openpose_face_only
โxformers: On(0.0.23.post1) / python: 3.10.18 / torch:2.1.2+cu121
Sampling Step
SD๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ ธ์ด์ฆ(noise)๋ก ๊ฐ๋ ์ฐฌ ์ด๋ฏธ์ง๋ฅผ ์ ์ง์ ์ผ๋ก ๋๋ ธ์ด์ง(denoising)ํ์ฌ ์ต์ข ์ด๋ฏธ์ง๋ฅผ ๋ง๋ ๋ค.
์ด denoising ๊ณผ์ ์ ์ฌ๋ฌ step์ ๊ฑธ์ณ ์ด๋ฃจ์ด์ง๋๋ฐ, sampling step์ ์ด ๋จ๊ณ๋ฅผ ์ผ๋ง๋ ๋ง์ด ๊ฑฐ์น ์ง๋ฅผ ๊ฒฐ์ ํ๋ ๊ฐ.
- ๋ ธ์ด์ฆ ์ ๊ฑฐ : ๊ฐ ์คํ ๋ง๋ค ๋ชจ๋ธ์ ์ด๋ฏธ์ง์ ๋จ์ ์๋ ๋ ธ์ด์ฆ๋ฅผ ์กฐ๊ธ์ฉ ๋์ด๋ธ๋ค.
- ๋ฐ๋ณต ํ์ : ์ด ๊ณผ์ ์ ์ฌ๋ฌ ๋ฒ ๋ฐ๋ณตํ ์๋ก ์ด๋ฏธ์ง๋ ๋ ์ ๋ช ํด์ง๊ณ ๋ํ ์ผ์ด ์ด์๋๋ค.
- DPM++ 2M Karras๋ ์ ์ step์ผ๋ก๋ ์ข์ ๊ฒฐ๊ณผ๋ฅผ ๋ด๋ ๊ฒฝํฅ์ด ์๋ค.
์ผ๋ฐ์ ์ผ๋ก step์ 20~30์ ๋ ๊ฐ์ ธ๊ฐ๋๋ฐ, ํ์ ๊ฐ์ ๋ํ ์ผ์ ๋ณํ์ํค๋ ๊ฒ์ด ๋ชฉํ์ด๋ฏ๋ก,30์ผ๋ก ๊ฐ์ ธ๊ฐ.
- ๋๋ฌด ์ค๋ ๊ฑธ๋ฆฐ๋ค. โ 30์์ 20์ผ๋ก ๋ณ๊ฒฝ(ํ ์ฌ๋ ๋น 15๋ถ์์ 10๋ถ์ผ๋ก ๋จ์ถ.)
2. ControlNet Settings
์ผ๊ตด ๊ตฌ์กฐ์ ํฌ์ฆ๋ฅผ ์๋ณธ(Neutral)๊ณผ ๋์ผํ๊ฒ ์ ์งํ๊ธฐ ์ํด ControlNet ์ ์ฉ.
๐ธ ControlNet Unit0: IP-Adapter
- Unit0-preprocessor: ip-adapter_clip_sdxl_plus_vith
- Unit0: ip-adapter-plus-face_sdxl_vit-h
- Control Weight: 0.9
- Weight Type: Normal (0.9, 0.9, 0.9 โ)
- Control Step(Starting, Ending): 0.0~1.0
- Resolution: 512
SD์ ์ํด ์๋ณธ๊ณผ ๋ค๋ฅธ ์ฌ๋์ ์ผ๊ตด์ด ๋๋ ๊ฒ์ ๋ง์์ค.
๐ธ ControlNet Unit1: OpenPose
- Unit1-preprocessor: openpose_faceonly
- Unit1: OpenPose XL2
- Control Weight: 0.5
- Control Step(Starting, Ending): 0.0~0.7
- Resolution: 512
์๋ณธ๊ณผ ๋ค๋ฅธ ๋ ๋ฐฉํฅ์ด๋ ์ผ๊ตด ์ค๊ณฝ์ ๋ณด์ ํด์ค.
๐ ๋ถ์ ๋ฐ ์์ฌ์
Angry Face
- neg-prompt๊ฐ ๋งค์ฐ๋งค์ฐ ์ค์ํ๋ค.
- angry๋ฅผ ๋ง๋ค ๋, smile์ neg๋ก ์ค ๋์ ์๋ ๋ ์ฐจ์ด๊ฐ ์๋ค.
const SDXL_path = "assets/Expression-changed";
const expression = "ANG";
const nums = [1]; // ํ์ ์ ๋ณ๊ฒฝ
//const nums = Array.from({ length: 80 }, (_, i) => i + 1);
// ํ์ฌ ๋
ธํธ ๊ธฐ์ค์ผ๋ก ๋งํฌ -> TFile -> ๋ฆฌ์์ค URL๋ก ๋ณํ
const srcPath = dv.current().file.path;
const toUrl = (vaultPath) => {
const file = app.metadataCache.getFirstLinkpathDest(vaultPath, srcPath);
if (!file) {
console.warn("[์ด๋ฏธ์ง ์ฐพ์ ์ ์์]", vaultPath);
return "";
}
return app.vault.getResourcePath(file);
};
for (const n of nums) {
const withNegPath = `${SDXL_path}/${expression}/interesting/neg-prompt-comp/w-neg-smile.jpeg`;
const withoutNegPath = `${SDXL_path}/${expression}/interesting/neg-prompt-comp/wo-neg-smile.jpeg`;
const withNeg = toUrl(withNegPath);
const withoutNeg = toUrl(withoutNegPath);
const html = `
<div class="image-comparison">
<div class="image-column">
<h3>๐ ANG w/ smile-neg prompt</h3>
<div class="image-container">
<img src="${withNeg}" alt="Angry expression w/ neg prompt 'smile'" class="clickable-image" data-full="${withNeg}">
<div class="image-overlay"><span class="zoom-icon">๐</span></div>
</div>
<p class="image-description">neg prompt "smile"</p>
</div>
<div class="image-column">
<h3>๐ค ANG w/o smile-neg prompt</h3>
<div class="image-container">
<img src="${withoutNeg}" alt="Angry expression w/ neg prompt 'smile'" class="clickable-image" data-full="${withoutNeg}">
<div class="image-overlay"><span class="zoom-icon">๐</span></div>
</div>
<p class="image-description">neg prompt "smile" x</p>
</div>
</div>`;
dv.container.innerHTML += html;
}๐ ๊ฒฐ๊ณผ
Angry Face
Caucasian Male (CM)
const base_path = "assets/LCBL/AI-Generated";
const SDXL_path = "assets/LCBL/Expression-changed";
const expression = "ANG";
const race = "Caucasian";
const gender = "Man";
const code = race[0]+gender[0];
const nums = [1,2,3]; // ํ์ ์ ๋ณ๊ฒฝ
//const nums = Array.from({ length: 80 }, (_, i) => i + 1);
// ํ์ฌ ๋
ธํธ ๊ธฐ์ค์ผ๋ก ๋งํฌ -> TFile -> ๋ฆฌ์์ค URL๋ก ๋ณํ
const srcPath = dv.current().file.path;
const toUrl = (vaultPath) => {
const file = app.metadataCache.getFirstLinkpathDest(vaultPath, srcPath);
if (!file) {
console.warn("[์ด๋ฏธ์ง ์ฐพ์ ์ ์์]", vaultPath);
return "";
}
return app.vault.getResourcePath(file);
};
for (const n of nums) {
const neuPath = `${base_path}/${race}-${gender}/${code}${n}_Neu.jpeg`;
const angPath = `${base_path}/${race}-${gender}/${code}${n}_ANG.jpeg`;
const sdxlPath = `${SDXL_path}/${expression}/${code}/${code}${n}/${code}${n}-${expression}-SDXL.jpeg`;
const neu = toUrl(neuPath);
const ang = toUrl(angPath);
const sdxl = toUrl(sdxlPath);
const html = `
<div class="image-comparison">
<div class="image-column">
<h3>๐ Neutral</h3>
<div class="image-container">
<img src="${neu}" alt="Neutral expression ${code+n}" class="clickable-image" data-full="${neu}">
<div class="image-overlay"><span class="zoom-icon">๐</span></div>
</div>
<p class="image-description">๊ธฐ์ค ์ค๋ฆฝ ํ์ </p>
</div>
<div class="image-column">
<h3>๐ ANG (OpenArt)</h3>
<div class="image-container">
<img src="${ang}" alt="Angry expression OpenArt ${code+n}" class="clickable-image" data-full="${ang}">
<div class="image-overlay"><span class="zoom-icon">๐</span></div>
</div>
<p class="image-description">OpenArt๋ก ์์ฑ๋ ๋ถ๋
ธ ํ์ </p>
</div>
<div class="image-column">
<h3>๐ค ANG (SDXL)</h3>
<div class="image-container">
<img src="${sdxl}" alt="Angry expression SDXL ${code+n}" class="clickable-image" data-full="${sdxl}">
<div class="image-overlay"><span class="zoom-icon">๐</span></div>
</div>
<p class="image-description">SDXL๋ก ์์ฑ๋ ๋ถ๋
ธ ํ์ </p>
</div>
</div>`;
dv.container.innerHTML += html;
}Caucasian Female (CW)
const group = "assets/LCBL/AI-Generated/Caucasian-Woman";
const code = "CW";
const nums = [1,2]; // ํ์ ์ ๋ณ๊ฒฝ
//const nums = Array.from({ length: 80 }, (_, i) => i + 1);
// ํ์ฌ ๋
ธํธ ๊ธฐ์ค์ผ๋ก ๋งํฌ -> TFile -> ๋ฆฌ์์ค URL๋ก ๋ณํ
const srcPath = dv.current().file.path;
const toUrl = (vaultPath) => {
const file = app.metadataCache.getFirstLinkpathDest(vaultPath, srcPath);
if (!file) {
console.warn("[์ด๋ฏธ์ง ์ฐพ์ ์ ์์]", vaultPath);
return "";
}
return app.vault.getResourcePath(file);
};
for (const n of nums) {
const neuPath = `${group}/${code}${n}_Neu.jpeg`;
const angPath = `${group}/${code}${n}_ANG.jpeg`;
const sdxlPath = `${group}/${code}-SDXL/${code}${n}_ANG_SDXL.png`;
const neu = toUrl(neuPath);
const ang = toUrl(angPath);
const sdxl = toUrl(sdxlPath);
const html = `
<div class="image-comparison">
<div class="image-column">
<h3>๐ Neutral</h3>
<div class="image-container">
<img src="${neu}" alt="Neutral expression ${code+n}" class="clickable-image" data-full="${neu}">
<div class="image-overlay"><span class="zoom-icon">๐</span></div>
</div>
<p class="image-description">๊ธฐ์ค ์ค๋ฆฝ ํ์ </p>
</div>
<div class="image-column">
<h3>๐ ANG (OpenArt)</h3>
<div class="image-container">
<img src="${ang}" alt="Angry expression OpenArt ${code+n}" class="clickable-image" data-full="${ang}">
<div class="image-overlay"><span class="zoom-icon">๐</span></div>
</div>
<p class="image-description">OpenArt๋ก ์์ฑ๋ ๋ถ๋
ธ ํ์ </p>
</div>
<div class="image-column">
<h3>๐ค ANG (SDXL)</h3>
<div class="image-container">
<img src="${sdxl}" alt="Angry expression SDXL ${code+n}" class="clickable-image" data-full="${sdxl}">
<div class="image-overlay"><span class="zoom-icon">๐</span></div>
</div>
<p class="image-description">SDXL๋ก ์์ฑ๋ ๋ถ๋
ธ ํ์ </p>
</div>
</div>`;
dv.container.innerHTML += html;
}Korean Male (KM)
const group = "assets/LCBL/AI-Generated/Korean-Man";
const code = "KM";
const nums = [1,2]; // ํ์ ์ ๋ณ๊ฒฝ
//const nums = Array.from({ length: 80 }, (_, i) => i + 1);
// ํ์ฌ ๋
ธํธ ๊ธฐ์ค์ผ๋ก ๋งํฌ -> TFile -> ๋ฆฌ์์ค URL๋ก ๋ณํ
const srcPath = dv.current().file.path;
const toUrl = (vaultPath) => {
const file = app.metadataCache.getFirstLinkpathDest(vaultPath, srcPath);
if (!file) {
console.warn("[์ด๋ฏธ์ง ์ฐพ์ ์ ์์]", vaultPath);
return "";
}
return app.vault.getResourcePath(file);
};
for (const n of nums) {
const neuPath = `${group}/${code}${n}_Neu.jpeg`;
const angPath = `${group}/${code}${n}_ANG.jpeg`;
const sdxlPath = `${group}/${code}-SDXL/${code}${n}_ANG_SDXL.png`;
const neu = toUrl(neuPath);
const ang = toUrl(angPath);
const sdxl = toUrl(sdxlPath);
const html = `
<div class="image-comparison">
<div class="image-column">
<h3>๐ Neutral</h3>
<div class="image-container">
<img src="${neu}" alt="Neutral expression ${code+n}" class="clickable-image" data-full="${neu}">
<div class="image-overlay"><span class="zoom-icon">๐</span></div>
</div>
<p class="image-description">๊ธฐ์ค ์ค๋ฆฝ ํ์ </p>
</div>
<div class="image-column">
<h3>๐ ANG (OpenArt)</h3>
<div class="image-container">
<img src="${ang}" alt="Angry expression OpenArt ${code+n}" class="clickable-image" data-full="${ang}">
<div class="image-overlay"><span class="zoom-icon">๐</span></div>
</div>
<p class="image-description">OpenArt๋ก ์์ฑ๋ ๋ถ๋
ธ ํ์ </p>
</div>
<div class="image-column">
<h3>๐ค ANG (SDXL)</h3>
<div class="image-container">
<img src="${sdxl}" alt="Angry expression SDXL ${code+n}" class="clickable-image" data-full="${sdxl}">
<div class="image-overlay"><span class="zoom-icon">๐</span></div>
</div>
<p class="image-description">SDXL๋ก ์์ฑ๋ ๋ถ๋
ธ ํ์ </p>
</div>
</div>`;
dv.container.innerHTML += html;
}Korean Female (KW)
const group = "assets/LCBL/AI-Generated/Korean-Woman";
const code = "KW";
const nums = [1,2]; // ํ์ ์ ๋ณ๊ฒฝ
//const nums = Array.from({ length: 80 }, (_, i) => i + 1);
// ํ์ฌ ๋
ธํธ ๊ธฐ์ค์ผ๋ก ๋งํฌ -> TFile -> ๋ฆฌ์์ค URL๋ก ๋ณํ
const srcPath = dv.current().file.path;
const toUrl = (vaultPath) => {
const file = app.metadataCache.getFirstLinkpathDest(vaultPath, srcPath);
if (!file) {
console.warn("[์ด๋ฏธ์ง ์ฐพ์ ์ ์์]", vaultPath);
return "";
}
return app.vault.getResourcePath(file);
};
for (const n of nums) {
const neuPath = `${group}/${code}${n}_Neu.jpeg`;
const angPath = `${group}/${code}${n}_ANG.jpeg`;
const sdxlPath = `${group}/${code}-SDXL/${code}${n}_ANG_SDXL.png`;
const neu = toUrl(neuPath);
const ang = toUrl(angPath);
const sdxl = toUrl(sdxlPath);
const html = `
<div class="image-comparison">
<div class="image-column">
<h3>๐ Neutral</h3>
<div class="image-container">
<img src="${neu}" alt="Neutral expression ${code+n}" class="clickable-image" data-full="${neu}">
<div class="image-overlay"><span class="zoom-icon">๐</span></div>
</div>
<p class="image-description">๊ธฐ์ค ์ค๋ฆฝ ํ์ </p>
</div>
<div class="image-column">
<h3>๐ ANG (OpenArt)</h3>
<div class="image-container">
<img src="${ang}" alt="Angry expression OpenArt ${code+n}" class="clickable-image" data-full="${ang}">
<div class="image-overlay"><span class="zoom-icon">๐</span></div>
</div>
<p class="image-description">OpenArt๋ก ์์ฑ๋ ๋ถ๋
ธ ํ์ </p>
</div>
<div class="image-column">
<h3>๐ค ANG (SDXL)</h3>
<div class="image-container">
<img src="${sdxl}" alt="Angry expression SDXL ${code+n}" class="clickable-image" data-full="${sdxl}">
<div class="image-overlay"><span class="zoom-icon">๐</span></div>
</div>
<p class="image-description">SDXL๋ก ์์ฑ๋ ๋ถ๋
ธ ํ์ </p>
</div>
</div>`;
dv.container.innerHTML += html;
}Black Male (BM)
const group = "assets/LCBL/AI-Generated/Black-Man";
const code = "BM";
const nums = [1,2]; // ํ์ ์ ๋ณ๊ฒฝ
//const nums = Array.from({ length: 80 }, (_, i) => i + 1);
// ํ์ฌ ๋
ธํธ ๊ธฐ์ค์ผ๋ก ๋งํฌ -> TFile -> ๋ฆฌ์์ค URL๋ก ๋ณํ
const srcPath = dv.current().file.path;
const toUrl = (vaultPath) => {
const file = app.metadataCache.getFirstLinkpathDest(vaultPath, srcPath);
if (!file) {
console.warn("[์ด๋ฏธ์ง ์ฐพ์ ์ ์์]", vaultPath);
return "";
}
return app.vault.getResourcePath(file);
};
for (const n of nums) {
const neuPath = `${group}/${code}${n}_Neu.jpeg`;
const angPath = `${group}/${code}${n}_ANG.jpeg`;
const sdxlPath = `${group}/${code}-SDXL/${code}${n}_ANG_SDXL.png`;
const neu = toUrl(neuPath);
const ang = toUrl(angPath);
const sdxl = toUrl(sdxlPath);
const html = `
<div class="image-comparison">
<div class="image-column">
<h3>๐ Neutral</h3>
<div class="image-container">
<img src="${neu}" alt="Neutral expression ${code+n}" class="clickable-image" data-full="${neu}">
<div class="image-overlay"><span class="zoom-icon">๐</span></div>
</div>
<p class="image-description">๊ธฐ์ค ์ค๋ฆฝ ํ์ </p>
</div>
<div class="image-column">
<h3>๐ ANG (OpenArt)</h3>
<div class="image-container">
<img src="${ang}" alt="Angry expression OpenArt ${code+n}" class="clickable-image" data-full="${ang}">
<div class="image-overlay"><span class="zoom-icon">๐</span></div>
</div>
<p class="image-description">OpenArt๋ก ์์ฑ๋ ๋ถ๋
ธ ํ์ </p>
</div>
<div class="image-column">
<h3>๐ค ANG (SDXL)</h3>
<div class="image-container">
<img src="${sdxl}" alt="Angry expression SDXL ${code+n}" class="clickable-image" data-full="${sdxl}">
<div class="image-overlay"><span class="zoom-icon">๐</span></div>
</div>
<p class="image-description">SDXL๋ก ์์ฑ๋ ๋ถ๋
ธ ํ์ </p>
</div>
</div>`;
dv.container.innerHTML += html;
}Black Female (BW)
const group = "assets/LCBL/AI-Generated/Black-Woman";
const code = "BW";
const nums = [1,2]; // ํ์ ์ ๋ณ๊ฒฝ
//const nums = Array.from({ length: 80 }, (_, i) => i + 1);
// ํ์ฌ ๋
ธํธ ๊ธฐ์ค์ผ๋ก ๋งํฌ -> TFile -> ๋ฆฌ์์ค URL๋ก ๋ณํ
const srcPath = dv.current().file.path;
const toUrl = (vaultPath) => {
const file = app.metadataCache.getFirstLinkpathDest(vaultPath, srcPath);
if (!file) {
console.warn("[์ด๋ฏธ์ง ์ฐพ์ ์ ์์]", vaultPath);
return "";
}
return app.vault.getResourcePath(file);
};
for (const n of nums) {
const neuPath = `${group}/${code}${n}_Neu.jpeg`;
const angPath = `${group}/${code}${n}_ANG.jpeg`;
const sdxlPath = `${group}/${code}-SDXL/${code}${n}_ANG_SDXL.png`;
const neu = toUrl(neuPath);
const ang = toUrl(angPath);
const sdxl = toUrl(sdxlPath);
const html = `
<div class="image-comparison">
<div class="image-column">
<h3>๐ Neutral</h3>
<div class="image-container">
<img src="${neu}" alt="Neutral expression ${code+n}" class="clickable-image" data-full="${neu}">
<div class="image-overlay"><span class="zoom-icon">๐</span></div>
</div>
<p class="image-description">๊ธฐ์ค ์ค๋ฆฝ ํ์ </p>
</div>
<div class="image-column">
<h3>๐ ANG (OpenArt)</h3>
<div class="image-container">
<img src="${ang}" alt="Angry expression OpenArt ${code+n}" class="clickable-image" data-full="${ang}">
<div class="image-overlay"><span class="zoom-icon">๐</span></div>
</div>
<p class="image-description">OpenArt๋ก ์์ฑ๋ ๋ถ๋
ธ ํ์ </p>
</div>
<div class="image-column">
<h3>๐ค ANG (SDXL)</h3>
<div class="image-container">
<img src="${sdxl}" alt="Angry expression SDXL ${code+n}" class="clickable-image" data-full="${sdxl}">
<div class="image-overlay"><span class="zoom-icon">๐</span></div>
</div>
<p class="image-description">SDXL๋ก ์์ฑ๋ ๋ถ๋
ธ ํ์ </p>
</div>
</div>`;
dv.container.innerHTML += html;
}




