EMB visionVISION
데모 바로가기

emb-vision-sdk 소개

페이지에 이미 있는 <video> 를 셀렉터로 지정하면 그 위에 <canvas> 가 겹쳐지며 WebGPU(미지원 시 WebGL)로 화질을 실시간 개선하는 프레임워크 비의존 라이브러리입니다. 낮은 비트레이트 영상도 선명하게 복원해 OTT·스트리밍 서비스의 CDN 트래픽과 운영 비용을 줄입니다.

🔑 라이선스 키로 활성화emb-vision-sdk 는 설치 후 발급받은 라이선스 키로 동작합니다. 키를 지정하면 효과가 즉시 적용되고 2초 후 검증되며, 검증에 실패하면 효과를 끄고 원본으로 되돌립니다. 키 발급·도입 문의는 https://embracelabs.com/contact 를 이용해주세요.

소개 #

재생은 당신의 플레이어가, 화면 개선은 이 라이브러리가 담당합니다. 영상을 로드·디코드·재생하지 않으며, 대상 <video> 의 출력 화면만 GPU 로 개선합니다. 따라서 hls.js·dash.js·video.js·native 등 어떤 재생 방식과도 함께 쓸 수 있습니다.

  • 프레임워크 비의존 — React·Vue·Svelte·순수 JS 어디서나
  • WebGPU 우선, WebGL 자동 폴백, 저사양 기기 강도 자동 감쇠
  • 11종 프로필 + 업스케일(FSR/Lanczos) + 엣지 강조 + CNN 복원
  • 의존성 0 · ESM · 타입 정의 포함

설치 (npm) #

npm install emb-vision-sdk

설치 후 발급받은 라이선스 키enhance() 옵션의 licenseKey 로 전달하면 활성화됩니다. (키 발급: support@sgrsoft.com)

ESM 전용 패키지입니다. 번들러(Vite/webpack/Rollup) 또는 네이티브 ESM 환경에서 import 합니다.

import { enhance } from "emb-vision-sdk";

빠른 시작 #

<!-- 1) 페이지에 video 가 있고 -->
<video id="player" src="movie.mp4" controls></video>
// 2) 셀렉터만 넘기면 끝 — 캔버스가 video 위에 겹쳐지고 화질 개선 시작
import { enhance } from "emb-vision-sdk";

const fx = enhance("#player", {
  enabled: true,
  profile: "default",
  licenseKey: "{licenseKey}", // 발급받은 라이선스 키
});

별도 캔버스를 만들거나 렌더 루프를 돌릴 필요가 없습니다. {licenseKey} 자리에 발급받은 키를 넣으면 효과가 바로 시작되고 2초 후 검증됩니다(실패 시 원본으로 되돌림). 효과를 끄려면 fx.setEnabled(false), 정리하려면 fx.destroy() 를 호출합니다.

🎁 체험용 라이선스 키 — 아래 키로 바로 테스트해 보세요.
licenseKey: "VSK-ZP02Z-K3FJ9-4C4DQ-8EM6A"

http://localhost:{port} (로컬 개발 환경)에서는 도메인 검증이 면제되어 자유롭게 테스트할 수 있습니다. 운영 도메인에 적용하려면 도메인이 바인딩된 별도 키를 support@sgrsoft.com 으로 발급받으세요.

LLM 코드 생성 #

코딩 에이전트가 이 패키지를 정확히 사용하도록 돕는 압축 레퍼런스를 제공합니다. llms.txt 를 읽히면 옵션·프로필·제약을 한 번에 이해하고 정확한 코드를 생성합니다.

Pick your tool

사용 중인 LLM(예: ChatGPT, Claude, Copilot, Cursor 등)에 아래 프롬프트를 그대로 붙여넣습니다.

ChatGPT Claude Copilot Cursor
# 에이전트에게 그대로 요청
https://emb-vision-demo.web.app/llms.txt 읽고 hls.js 적용 예제코드 알려줘

JavaScript · /llms.txt ↗ iOS · /ios-llms.txt ↗ Android · /android-llms.txt ↗

