智能人像自动抠图——C++ 实现LFM 模型推理

本文介绍了如何使用Pytorch复现CVPR2019上提出的全自动人像抠图算法,该算法无需绿幕或trimap图,仅需原图即可进行高质量抠图。作者分享了实现代码,并展示了模型在人像抠图上的应用,包括在影视剪辑、直播娱乐等领域。虽然存在一些场景处理不佳的问题,但通过增加样本量和优化算法可以改进。模型及代码已上传至优快云供下载研究。
部署运行你感兴趣的模型镜像

前言

  1. 关于抠图,首先想到的是PS的抠图,要美工手动一点点的把细节抠出来,抠图的好坏取决一个美工对PS的熟悉程度,在人像抠图方面,对头发的处理更是耗时耗力的一件事,在拍证件照的照像馆都有固定的绿幕来挡掉复杂的背景,以免增加后期的工作量,那么有没有一种完全自动的抠图办法呢? [A Late Fusion CNN for Digital Matting] CVPR 2019 上的一编论文, 作者提出了全自动抠图这个算法,不需要绿幕,也不需要输入trimap图(有了解传统抠图算法的应该知道这个图是干吗用的),背景图也不需要。在只需要输入一张原图的情况下,就能很好的抠图。我用Pytorch复现了他的这个算法,并且效果还不错。
  2. 关于人像抠图的应用场景可谓无所不在,在影视剪辑、直播娱乐、线上教学、视频会议等场景中都有人像分割的身影,它可以帮助用户实时、精准地将人物和背景精准识别出来,实现更精细化的人物美颜、背景虚化替换、弹幕穿人等,我那时想复现这编论文也是因为公司想做证件照相关的项目。
  3. 我的编译环境是Windows 10 64位,IDE是VS2019,配置了OpenCV 4.5,实现语言是C++,用OpenCV的dnn来进行模型推理。

模型演示

1.实现代码

void LFMatting(const cv::Mat& cv_src,cv::Mat &cv_matting, cv::Mat& cv_dst, cv::dnn::Net& net, int target_w = 640, int target_h = 960);
void imshow(std::string name, const cv::Mat& img)
{
	cv::namedWindow(name, 0);
	int max_rows = 500;
	int max_cols = 600;
	if (img.rows >= img.cols && img.rows > max_rows) 
	{
		cv::resizeWindow(name, cv::Size(img.cols * max_rows / img.rows, max_rows));
	}
	else if (img.cols >= img.rows && img.cols > max_cols)
	{
		cv::resizeWindow(name, cv::Size(max_cols, img.rows * max_cols / img.cols));
	}
	cv::imshow(name, img);
}

void  mergeImage(std::vector<cv::Mat>& src_vor, cv::Mat& cv_dst, int channel)
{
	cv::Mat img_merge;
	cv::Size size(src_vor.at(0).cols * src_vor.size(), src_vor.at(0).rows);
	if (channel == 1)
	{
		img_merge.create(size, CV_8UC1);
	}
	else if (channel == 3)
	{
		img_merge.create(size, CV_8UC3);
	}
	for (int i = 0; i < src_vor.size(); i++)
	{
		cv::Mat cv_temp = img_merge(cv::Rect(src_vor.at(i).cols * i, 0, src_vor.at(i).cols, src_vor.at(i).rows));
		src_vor.at(i).copyTo(cv_temp);
	}

	cv_dst = img_merge.clone();
}

cv::Mat channelSwitching(const cv::Mat& cv_src)
{
	cv::Mat three_channel = cv::Mat::zeros(cv_src.rows, cv_src.cols, CV_8UC3);
	std::vector<cv::Mat> channels;
	if (cv_src.channels() == 1)
	{
		for (int i = 0; i < 3; i++)
		{
			channels.push_back(cv_src);
		}
		merge(&channels[0], channels.size(), three_channel);
	}

	return three_channel;
}


int main(int argc, char* argv[])
{
	cv::dnn::Net net = cv::dnn::readNet("model/graph_final_960_640.pb", "model/graph_final_960_640.pbtxt");

	std::string path = "images";
	std::vector<std::string> filenames;
	cv::glob(path, filenames, false);

	for (auto v : filenames)
	{
		cv::Mat cv_src = cv::imread(v,1);
		std::vector<cv::Mat> cv_dsts(3);
		cv::Mat	cv_matting, cv_dst;

		cv_dsts[0] = cv_src.clone();

		LFMatting(cv_src,cv_matting, cv_dsts[2], net);

		cv_dsts[1] = channelSwitching(cv_matting);
		cv_dsts[1].convertTo(cv_dsts[1], CV_8UC3, 255);
		
		mergeImage(cv_dsts, cv_dst, 3);

		cv::imwrite("dst.jpg", cv_dst);
		//cv::waitKey();
	}

	return 0;
}

