티스토리 뷰
MediaPipe와 Canvas를 사용하여 비디오에서 가상 배경을 구현했다. 이 과정에서 globalCompositeOperation에 대한 지식이 필요했고, 이 과정에서 공부한 내용을 기록해보려고 한다. 추가적으로 가상 배경에는 globalCompositeOperation를 어떻게 활용했는지도 정리해보려고 한다. Ɛ( ◕ _· ◕)3
Canvas API의 일부로, <canvas> 요소의 드로잉 표면에 대한 2D 렌더링 컨텍스트를 제공한다. 이 인터페이스는 도형, 텍스트, 이미지 등의 객체를 그리는데 사용한다.
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
The CanvasRenderingContext2D.globalCompositeOperation property of the Canvas 2D API sets the type of compositing operation to apply when drawing new shapes.
globalCompositeOperation 속성은 소스(캔버스에 새로 그릴 도형)가 캔버스에 그려지는 방식과 대상(이미 캔버스에 그려진 도형)이 어떻게 그려지는지를 설정하는 역할을 한다. 아래 예제에 있는 사각형과 원이 그려진 캔버스에 globalCompositeOperation를 통해 다양한 블렌딩 모드를 확인할 수 있다.
예제 코드는 W3C에서 제공되는 예제 코드를 변형하여 사용하였다. (예제 링크)
<!DOCTYPE html>
<html>
<style>
body {
font-weight:bold;
}
canvas {
border: 1px solid #dddddd;
border-radius: 16px;
margin-bottom : 16px;
margin-right: 16px;
}
</style>
<body>
<h1>HTML5 Canvas</h1>
<h2>The globalCompositeOperation Property</h2>
<script>
const gco = new Array();
gco.push("source-atop");
gco.push("source-in");
gco.push("source-out");
gco.push("source-over");
gco.push("destination-atop");
gco.push("destination-in");
gco.push("destination-out");
gco.push("destination-over");
gco.push("lighter");
gco.push("copy");
gco.push("xor");
for (let n = 0; n < gco.length; n++) {
document.write("<div id='p_" + n + "' style='float:left;'>" + gco[n] + ":<br>");
const canvas = document.createElement("canvas");
canvas.width = 200;
canvas.height = 190;
document.getElementById("p_" + n).appendChild(canvas);
const ctx = canvas.getContext("2d");
ctx.fillStyle = "#444444";
ctx.fillRect(20, 20, 100, 100);
ctx.globalCompositeOperation = gco[n];
ctx.beginPath();
ctx.fillStyle = "#FFCD41";
ctx.arc(120, 120, 50, 0, 2 * Math.PI);
ctx.fill();
document.write("</div>");
}</script>
</body>
</html>
source-atop는 소스 이미지를 대상 이미지 위에 표현한다. 대상 이미지 바깥쪽에 있는 소스 이미지는 나타나지 않는다.
souce-in은 소스 이미지를 대상 이미지 내부에 표현한다. 대상 이미지 안에 있는 소스 이미지의 일부만 나타나며, 대상 이미지는 투명하게 처리된다.
souce-out은 소스 이미지를 대상 이미지 바깥쪽에 표현한다. 대상 이미지 바깥쪽에 있는 소스 부분만 보이며 대상 이미지는 투명하게 처리된다.
source-over는 소스 이미지를 대상 이미지 위에 표현한다.
destination-atop는 대상 이미지를 소스 이미지 위에 표현한다. 단 소스 이미지 바깥쪽에 존재하는 대상 이미지는 보이지 않게 처리한다.
destination-in는 대상 이미지를 소스 이미지 내부에 표현한다. 소스 이미지 안에 있는 대상 이미지의 부분만이 보이며, 소스 이미지는 투명하게 처리된다.
destination-out은 대상 이미지를 소스 이미지 바깥쪽에 표현한다. 소스 이미지 바깥쪽에 있는 대상 이미지만이 화면에 나타나며, 소스 이미지는 투명하게 처리된다.
destination-over는 대상 이미지를 소스 이미지 위에 표현한다.
lighter는 소스 이미지와 대상 이미지를 더하여 밝은 색상을 만들어낸다. 두 이미지의 색이 합쳐져 더 밝은 효과를 생성한다.
copy는 소스 이미지만 표시하고 대상 이미지는 투명하게 처리한다.
마지막으로 xor는 소스 이미지와 대상 이미지를 배타적 OR연산을 사용하여 결합한다.(두 이미지간 서로 겹치는 부분을 투명하게 처리한다.)
사용자의 마스킹된 이미지, 가상 배경 이미지, 그리고 원본 비디오 프레임 이미지가 필요하다. 사용자의 마스킹된 이미지는 @mediapipe/tasks-vision를 사용하여 생성하였다.
사용자의 마스킹된 이미지와 가상 배경 이미지를 추가한 캔버스에 soruce-out를 적용하면 가상 배경에 마스크된 사용자가 뚫린 형태의 이미지가 만들어진다. 이렇게 만들어진 이미지를 다시 원본 비디오 프레임과 합쳐 가상 배경이 적용된 것처럼 만들 수 있다.
++ 추가적으로 canvas를 사용하며 아래와 같은 오류를 마주쳤다!
Uncaught (in promise) TypeError: Failed to execute 'drawImage' on 'CanvasRenderingContext2D': The provided value is not of type '(CSSImageValue or HTMLCanvasElement or HTMLImageElement or HTMLVideoElement or ImageBitmap or OffscreenCanvas or SVGImageElement or VideoFrame)'.
내가 사용했던 drawImage()는 첫 번째 인자로 그릴 이미지를 받는다.
drawImage(image, dx, dy)
drawImage(image, dx, dy, dWidth, dHeight)
drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
내가 사용했던 drawImage()는 첫 번째 인자로 그릴 이미지를 받는다. 가상 배경 설정을 구현하며, drawImage()의 첫 번째 인자로 마스킹된 사용자의 비디오 프레임을 넘겨주었다. 이 과정에서 아직 마스킹된 데이터가 생성되지 않은 시점에 drawImage()가 실행되어 발생된 문제였다.
- Total
- Today
- Yesterday