기본 사용법 #

셀렉터 문자열 또는 HTMLVideoElement 를 직접 넘길 수 있습니다. licenseKey 에 발급받은 키({licenseKey})를 전달해 활성화합니다.

const video = document.querySelector("video");
const fx = enhance(video, {
  enabled: true,
  profile: "ott",
  licenseKey: "{licenseKey}", // 발급받은 라이선스 키
});

fx.update({ enabled: true, profile: "detail" }); // 프로필 변경
fx.setEnabled(false);                          // 잠깐 끄기(원본 노출)
fx.getState();                                // { backend, enabled, unsupported }
fx.destroy();                                // 정리 + 원본 복원
— 옵션을 바꿀 때 새로 enhance() 하지 말고 같은 핸들의 update() 를 사용하세요. 렌더러 재생성 비용이 없습니다.

프로필 #

상황별 권장 프리셋입니다. 개별 강도 옵션을 함께 지정하면 프로필 기본값을 덮어씁니다.

profile용도
defaultCinematic — 자연스럽고 즉시 체감되는 실사용 메인
detailUltra Detail — 시연/마케팅용 와우 효과
ott저비트레이트 스트리밍 압축 노이즈 완화
education텍스트/판서 가독성 강조
cctv야간/저조도 대비 강조
anime라인 강조 + 색면 평탄화
oled깊은 black + 영화적 대비
retroCRT scanline + warm gamma
entertainment예능/뮤직 — 얼굴·머리 라인 선명 + 원색(채도) 강조
interview인터뷰 — 발화자 얼굴선 강조 + 피부 디테일 스무딩
idol아이돌 직캠/무대 — 또렷한 이목구비 + 화사한 피부·원색
off효과 비활성(원본 출력)

업스케일 #

저해상도 영상을 캔버스 해상도로 SR 처리합니다. upscale 에 원하는 조합을 직접 지정합니다.

import { enhance } from "emb-vision-sdk";

enhance("#player", {
  enabled: true, profile: "ott",
  upscale: { enabled: true, target: "1080p", algorithm: "fsr", sharpness: 0.06 }, // 1080p + FSR EASU
});

target: source · 720p · 1080p · 1440p · 2x · 3x · custom. algorithm: none · nearest · bilinear · lanczos · fsr. sharpness: 0..0.2(그 이상은 halo).

엣지 강조 #

unsharp(classic)과 RCAS(halo-free)를 하나의 API로 통합했습니다.

enhance("#player", {
  enabled: true,
  edgeEnhancement: { enabled: true, algorithm: "rcas", strength: 0.5 },
});

프레임워크 연동 #

React

import { useEffect, useRef } from "react";
import { enhance } from "emb-vision-sdk";

function Player() {
  const ref = useRef(null);
  useEffect(() => {
    const fx = enhance(ref.current, { enabled: true, profile: "default" });
    return () => fx.destroy(); // 언마운트 시 정리
  }, []);
  return <video ref={ref} src="movie.mp4" controls />;
}

Vue 3

onMounted(() => { fx = enhance(video.value, { enabled: true }); });
onBeforeUnmount(() => fx?.destroy());

HLS / DASH 연동 #

재생은 평소처럼 hls.js·dash.js 로 하고, 같은 <video>enhance() 만 얹습니다.

import Hls from "hls.js";
import { enhance } from "emb-vision-sdk";

const video = document.querySelector("#player");
const hls = new Hls(); hls.loadSource(url); hls.attachMedia(video);

enhance(video, { enabled: true, profile: "ott" });

트래픽·비용 절감 #

낮은 비트레이트 영상을 전송하고 클라이언트에서 AI 화질 개선을 수행하면, 동일 체감 화질을 유지하면서 CDN 트래픽·저장공간·인프라 비용을 줄일 수 있습니다.

// 저비트레이트 소스 + OTT 프로필 + 업스케일
enhance("#player", {
  enabled: true, profile: "ott",
  upscale: { enabled: true, target: "1080p", algorithm: "fsr", sharpness: 0.06 },
});
운영 관점 권장 — 인코딩 비트레이트를 한 단계 낮추고 클라이언트 Enhancement 로 체감 화질을 보전하면, 전송량 감소가 곧 CDN 비용 절감으로 이어집니다.