void LFMatting(const cv::Mat& cv_src,cv::Mat &cv_matting, cv::Mat& cv_dst, cv::dnn::Net& net, int target_w,int target_h)
{
	cv::Size reso(target_h, target_w);
	cv::Mat blob = cv::dnn::blobFromImage(cv_src, 1.0, reso,
		cv::Scalar(127.156207, 115.917443, 106.031127), true, false);
	net.setInput(blob);
	std::vector<cv::Mat> outputs;
	std::vector<std::string> names = {
		"deFG_side_0_out/ResizeNearestNeighbor",
		"deBG_side_0_out/ResizeNearestNeighbor",
		"fusion_sigmoid_output/Sigmoid"
	};

	net.forward(outputs, names);
	auto t1 = cv::getTickCount();

	for (size_t i = 0; i < outputs.size(); ++i)
	{
		outputs[i] = outputs[i].reshape(0, { outputs[i].size[2], outputs[i].size[3] });
		cv::resize(outputs[i], outputs[i], cv_src.size(), 0.0, 0.0, cv::INTER_LINEAR);
	}

	cv::Mat fg = outputs[0];
	cv::Mat bg = outputs[1];
	cv::Mat alpha = outputs[2];
	cv_matting = fg.mul(alpha) + (1.0 - bg).mul(1.0 - alpha);

	cv_dst = cv::Mat::zeros(cv::Size(cv_src.cols, cv_src.rows), CV_8UC3);
	const int bg_color[3] = { 219,142,67 };
	float* alpha_data = (float*)alpha.data;
	for (int i = 0; i < cv_matting.rows; i++)
	{
		for (int j = 0; j < cv_matting.cols; j++)
		{
			float alpha_ = alpha_data[i * cv_matting.cols + j];
			cv_dst.at < cv::Vec3b>(i, j)[0] = cv_src.at < cv::Vec3b>(i, j)[0] * alpha_ + (1 - alpha_) * bg_color[0];
			cv_dst.at < cv::Vec3b>(i, j)[1] = cv_src.at < cv::Vec3b>(i, j)[1] * alpha_ + (1 - alpha_) * bg_color[1];
			cv_dst.at < cv::Vec3b>(i, j)[2] = cv_src.at < cv::Vec3b>(i, j)[2] * alpha_ + (1 - alpha_) * bg_color[2];
		}
	}
}

2.一些不错的效果:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
3.还有一些是场景处理不是很好。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
4. 我现在训练的样本在10000张左右,如果加大样本量或者优化下算法,是可以解决掉这些问题点。
5. 模型和源码都上传到优快云,感兴趣的可以下载试玩:https://download.youkuaiyun.com/download/matt45m/51419768

您可能感兴趣的与本文相关的镜像

PyTorch 2.5

PyTorch 2.5

PyTorch
Cuda

PyTorch 是一个开源的 Python 机器学习库,基于 Torch 库,底层由 C++ 实现,应用于人工智能领域,如计算机视觉和自然语言处理

### 使用OpenCV实现人物图像分割和抠图 #### 方法概述 为了实现高质量的人物图像分割和抠图,可以采用多种技术手段。其中一种常用的技术是使用`GrabCut`算法[^1]。此算法能够通过少量的用户交互操作获得较好的分割结果。 #### 实现步骤详解 ##### 准备工作 首先需要安装并导入必要的Python包: ```python import cv2 import numpy as np from matplotlib import pyplot as plt ``` ##### 加载待处理的人像图片 读取要进行抠图的目标图像文件,并显示原图以便观察效果: ```python img = cv2.imread('person.jpg') plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) plt.title('Original Image'), plt.xticks([]), plt.yticks([]) plt.show() ``` ##### 初始化掩模(Mask) 创建一个与输入图像大小相同的全零数组作为初始掩码(mask),用于标记哪些区域属于前景、背景或是未知状态: ```python mask = np.zeros(img.shape[:2],np.uint8) bgdModel = np.zeros((1,65),np.float64) fgdModel = np.zeros((1,65),np.float64) rect = (50,50,450,290) # 定义矩形框选范围,具体数值需根据实际图像调整 cv2.grabCut(img, mask, rect, bgdModel, fgdModel, 5, cv2.GC_INIT_WITH_RECT) ``` 此处定义了一个矩形区域(rectangle),用来指示大概的位置,在这个区域内执行初步的抓取切割(grab cut)过程。 ##### 处理后的掩膜应用到原始图像上 将经过`grabCut()`函数更新过的掩码转换成二值化形式,再以此为基础构建最终的结果图像: ```python mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8') img_cutout = img*mask2[:,:,np.newaxis] plt.subplot(121),plt.imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)) plt.title("Input"), plt.axis('off') plt.subplot(122),plt.imshow(cv2.cvtColor(img_cutout,cv2.COLOR_BGR2RGB)) plt.title("Output GrabCut"), plt.axis('off') plt.show() ``` 上述代码片段展示了如何利用`grabCut()`完成一次简单的人物抠图任务。对于更加复杂的情况,则可能还需要进一步优化参数设置或引入其他高级特性如深度学习模型辅助边缘检测等[^2]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

知来者逆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值