종횡비 버킷

Aspect ratio buckets

정사각형이 아닌 이미지를 늘리거나 잘라내지 않고, 비슷한 비율끼리 미리 정해둔 몇 가지 해상도(버킷)에 맞춰 한 번에 처리하는 방식.

다른 이름: BucketingResolution buckets
···
html
<div class="row" id="row">
  <div class="frame"><canvas></canvas><span class="lbl">1:1</span></div>
  <div class="frame"><canvas></canvas><span class="lbl">3:4</span></div>
  <div class="frame"><canvas></canvas><span class="lbl">16:9</span></div>
  <div class="frame"><canvas></canvas><span class="lbl">9:16</span></div>
  <div class="frame bad"><canvas></canvas><span class="lbl">비율 무시</span></div>
</div>
css
.row{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;height:100%;padding:0 6px;flex-wrap:nowrap}
.frame{display:flex;flex-direction:column;align-items:center;gap:3px;flex-shrink:0}
.frame canvas{display:block;border-radius:5px;border:1.5px solid var(--line)}
.frame.bad canvas{border-color:var(--accent-2)}
.lbl{font-size:8.5px;color:var(--muted);font-weight:700;white-space:nowrap}
js
function mulberry32(a) { return function () { a |= 0; a = a + 0x6D2B79F5 | 0; let t = Math.imul(a ^ a >>> 15, 1 | a); t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t; return ((t ^ t >>> 14) >>> 0) / 4294967296; }; }

function paintScene(ctx, w, h, seed) {
  const r = mulberry32(seed);
  const g = ctx.createLinearGradient(0, 0, 0, h);
  g.addColorStop(0, 'hsl(200 55% 68%)'); g.addColorStop(1, 'hsl(210 40% 35%)');
  ctx.fillStyle = g; ctx.fillRect(0, 0, w, h);
  ctx.beginPath(); ctx.arc(w * 0.68, h * 0.22, Math.min(w, h) * 0.1, 0, Math.PI * 2); ctx.fillStyle = 'hsl(45 90% 78%)'; ctx.fill();
  for (let l = 0; l < 2; l++) {
    ctx.beginPath(); ctx.moveTo(0, h);
    for (let i = 0; i <= 6; i++) { const x = w * i / 6; const y = h * (0.55 + l * 0.16) - r() * h * 0.12; ctx.lineTo(x, y); }
    ctx.lineTo(w, h); ctx.closePath();
    ctx.fillStyle = 'hsl(150 35% ' + (22 + l * 13) + '%)'; ctx.fill();
  }
}
// 박스 종횡비 목록. 마지막(비율 무시)은 정사각형 내용을 이 비율의 박스에 강제로 눌러 담아 왜곡을 보여준다.
const ratios = [1, 0.75, 1.78, 0.56, 1.35];
const BAD = ratios.length - 1;
const frameEls = [...document.querySelectorAll('.frame')];
function layout(seed) {
  const gap = 6, n = ratios.length;
  const totalRatio = ratios.reduce((s, r) => s + r, 0);
  const availW = innerWidth - 12;
  const maxH = innerHeight * 0.68;
  const H = Math.max(20, Math.min(maxH, (availW - gap * (n - 1)) / totalRatio));
  frameEls.forEach((f, i) => {
    const cv = f.querySelector('canvas');
    const boxW = Math.round(H * ratios[i]), boxH = Math.round(H);
    cv.style.width = boxW + 'px'; cv.style.height = boxH + 'px';
    if (i === BAD) {
      // 내용은 정사각형으로 그리고, 표시 박스만 다른 비율 — 캔버스가 자동으로 눌려 찌그러진다
      cv.width = Math.round(H); cv.height = Math.round(H);
    } else {
      cv.width = boxW; cv.height = boxH;
    }
    paintScene(cv.getContext('2d'), cv.width, cv.height, seed);
  });
}
const seeds = [3, 17];
let si = 0;
layout(seeds[si]);
addEventListener('resize', () => layout(seeds[si]));
setInterval(() => { si = (si + 1) % seeds.length; layout(seeds[si]); }, 3000);

모델을 한 번에 여러 장씩 묶어서 학습·생성하려면(배치 처리) 같은 텐서 크기가 필요합니다. 이미지마다 원래 비율이 제각각이라면, 가장 단순한 해법은 전부 강제로 잘라내거나(가장자리 소실) 억지로 늘리는(비율 왜곡) 것입니다.

버킷 방식은 그 대신 자주 쓰이는 비율 몇 가지(정사각형, 세로 사진, 가로 사진 등)를 미리 정해두고, 들어온 이미지를 그 원래 비율에 가장 가까운 버킷에 배정합니다. 같은 버킷 안에서는 크기가 동일하니 배치 처리가 되면서도, 늘리거나 잘라내는 대신 원래 비율에 훨씬 가까운 해상도로 처리되어 내용이 온전히 유지됩니다.

다만 버킷은 이산적인 몇 가지 값일 뿐이라, 모든 비율이 정확히 맞아떨어지지는 않습니다. 어떤 버킷과도 많이 다른 비율은 그래도 가장 가까운 버킷으로 약간의 리사이즈를 거쳐 들어갑니다.

아래 데모는 서로 다른 비율의 프레임 각각에 같은 장면을 그 프레임 크기에 맞춰 다시 그려서(늘리지 않고 자연스럽게) "비율마다 통째로 담긴다"를 보여주고, 붉은 테두리의 한 프레임에는 비율을 무시하고 억지로 눌러 넣은 왜곡된 예시를 나란히 둔 시뮬레이션입니다.

언제 쓰나

여러 비율의 이미지를 한 파이프라인에서 다뤄야 할 때 개념을 참고합니다. 직접 프롬프트로 조작하는 값이라기보다 도구·모델이 내부적으로 처리하는 방식입니다.