API · enhance() #

enhance(target, options?) => EnhanceHandle
인자타입설명
targetstring | HTMLVideoElementCSS 셀렉터 또는 video 엘리먼트. video 가 아니면 throw.
optionsVideoEnhancementOptions생략 시 { enabled:true, profile:"default" }

API · 핸들 메서드 #

멤버설명
update(options)옵션 갱신(프로필/강도/렌더러 등)
setEnabled(boolean)효과 on/off 단축
getState(){ backend:"webgl"|"webgpu"|null, enabled, unsupported }
on(type, cb) / off(type, cb)"quality" 이벤트 구독/해제(화질 측정). on 은 해제 함수 반환
measureQuality(source, opts?)단일 프레임 온디맨드 화질 평가 → Promise<QualityResult>
destroy()캔버스 제거 + 원본 video 원복
video대상 video 엘리먼트
enhancer내부 VideoEnhancer 인스턴스(저수준 제어)

API · 옵션 #

필드타입 / 범위설명
enabledboolean필수. 효과 on/off
profileEnhancementProfile프로필 프리셋
renderer"auto"|"webgpu"|"webgl"기본 "auto"(WebGPU→WebGL)
sharpen contrast cleanup anime0..1개별 강도(프로필 덮어씀)
gamma0.5..2.0감마
localContrast darkLift0..1로컬 대비 / 암부 lift
upscaleUpscaleParams업스케일(알고리즘/target/sharpness)
edgeEnhancement{enabled,algorithm,strength}엣지 강조 통합 API
cnn cnn1CnnParamsCNN 복원(WebGPU 전용)
qualityQualityOptions화질 측정(FR/NR) + 이벤트(화질 측정)
autoReduceOnLowEndboolean저사양 자동 감쇠(기본 true)

API · 타입 · Exports #

함수 enhance 외에 저수준 클래스/유틸/타입도 함께 export 합니다.

import {
  enhance, VideoEnhancer,
  WebGLVideoRenderer, WebGPUVideoRenderer, EnhancementPipeline,
  PROFILE_LIST, getProfileDefaults, isHighResDisplay,
  resolveOutputSize, clampOutputSize, normalizeAlgorithm,
  loadCnnWeightsFromUrl, parseCnnWeights, buildIdentityCnnLayers, buildSharpenCnnLayers,
  setOpenCvConfig, measureQualityOnce,   // 화질 측정
} from "emb-vision-sdk";

CNN 화질 복원 #

외부 가중치(.bin)를 주입해 multi-layer CNN 으로 디테일을 복원합니다. WebGPU 백엔드에서만 적용됩니다.

import { enhance, loadCnnWeightsFromUrl } from "emb-vision-sdk";

const layers = await loadCnnWeightsFromUrl("/cnn-720to1080.bin", 1);
enhance("#player", {
  enabled: true, renderer: "webgpu",
  cnn: { enabled: true, layers },
});
주의 — renderer 가 WebGL 로 폴백되면 CNN 옵션은 무시됩니다. 외부 .bin 은 CORS 가 허용된 URL 이어야 합니다.

렌더러 백엔드 #

renderer 로 백엔드를 선택합니다. 기본 "auto" 는 WebGPU 를 시도하고 실패 시 WebGL 로 폴백합니다. 현재 활성 백엔드는 getState().backend 로 확인할 수 있습니다.

동작
autoWebGPU 시도 → 실패 시 WebGL (기본)
webgpuWebGPU 강제(미지원 시 WebGL 폴백)
webgl / webgl2WebGL 만 사용(CNN 미적용)

WebGPU vs WebGL 기능 차이 #

두 백엔드는 대부분의 후처리·업스케일을 동일하게 지원하지만, CNN 화질 복원은 WebGPU 에서만 동작합니다. renderer:"auto"(기본)는 WebGPU 를 우선 시도하고 미지원 시 WebGL 로 폴백하며, 활성 백엔드는 getState().backend 로 확인합니다.

