업스케일링

Upscaling

저해상도 이미지를 단순히 확대하는 게 아니라, 모델이 그럴듯한 디테일을 새로 채워 넣어 더 커 보이면서도 선명하게 만드는 처리.

다른 이름: AI upscalingSuper-resolution
···
html
<div class="wrap"><canvas id="cv"></canvas></div>
css
.wrap{position:relative;width:100%;height:100%}
#cv{position:absolute;inset:0;width:100%;height:100%}
js
const cv = document.getElementById('cv'), ctx = cv.getContext('2d');
function fit() { const dpr = Math.min(devicePixelRatio || 1, 2); cv.width = innerWidth * dpr; cv.height = innerHeight * dpr; ctx.setTransform(dpr, 0, 0, dpr, 0, 0); }
addEventListener('resize', fit); fit();

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(c, w, h, detail) {
  const r = mulberry32(6);
  const g = c.createLinearGradient(0, 0, 0, h);
  g.addColorStop(0, 'hsl(205 50% 58%)'); g.addColorStop(1, 'hsl(205 35% 28%)');
  c.fillStyle = g; c.fillRect(0, 0, w, h);
  c.beginPath(); c.arc(w * 0.72, h * 0.22, Math.min(w, h) * 0.08, 0, Math.PI * 2); c.fillStyle = 'hsl(45 90% 75%)'; c.fill();
  for (let l = 0; l < 3; l++) {
    const pts = [];
    c.beginPath(); c.moveTo(0, h);
    for (let i = 0; i <= 7; i++) { const x = w * i / 7; const y = h * (0.5 + l * 0.13) - r() * h * 0.1; pts.push([x, y]); c.lineTo(x, y); }
    c.lineTo(w, h); c.closePath();
    c.fillStyle = 'hsl(' + (150 - l * 10) + ' 40% ' + (20 + l * 10) + '%)'; c.fill();
    if (detail) {
      c.strokeStyle = 'rgba(255,255,255,0.22)'; c.lineWidth = 0.6;
      c.beginPath(); pts.forEach(([x, y], i) => (i ? c.lineTo(x, y) : c.moveTo(x, y))); c.stroke();
      for (let i = 0; i < 14; i++) c.fillRect(r() * w, h * (0.5 + l * 0.13) - r() * h * 0.08, 1.2, 1.2);
    }
  }
}
function render(dividerX) {
  const w = innerWidth, h = innerHeight;
  ctx.clearRect(0, 0, w, h);
  ctx.imageSmoothingEnabled = true;
  paintScene(ctx, w, h, true);
  ctx.save(); ctx.beginPath(); ctx.rect(0, 0, dividerX, h); ctx.clip();
  const SW = 22, SH = 14;
  const small = document.createElement('canvas'); small.width = SW; small.height = SH;
  paintScene(small.getContext('2d'), SW, SH, false);
  ctx.imageSmoothingEnabled = false;
  ctx.drawImage(small, 0, 0, w, h);
  ctx.restore();
  ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(dividerX, 0); ctx.lineTo(dividerX, h); ctx.stroke();
  ctx.beginPath(); ctx.arc(dividerX, h / 2, 9, 0, Math.PI * 2); ctx.fillStyle = '#fff'; ctx.fill();
}
let x = 0.5, dir = 1;
setInterval(() => {
  render(innerWidth * x);
  x += dir * 0.006;
  if (x >= 0.82) dir = -1; if (x <= 0.18) dir = 1;
}, 40);
render(innerWidth * 0.5);

단순한 확대(bicubic 등)는 있는 픽셀을 보간해서 빈틈을 메우기 때문에 커질수록 뭉개지고 흐려집니다. 학습된 업스케일러는 저해상도·고해상도 이미지 쌍으로 학습되어, 원본엔 없던 그럴듯한 디테일(머리카락 결, 직물의 짜임 같은)을 새로 "추측해서" 채워 넣습니다.

확산 모델 계열의 업스케일러는 종종 확대된 이미지를 다시 img2img처럼 다룹니다 — 약간의 노이즈를 더한 뒤 낮은 강도로 다시 디노이징해서, 이미 있는 구조에 자연스럽게 맞아떨어지는 세부만 더합니다.

디테일을 "채워 넣는다"는 말은 원본에 없던 것을 만든다는 뜻이기도 합니다. 질감처럼 그럴듯하면 충분한 용도에는 좋지만, 글자나 얼굴처럼 원본에 충실해야 하는 부분에서는 모델이 지어낸 디테일이 오히려 왜곡으로 읽힐 위험이 있습니다.

아래 데모는 실제 업스케일러 대신, 같은 시드로 그린 장면을 왼쪽엔 낮은 해상도로 거칠게, 오른쪽엔 높은 해상도에 잔선·입자를 더해 또렷하게 그려서 대비를 보여주는 시뮬레이션입니다.

언제 쓰나

이미 만든 이미지를 더 큰 해상도로 뽑아야 할 때 씁니다. 문자나 얼굴처럼 정확도가 중요한 영역은 결과를 눈으로 확인하는 편이 안전합니다.