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 등)에 아래 프롬프트를 그대로 붙여넣습니다.
# 에이전트에게 그대로 요청
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 | 용도 |
|---|---|
default | Cinematic — 자연스럽고 즉시 체감되는 실사용 메인 |
detail | Ultra Detail — 시연/마케팅용 와우 효과 |
ott | 저비트레이트 스트리밍 압축 노이즈 완화 |
education | 텍스트/판서 가독성 강조 |
cctv | 야간/저조도 대비 강조 |
anime | 라인 강조 + 색면 평탄화 |
oled | 깊은 black + 영화적 대비 |
retro | CRT 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 },
});
API · enhance() #
enhance(target, options?) => EnhanceHandle
| 인자 | 타입 | 설명 |
|---|---|---|
target | string | HTMLVideoElement | CSS 셀렉터 또는 video 엘리먼트. video 가 아니면 throw. |
options | VideoEnhancementOptions | 생략 시 { 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 · 옵션 #
| 필드 | 타입 / 범위 | 설명 |
|---|---|---|
enabled | boolean | 필수. 효과 on/off |
profile | EnhancementProfile | 프로필 프리셋 |
renderer | "auto"|"webgpu"|"webgl" | 기본 "auto"(WebGPU→WebGL) |
sharpen contrast cleanup anime | 0..1 | 개별 강도(프로필 덮어씀) |
gamma | 0.5..2.0 | 감마 |
localContrast darkLift | 0..1 | 로컬 대비 / 암부 lift |
upscale | UpscaleParams | 업스케일(알고리즘/target/sharpness) |
edgeEnhancement | {enabled,algorithm,strength} | 엣지 강조 통합 API |
cnn cnn1 | CnnParams | CNN 복원(WebGPU 전용) |
quality | QualityOptions | 화질 측정(FR/NR) + 이벤트(화질 측정) |
autoReduceOnLowEnd | boolean | 저사양 자동 감쇠(기본 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 로 백엔드를 선택합니다. 기본 "auto" 는 WebGPU 를 시도하고 실패 시 WebGL 로 폴백합니다.
현재 활성 백엔드는 getState().backend 로 확인할 수 있습니다.
| 값 | 동작 |
|---|---|
auto | WebGPU 시도 → 실패 시 WebGL (기본) |
webgpu | WebGPU 강제(미지원 시 WebGL 폴백) |
webgl / webgl2 | WebGL 만 사용(CNN 미적용) |
WebGPU vs WebGL 기능 차이 #
두 백엔드는 대부분의 후처리·업스케일을 동일하게 지원하지만, CNN 화질 복원은 WebGPU 에서만 동작합니다.
renderer:"auto"(기본)는 WebGPU 를 우선 시도하고 미지원 시 WebGL 로 폴백하며, 활성 백엔드는 getState().backend 로 확인합니다.
| 기능 | WebGPU | WebGL |
|---|---|---|
| 업스케일 (FSR · Lanczos · bilinear · nearest) | O | O |
| 엣지 강조 (RCAS · Unsharp) | O | O |
| 대비 · 감마 · 노이즈 제거 · 톤 보정 | O | O |
| 스캔라인 등 스타일 필터 | O | O |
CNN 화질 복원 (cnn · cnn1) | O | X (무시됨) |
| 연산 방식 | compute shader | fragment shader |
| 대표 가용 환경 | Chrome/Edge 113+ 등 | WebGL1 지원 대부분 |
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 #
화질을 두 가지 방식으로 측정하고, 결과를 이벤트 또는 콜백(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" | 측정 종류 |
score | number (0..100) | 정규화 통합 점수(높을수록 좋음) |
resolution | { width, height } | 비교에 사용한 해상도 |
psnr ssim msssim | number | FR — PSNR(dB) / SSIM·MS-SSIM(0..1) |
sharpness blockiness noise contrast | number | NR proxy 서브지표 |
engine brisque | string / number | NR 엔진 / BRISQUE 값(낮을수록 좋음) |
quality 옵션
| 필드 | 타입 / 기본 | 설명 |
|---|---|---|
reference.enabled | boolean | 원본 대비(FR) 측정 on/off |
reference.metrics | ("psnr"|"ssim"|"msssim")[] / ["psnr","ssim"] | 계산할 FR 지표 |
noReference.enabled | boolean | 무참조(NR) 측정 on/off |
noReference.engine | "proxy"|"brisque"|"niqe" / "proxy" | 실시간은 proxy, brisque/niqe 는 온디맨드 |
reference.intervalMs noReference.intervalMs | number / 500·1000 | 발화 주기(ms) |
log | boolean / 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" });
crossorigin="anonymous").
교차 출처로 오염된 캔버스는 측정값을 얻을 수 없습니다.브라우저 지원 #
| Chrome / Edge 113+ | WebGPU |
| 그 외 WebGL1 지원 브라우저 | WebGL 폴백 |
| 둘 다 불가 | 효과 끄고 원본 노출(getState().unsupported === true) |
<video crossorigin="anonymous"> 와 서버 CORS 헤더가 필요합니다(canvas 오염 방지). 자세한 제약은 제약 · 한계 참고.제약 · 한계 #
이 라이브러리는 <video> 프레임을 GPU 텍스처로 업로드해 처리합니다. 따라서 프레임을 읽을 수 없는 환경에는 적용되지 않습니다. 도입 전 아래 제약을 반드시 확인하세요.
DRM 보호 콘텐츠 — 적용 불가
<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 #
화면에 표시되는 영상 프레임에 육안으로 보이지 않는 고유 식별 마크를 삽입하고, 유출된
캡처·녹화본에서 이를 복원해 어느 재생본에서 나갔는지 출처를 추적하는 기능입니다.
화질 개선(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 을 대체하지는 않으며 강한 편집/크롭·심한 재압축에서는 복원이 실패할 수 있습니다.
emb-vision-sdk)와 별개의
PoC 모듈로 제공됩니다. 라이브 데모에서 삽입·추적·견고성을 직접 확인할 수 있습니다.