기능WebGPUWebGL
업스케일 (FSR · Lanczos · bilinear · nearest)OO
엣지 강조 (RCAS · Unsharp)OO
대비 · 감마 · 노이즈 제거 · 톤 보정OO
스캔라인 등 스타일 필터OO
CNN 화질 복원 (cnn · cnn1)OX (무시됨)
연산 방식compute shaderfragment shader
대표 가용 환경Chrome/Edge 113+ 등WebGL1 지원 대부분
CNN 복원이 필요한 시나리오라면 renderer:"webgpu" 로 강제하거나, 런타임에 getState().backend === "webgpu" 인지 확인하세요. WebGL 로 폴백되면 cnn 옵션은 조용히 무시됩니다.

원본/개선 비교 #

효과를 켠 상태에서 원본 <video> 를 다시 보이게 하고, 개선 캔버스 오버레이를 clip-path 로 절반만 남기면 좌우 동시 비교를 구현할 수 있습니다(데모의 “동시 비교” 참고).

const ov = document.querySelector("[data-vpe-enhancement]");
ov.style.clipPath = "inset(0 0 0 50%)"; // 오른쪽 절반 = 개선
fx.video.style.visibility = "visible";       // 왼쪽 = 원본

화질 측정 Beta #

⚠️ Beta · 웹 전용 — 현재 웹(브라우저)에서만 지원되며 iOS / Android 는 지원하지 않습니다. API·동작 방식은 안정화 과정에서 변경될 수 있습니다. 라이브 데모는 화질 측정 페이지에서 확인하세요.

화질을 두 가지 방식으로 측정하고, 결과를 이벤트 또는 콜백(Promise) 으로 받습니다.

방식지표설명
Full-Reference (원본 대비)PSNR · SSIM · MS-SSIM원본(<video>) 대비 개선 출력(<canvas>)의 충실도. 실시간 측정.
No-Reference (무참조)proxy · BRISQUE원본 없이 단일 프레임의 인지 화질. proxy 는 실시간, brisque 는 온디맨드(OpenCV.js).

1) 실시간 측정 활성화 + 이벤트 수신

quality 옵션으로 측정을 켜면, 인핸스 동작 중 주기적으로 quality 이벤트가 발화됩니다. handle.on() 구독과 동시에 video 엘리먼트로 CustomEvent("vpe:quality") 도 디스패치됩니다.

const handle = enhance("#player", {
  enabled: true, profile: "default",
  quality: {
    reference:   { enabled: true, metrics: ["psnr", "ssim", "msssim"], intervalMs: 300 },
    noReference: { enabled: true, engine: "proxy", intervalMs: 300 },
    log: false,   // true 면 콘솔에 배지로 출력
  },
});

handle.on("quality", (e) => {
  if (e.kind === "reference") console.log(e.psnr, e.ssim, e.score);
  else                          console.log(e.engine, e.sharpness, e.score);
});

// 프레임워크 무관 — DOM 이벤트로도 동일하게 수신 가능
handle.video.addEventListener("vpe:quality", (e) => console.log(e.detail));

2) 온디맨드 평가 — measureQuality(source)

썸네일·캔버스·이미지·VideoFrame단일 프레임을 평가해 Promise 로 받습니다(이벤트로도 동시 발화). reference 를 함께 주면 FR, 아니면 NR 로 측정합니다.

// 무참조(NR) — 업로드한 썸네일 평가
const r = await handle.measureQuality(thumbnailImg);
console.log(r.score, r.sharpness, r.noise);

// 원본 대비(FR) — 개선 출력 canvas vs 원본 video
const fr = await handle.measureQuality(outputCanvas, { reference: handle.video });
console.log(fr.psnr, fr.ssim, fr.msssim);

이벤트 / 결과 필드 — QualityEvent

