我正在开发一个应用程序,我需要将绿色/蓝色背景屏幕上的人物网络摄像头视频(绿色/蓝色的色调可能不同)与静态背景图像或视频叠加。
不幸的是,我找不到任何专业的解决方案(以C#框架/库的形式)来解决色键图像过滤问题。
我决定使用 OpenCV 库编写自己的解决方案。使用 @Burak 的 Python 解决方案,我通过将代码移植到 C# 部分解决了这个问题:
private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
Mat mask, result = null;
var worker = (BackgroundWorker)sender;
while (!worker.CancellationPending)
{
using (var frame = videoCap.RetrieveMat())
{
// Get a chroma key mask
mask = ChromaKeyMask(frame);
if (videoBack != null)
{
if (videoBack.Width != frame.Width && videoBack.Height != frame.Height)
{
videoBack = videoBack.Resize(frame.Size());
}
result = videoBack.Clone();
frame.CvtColor(ColorConversionCodes.RGB2BGR).CopyTo(result, mask);
}
Dispatcher.Invoke(() =>
{
FrameImage.Source = frame.ToWriteableBitmap();
MaskImage.Source = mask.ToWriteableBitmap();
ResultImage.Source = result?.CvtColor(ColorConversionCodes.BGR2RGB).ToWriteableBitmap();
mask?.Dispose();
result?.Dispose();
});
}
Thread.Sleep(20);
}
}
private Mat ChromaKeyMask(Mat src)
{
Mat mask;
using (var hsvMat = src.CvtColor(ColorConversionCodes.BGR2HSV))
{
var min = new Scalar(minH, minS, minV);
var max = new Scalar(maxH, maxS, maxV);
// Threshold the HSV image to extract green color
mask = hsvMat.InRange(min, max);
Cv2.BitwiseNot(mask, mask);
// Smooth edges of the mask
var size = new OpenCvSharp.Size(3, 3);
var kernel = Cv2.GetStructuringElement(MorphShapes.Rect, size);
Cv2.MorphologyEx(mask, mask, MorphTypes.Open, kernel);
Cv2.GaussianBlur(mask, mask, size, 0, 0);
}
return mask;
}
此代码可以工作,但对于生产级来说仍然不够好:蒙版“不稳定”和“浮动”,最终图像与背景组合后有一个绿色的“光环”。
所以,我的问题是:
mask = hsvMat.InRange(min, max);
得到的蒙版相差不远?我真的很抱歉(我是 OpenCV 和图像处理的新手),但我要求的是特定工作解决方案的答案,而不是关于色度键的一般想法(是的,我已经阅读了维基百科文章并观看了StackOverflow 上有很多问题/答案)。不需要使用C#; Python 和 C++ 都可以正常工作。
在这里您可以找到修改后的@Barak Python脚本,其中包含用于测试的图像。
我尝试了来自 https://www.visioforge.com 和 https://www.graphicsmill.com 的不同 SDK:非常不稳定和/或简单无法工作。
我需要一个专业级色度键过滤解决方案,来实时处理网络摄像头流。
绿屏/蓝屏色度键控的常用方法是依靠主体和背景之间的色相值差异。 然而,在您提供的示例图像中,主题图像本身存在一些绿色色调,这使得仅依靠 HSV 量是不够的。 我使用了另一种方法将图像转换为 YUV,因为在 YUV 的 V 分量中,主体图像的边缘和绿色背景之间存在明显的区别。 我对 OpenCV 不熟悉,所以我使用了 LEADTOOLS 成像 SDK(披露:我为其供应商工作)并使用您提供的图像进行了测试。这是我的 C# 代码:
/*
This function assumes the following:
- Both images have same dimensions
- Both images have same bits per pixel, preferably 24
- The location (10, 10) is part of the green background
*/
private void GreenKeyCombine(RasterImage subject, RasterImage background)
{
ColorSeparateCommand sep = new ColorSeparateCommand(ColorSeparateCommandType.Yuv);
sep.Run(subject);
sep.DestinationImage.Page = 3; // Take the V plane of YUV and use it as a mask
RasterImage mask = sep.DestinationImage.Clone();
IntensityDetectCommand intDet = new IntensityDetectCommand(123, 255, RasterColor.Black, RasterColor.White, IntensityDetectCommandFlags.Master);
intDet.Run(mask);
// Create a region in the mask image from the area surrounding the subject
mask.AddMagicWandToRegion(10, 10, RasterColor.Black, new RasterColor(5, 5, 5), RasterRegionCombineMode.Set);
// Transfer the region to the subject. Use SetNot to invert the region and make it cover the subject person’s area
subject.SetRegion(null, mask.GetRegion(null), RasterRegionCombineMode.SetNot);
LeadRect rc = new LeadRect(0, 0, subject.Width, subject.Height);
// Combine the subject with the background. The region causes the subject to be copied alone without surrounding area
CombineFastCommand comb = new CombineFastCommand(background, rc, new LeadPoint(0, 0), CombineFastCommandFlags.SourceCopy);
comb.Run(subject);
}
这是我用你的图像得到的结果:
请注意,此处理是使用静态位图完成的,但通过使用成像 SDK 和多媒体 SDK,使用网络摄像头源或任何类型的视频应该很容易完成此处理。这是在 LEADTOOLS 附带的 2 个演示中完成的,它们是 DrawOnVideoDemo 和 CallbackDemo。如果您想尝试代码,您可以从此页面获得 SDK 的免费评估。