VSR(비디오 슈퍼 해상도)은 사람들의 저해상도 비디오 스트림을 지능적으로 확장하여 대역폭 제한, 열악한 네트워크 조건, 압축 또는 낮은 품질의 원본 콘텐츠로 인해 손실될 선명도와 세부 정보를 복원하는 AI 기반 비디오 업 샘플링 기술입니다.
앱에 VSR 기능을 추가하면 다음을 포함한 시나리오가 가능합니다.
- 네트워크 연결 불량을 통해 비디오 품질 향상
- CDN 비용을 줄이기 위한 대역폭 최적화
- 여러 참가자가 있는 그룹 영상 통화와 같은 대역폭 높은 시나리오
- 편집, 업로드 또는 보기에서 소셜 미디어 비디오 품질 향상
VSR 기능에는 현재 NPU가 있는 Copilot+ PC 가 필요합니다. 자세한 내용은 Copilot+ PC용 AI 애플리케이션 개발을 참조하세요.
이러한 VSR API는 ML(Machine Learning) 모델을 사용하며, 화상 통화 및 회의 앱, 사람의 얼굴을 말하는 소셜 및 짧은 형식 비디오와 같은 시나리오를 위해 특별히 설계되었습니다.
VSR은 현재 다음 해상도, 형식 및 FPS 범위를 지원합니다.
| 특성 | 지원되는 콘텐츠 |
|---|---|
| 입력 해상도 | 240p – 1440p |
| 출력 해상도 | 480p – 1440p |
| FPS(초당 프레임 수) 범위 | 15 fps – 60 fps |
| 입력 픽셀 형식 | BGR(ImageBuffer API), NV12(Direct3D API) |
| 출력 픽셀 형식 | BGR(ImageBuffer API), BGRA(Direct3D API) |
VideoScaler 세션 만들기
다음 예제에서는 VSR 세션을 만드는 방법을 보여줍니다. 먼저 ExecutionProviderCatalog 인스턴스를 가져오고 EnsureAndRegisterCertifiedAsync 를 호출하여 사용 가능한 모델을 로드합니다. VideoScalar 클래스에서 GetReadyState를 호출하여 비디오 스케일러가 프레임을 처리할 준비가 되었는지 확인합니다. 그렇지 않은 경우 EnsureReadyAsync 를 호출하여 비디오 스케일러를 초기화합니다.
private VideoScaler? _videoScaler;
protected override async Task LoadModelAsync(SampleNavigationParameters sampleParams)
{
try
{
var catalog = ExecutionProviderCatalog.GetDefault();
await catalog.EnsureAndRegisterCertifiedAsync();
var readyState = VideoScaler.GetReadyState();
if (readyState == AIFeatureReadyState.NotReady)
{
var operation = await VideoScaler.EnsureReadyAsync();
if (operation.Status != AIFeatureReadyResultState.Success)
{
ShowException(null, "Video Scaler is not available.");
}
}
_videoScaler = await VideoScaler.CreateAsync();
}
catch (Exception ex)
{
ShowException(ex, "Failed to load model.");
}
sampleParams.NotifyCompletion();
}
VideoFrame 크기 조정
다음 코드 예제에서는 VideoScaler.ScaleFrame 메서드를 사용하여 VideoFrame 개체에 포함된 이미지 데이터의 크기를 조정합니다. MediaFrameReader 클래스를 사용하여 카메라에서 VideoFrame을 가져올 수 있습니다. 자세한 내용은 MediaFrameReader를 사용하여 미디어 프레임 처리를 참조하세요. WinUI 커뮤니티 도구 키트 CameraPreview 컨트롤을 사용하여 카메라에서 VideoFrame 개체를 가져올 수도 있습니다.
다음으로, 입력 비디오 프레임에서 Direct3DSurface 를 가져오고 업 스케일링의 출력을 위해 다른 Direct3DSurface 를 만듭니다. VideoScaler.ScaleFrame 은 프레임을 업스케일링하기 위해 호출됩니다. 이 예제에서는 앱의 UI에 있는 이미지 컨트롤이 업스케일 프레임으로 업데이트됩니다.
private async Task ProcessFrame(VideoFrame videoFrame)
{
// Process the frame with super resolution model
var processedBitmap = await Task.Run(async () =>
{
int width = 0;
int height = 0;
var inputD3dSurface = videoFrame.Direct3DSurface;
if (inputD3dSurface != null)
{
Debug.Assert(inputD3dSurface.Description.Format == Windows.Graphics.DirectX.DirectXPixelFormat.NV12, "input in NV12 format");
width = inputD3dSurface.Description.Width;
height = inputD3dSurface.Description.Height;
}
else
{
var softwareBitmap = videoFrame.SoftwareBitmap;
if (softwareBitmap == null)
{
return null;
}
Debug.Assert(softwareBitmap.BitmapPixelFormat == BitmapPixelFormat.Nv12, "input in NV12 format");
width = softwareBitmap.PixelWidth;
height = softwareBitmap.PixelHeight;
}
try
{
if (inputD3dSurface == null)
{
// Create Direct3D11-backed VideoFrame for input
using var inputVideoFrame = VideoFrame.CreateAsDirect3D11SurfaceBacked(
Windows.Graphics.DirectX.DirectXPixelFormat.NV12,
width,
height);
if (inputVideoFrame.Direct3DSurface == null)
{
return null;
}
// Copy the software bitmap to the Direct3D-backed frame
await videoFrame.CopyToAsync(inputVideoFrame);
inputD3dSurface = inputVideoFrame.Direct3DSurface;
}
// Create or resize output surface (BGRA8 format for display)
if (_outputD3dSurface == null || _outputWidth != width || _outputHeight != height)
{
_outputD3dSurface?.Dispose();
// DXGI_FORMAT_B8G8R8A8_UNORM = 87
_outputD3dSurface = Direct3DExtensions.CreateDirect3DSurface(87, width, height);
_outputWidth = width;
_outputHeight = height;
}
// Scale the frame using VideoScaler
var result = _videoScaler!.ScaleFrame(inputD3dSurface, _outputD3dSurface, new VideoScalerOptions());
if (result.Status == ScaleFrameStatus.Success)
{
var outputBitmap = await SoftwareBitmap.CreateCopyFromSurfaceAsync(
_outputD3dSurface,
BitmapAlphaMode.Premultiplied);
return outputBitmap;
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"ProcessFrame error: {ex.Message}");
}
return null;
});
if (processedBitmap == null)
{
return;
}
DispatcherQueue.TryEnqueue(async () =>
{
using (processedBitmap)
{
var source = new SoftwareBitmapSource();
await source.SetBitmapAsync(processedBitmap);
ProcessedVideoImage.Source = source;
}
});
}
ImageBuffer를 사용하여 SoftwareBitmap 크기 조정
다음 코드 예제에서는 VideoScalar 클래스를 사용하여 SoftwareBitmap의 크기를 조정하는 방법을 보여 줍니다. 이 예제에서는 VSR API의 일반적인 사용을 나타내지 않습니다. Direct3D를 사용하는 것보다 성능이 떨어집니다. 그러나 이 예제를 사용하여 카메라 또는 비디오 스트리밍 파이프라인을 설정하지 않고 VSR API를 실험할 수 있습니다. ImageBuffer를 사용할 때 비디오 스케일러에 BGR8이 필요하므로 제공된 SoftwareBitmap의 픽셀 형식을 변환하려면 일부 도우미 메서드가 필요합니다.
이 문서의 예제 코드는 Windows AI API 샘플의 VSR 구성 요소를 기반으로 합니다.
public SoftwareBitmap ScaleVideoFrame(SoftwareBitmap inputFrame)
{
ImageBuffer inputImageBuffer = SoftwareBitmapExtensions.ConvertToBgr8ImageBuffer(inputFrame);
var size = (uint)(inputFrame.PixelWidth * inputFrame.PixelHeight * 3);
IBuffer outputBuffer = new global::Windows.Storage.Streams.Buffer(size);
outputBuffer.Length = size;
ImageBuffer outputImageBuffer = ImageBuffer.CreateForBuffer(
outputBuffer,
ImageBufferPixelFormat.Bgr8,
inputFrame.PixelWidth,
inputFrame.PixelHeight,
inputFrame.PixelWidth * 3);
var result = Session.ScaleFrame(inputImageBuffer, outputImageBuffer, new VideoScalerOptions());
if (result.Status != ScaleFrameStatus.Success)
{
throw new Exception($"Failed to scale video frame: {result.Status}");
}
return SoftwareBitmapExtensions.ConvertBgr8ImageBufferToBgra8SoftwareBitmap(outputImageBuffer);
}
소프트웨어 비트맵 확장 메서드
다음 도우미 메서드는 비디오 스칼라의 입력 및 출력 요구 사항에 맞게 BGRA8 및 BGR8 형식 간에 SoftwareBitmap을 변환합니다.
public static ImageBuffer ConvertToBgr8ImageBuffer(SoftwareBitmap input)
{
var bgraBitmap = input;
if (input.BitmapPixelFormat != BitmapPixelFormat.Bgra8)
{
bgraBitmap = SoftwareBitmap.Convert(input, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
}
int width = bgraBitmap.PixelWidth;
int height = bgraBitmap.PixelHeight;
byte[] bgraBuffer = new byte[width * height * 4];
bgraBitmap.CopyToBuffer(bgraBuffer.AsBuffer());
byte[] bgrBuffer = new byte[width * height * 3];
for (int i = 0, j = 0; i < bgraBuffer.Length; i += 4, j += 3)
{
bgrBuffer[j] = bgraBuffer[i];
bgrBuffer[j + 1] = bgraBuffer[i + 1];
bgrBuffer[j + 2] = bgraBuffer[i + 2];
}
return ImageBuffer.CreateForBuffer(
bgrBuffer.AsBuffer(),
ImageBufferPixelFormat.Bgr8,
width,
height,
width * 3);
}
public static SoftwareBitmap ConvertBgr8ImageBufferToBgra8SoftwareBitmap(ImageBuffer bgrImageBuffer)
{
if (bgrImageBuffer.PixelFormat != ImageBufferPixelFormat.Bgr8)
{
throw new ArgumentException("Input ImageBuffer must be in Bgr8 format");
}
int width = bgrImageBuffer.PixelWidth;
int height = bgrImageBuffer.PixelHeight;
// Get BGR data from ImageBuffer
byte[] bgrBuffer = new byte[width * height * 3];
bgrImageBuffer.CopyToByteArray(bgrBuffer);
// Create BGRA buffer (4 bytes per pixel)
byte[] bgraBuffer = new byte[width * height * 4];
for (int i = 0, j = 0; i < bgrBuffer.Length; i += 3, j += 4)
{
bgraBuffer[j] = bgrBuffer[i]; // B
bgraBuffer[j + 1] = bgrBuffer[i + 1]; // G
bgraBuffer[j + 2] = bgrBuffer[i + 2]; // R
bgraBuffer[j + 3] = 255; // A (full opacity)
}
// Create SoftwareBitmap and copy data
var softwareBitmap = new SoftwareBitmap(
BitmapPixelFormat.Bgra8,
width,
height,
BitmapAlphaMode.Premultiplied);
softwareBitmap.CopyFromBuffer(bgraBuffer.AsBuffer());
return softwareBitmap;
}
책임 있는 인공지능
다음 단계의 조합을 사용하여 이러한 이미징 API가 신뢰할 수 있고 안전하며 책임감 있게 빌드되도록 했습니다. 앱에서 AI 기능을 구현할 때 Windows의 책임 있는 생성 AI 개발에 설명된 모범 사례를 검토하는 것이 좋습니다.
이러한 VSR API는 ML(Machine Learning) 모델을 사용하며, 화상 통화 및 회의 앱과 같은 시나리오와 사람의 얼굴 말하기를 특징으로 하는 소셜 및 짧은 형식의 비디오를 위해 특별히 설계되었습니다. 따라서 다음 시나리오에서는 이러한 API를 이미지에 사용하지 않는 것이 좋습니다.
- 이미지에 잠재적으로 민감한 콘텐츠와 부정확한 설명이 포함된 경우 플래그, 지도, 지구본, 문화 기호 또는 종교적 기호와 같이 논란의 여지가 있을 수 있습니다.
- 의학적 조언이나 진단, 법적 콘텐츠 또는 재무 문서와 같은 정확한 설명이 중요한 경우