필드타입설명
kind"reference" | "noReference"측정 종류
scorenumber (0..100)정규화 통합 점수(높을수록 좋음)
resolution{ width, height }비교에 사용한 해상도
psnr ssim msssimnumberFR — PSNR(dB) / SSIM·MS-SSIM(0..1)
sharpness blockiness noise contrastnumberNR proxy 서브지표
engine brisquestring / numberNR 엔진 / BRISQUE 값(낮을수록 좋음)

quality 옵션

필드타입 / 기본설명
reference.enabledboolean원본 대비(FR) 측정 on/off
reference.metrics("psnr"|"ssim"|"msssim")[] / ["psnr","ssim"]계산할 FR 지표
noReference.enabledboolean무참조(NR) 측정 on/off
noReference.engine"proxy"|"brisque"|"niqe" / "proxy"실시간은 proxy, brisque/niqe 는 온디맨드
reference.intervalMs noReference.intervalMsnumber / 500·1000발화 주기(ms)
logboolean / false콘솔 배지 출력

BRISQUE (OpenCV.js, 온디맨드)

딥하지 않은 고전 NR 지표 BRISQUE 는 OpenCV.js 를 지연 로드해 사용합니다(코어 번들 미포함, ~8MB). 모델 yml 경로를 setOpenCvConfig 로 주입한 뒤 engine: "brisque" 로 호출하세요. 미가용 시 proxy 로 자동 폴백합니다.

import { setOpenCvConfig } from "emb-vision-sdk";
setOpenCvConfig({
  scriptUrl:    "https://docs.opencv.org/4.x/opencv.js",
  brisqueModel: "/models/brisque_model_live.yml",
  brisqueRange: "/models/brisque_range_live.yml",
});
const r = await handle.measureQuality(frame, { engine: "brisque" });
📐 FR 은 “충실도” 지표입니다 — 인핸스는 원본을 의도적으로 변형하므로, 원본 대비 PSNR/SSIM 은 구조 보존 정도로 읽어야 합니다. 너무 낮으면 과변형·아티팩트, 너무 높으면 효과가 미미하다는 뜻입니다. 업스케일 출력은 원본 해상도로 다운샘플해 비교하므로 해상도 차이는 자동 정렬됩니다.
주의 — 측정은 출력 픽셀을 읽어오므로 CORS 가 허용된 영상이어야 합니다(crossorigin="anonymous"). 교차 출처로 오염된 캔버스는 측정값을 얻을 수 없습니다.

브라우저 지원 #

Chrome / Edge 113+WebGPU
그 외 WebGL1 지원 브라우저WebGL 폴백
둘 다 불가효과 끄고 원본 노출(getState().unsupported === true)
교차 출처(CORS) 영상은 <video crossorigin="anonymous"> 와 서버 CORS 헤더가 필요합니다(canvas 오염 방지). 자세한 제약은 제약 · 한계 참고.

제약 · 한계 #

이 라이브러리는 <video> 프레임을 GPU 텍스처로 업로드해 처리합니다. 따라서 프레임을 읽을 수 없는 환경에는 적용되지 않습니다. 도입 전 아래 제약을 반드시 확인하세요.

DRM 보호 콘텐츠 — 적용 불가

Widevine · PlayReady · FairPlay 등 DRM(EME)으로 보호된 영상에는 적용할 수 없습니다. 보호 콘텐츠는 브라우저가 secure path 로 렌더링해 <video> 프레임의 canvas/GPU 캡처를 차단하므로, 효과가 적용되지 않거나 빈/검은 화면이 됩니다. (캡처 시도 시 보안 오류)

적용 가능한 경우: DRM 미적용(클리어) 스트림, 무료/프리뷰 콘텐츠, 광고 등 보호되지 않은 <video>. DRM 콘텐츠가 핵심인 서비스는 적용 대상에서 제외하거나, 비보호 구간에만 사용하세요.

CORS — 교차 출처 영상

다른 도메인에서 불러오는 영상에 효과를 적용하려면 두 가지가 모두 필요합니다.

  • <video crossorigin="anonymous"> 속성
  • 영상 서버의 Access-Control-Allow-Origin 등 CORS 응답 헤더

둘 중 하나라도 없으면 canvas 가 tainted 되어 GPU 업로드가 보안 오류로 실패하고, 효과 없이 원본이 노출됩니다. HLS/DASH 세그먼트(.ts/.m4s) 서버에도 동일하게 CORS 가 적용돼야 합니다. 같은 출처(same-origin) 영상은 제약이 없습니다.

성능 · 환경

  • 처리량은 시청자 GPU 성능에 의존합니다. 저사양 PC·모바일에서는 autoReduceOnLowEnd(기본 true)로 강도를 자동 감쇠하지만, GPU 사용률·배터리·발열은 0 이 아닙니다.
  • WebGPU 미지원 시 WebGL 로 폴백하며 CNN 복원은 적용되지 않습니다(차이표).
  • 체감 화질·실측 FPS·전력 영향은 콘텐츠·코덱·기기에 따라 다르므로 도입 전 PoC 측정을 권장합니다.

포렌식 워터마크 Beta #

⚠️ Beta · 웹 전용 — 현재 웹(브라우저)에서만 지원되며 iOS / Android 는 지원하지 않습니다. Beta 기간 동안 평가용으로 제공되며, 정식 출시(Beta 종료) 이후에는 별도 라이선스가 필요할 수 있습니다. API·동작 방식은 안정화 과정에서 변경될 수 있습니다. 도입 검토는 support@sgrsoft.com 으로 문의해 주세요.

화면에 표시되는 영상 프레임에 육안으로 보이지 않는 고유 식별 마크를 삽입하고, 유출된 캡처·녹화본에서 이를 복원해 어느 재생본에서 나갔는지 출처를 추적하는 기능입니다. 화질 개선(enhance())과 독립적으로 동작하며, 재인코딩 없이 프레임(ImageData) 단위로 적용됩니다. embed · extract · formatId 세 함수로 구성됩니다.

삽입 — embed(image, id, options)

ImageData 에 식별 ID 를 제자리(in-place)로 삽입합니다. strength 는 QIM 양자화 폭 Δ(기본 24)로, 클수록 재압축에 견고하지만 가시성이 올라갑니다.

// 재생 중인 video 프레임을 canvas 로 캡처 → ImageData 추출
ctx.drawImage(video, 0, 0, w, h);
const frame = ctx.getImageData(0, 0, w, h);

embed(frame, 10293, { strength: 24 });   // 시청자/세션 ID 삽입(보이지 않음)
ctx.putImageData(frame, 0, 0);

복원 — extract(image, options)

유출본 이미지에서 ID 를 복원합니다. { id, ok, confidence } 를 반환하며 ok=false 면 워터마크 없음/복원 실패입니다.

const r = extract(frame, { strength: 24 });
if (r.ok) {
  console.log(formatId(r.id), r.confidence);  // "U-10293", 0.98
}
🔎 추적은 코드 없이도 가능합니다 — 위 extract() 를 직접 구현하지 않아도, 사이트에서 제공하는 워터마크 디코더 에 유출·캡처된 이미지를 올리기만 하면 숨겨진 세션 ID(U-…)를 그 자리에서 복원합니다. 원본 영상도 필요 없습니다(blind) — 풀프레임 스크린샷일수록 복원율이 높습니다. 즉, 서비스에는 삽입(embed)만 연동해 두면, 유출이 발생했을 때 추적은 디코더 페이지에 이미지를 올리는 것만으로 즉시 처리할 수 있어 별도 추출 코드·도구를 만들 필요가 없습니다.

payload 는 magic + ID + CRC16(총 64bit)을 8×8 타일로 반복 삽입하고 다수결로 복원하므로, JPEG 재압축 등 일부 손실 후에도 일정 수준 생존합니다. 단, DRM 을 대체하지는 않으며 강한 편집/크롭·심한 재압축에서는 복원이 실패할 수 있습니다.

현재 제공 형태 — 포렌식 워터마크는 npm 패키지(emb-vision-sdk)와 별개의 PoC 모듈로 제공됩니다. 라이브 데모에서 삽입·추적·견고성을 직접 확인할 수 있습니다.