抠图算法(交互式)以及证件照的自动抠图

研究抠图算法有段时间,颇有所得 研究的算法包括 Grabcut 、Shared Sample Alpha Matting、robust matting 以及lazysnapping. 研究抠图算法主要是应用于移动端的交互式抠图,用户输入 前景像素、背景像素的Trimap图作为mask ,对原图进行分割。交互式抠图算法达到可以商用的标准主要有两点:1、精确的分割出前景像素与背景像素,2、分割后的主体或前景部分,边缘过渡效果良好,在新的背景上,视觉上没有违和感。

从实现上的难易程度、对用户的操作要求、以及算法的复杂度上考虑主要采用并实现了 Grabcut、lazysnapping算法。Shared Sample Alpha Matting在交互上用户的操作成本高一点效果一般,没有具体去实现只是看过 imageshop 一篇博客中实现的效果,大家有兴趣可以去下载看一看 链接地址:点击打开链接.考虑算法实现主要用在移动端,用户的交互感受是蛮重要的。我采用了Grabcut以及参考了lazysnapping 的思路,做了两种交互抠图实现方案。

其中,Grabcut opencv提供了实现API ,我也是直接用的OpenCV的 实现接口,总体效果还可以接受,Grabcut缺点是 对于前景 与背景像素相差不大的部分不能有效的分割,它采用图论理论,前景像素与背景像素建立无向图,利用图割maxflow-mincut理论 分割 前景像素 与 背景像素,grabcut论文中也有提及边缘处理的方法,但是没有给出具体的实现,以及找不到相关的资料,也就不了了之。对于前景与背景差异性较大情况,grabcut表现还是不错的,其实微软的办公软件 World 、PPT中都有删除背景功能,它们采用的就是Gabcut方法,首先用矩形框框选出主体,执行算法得到初分割图,然后再用画笔标记 做区域针对性分割。该算法效果就不展示了,感兴趣的可以利用opencv 实现一下。需要注意的一点是 每次算法的输入的是原图srcImage跟mask 图。下面对基于OpenCV 的Grabcut的实现简要说一下,它包括两个接口:第一个是输入参数是原图和矩形框,第二个接口输入参数是原图和mask图。

1、cv::grabCut(srcImg, mask, retangle, bgModle, fgModle, 5, cv::GC_INIT_WITH_RECT);

srcImg:原图,ratangle:矩形框,bgModle: 与原图大小一致Mat类型,fgModle:与原图大小一致 Mat类型, 5:高斯混合建模迭代次数,cv::GC_INIT_WITH_RECT:标志位。

重点说下mask参数:接口输出参数mask,mask代表初步分割的结果,有四种值:cv::GC_FGD(前景像素)、cv::GC_BGD(背景像素)、cv::GC_PR_FGD(可能前景像素)、GC_PR_BGD(可能背景像素),根据mask图可以得到分割后的二值图:

cv::Mat4b resultMaskToMatrix(int w, int h){
	cv::Mat cvMat(h, w, CV_8UC4);
	cvMat.setTo(0);

	unsigned char *data = mask.data;
	unsigned char *data2 = cvMat.data;
	foreCount = 0;
	int fgd = 0, bgd = 0, pfgd = 0, pbgd = 0;

	for (int y = 0; y < h; y++){
		for (int x = 0; x < w; x++){
			int index = w * y + x;
			int offset = 4 * (w * y + x);
			if (data[index] == cv::GC_FGD){
				//cvMat.at<cv::Vec4b>(cv::Point(x, y)) == cv::Vec4b(0, 0, 0, 255);
				data2[offset] = 255;
				data2[offset + 1] = 255;
				data2[offset + 2] = 255;
				data2[offset + 3] = 255;
				fgd++;
			}
			else if (data[index] == cv::GC_BGD){
				//cvMat.at<cv::Vec4b>(cv::Point(x, y)) == cv::Vec4b(255, 255, 255, 255);
				data2[offset] = 0;
				data2[offset + 1] = 0;
				data2[offset + 2] = 0;
				data2[offset + 3] = 255;
				bgd++;
			}
			else if (data[index] == cv::GC_PR_FGD){
				//cvMat.at<cv::Vec4b>(cv::Point(x, y)) == cv::Vec4b(0,  0,  0,  255);
				data2[offset] = 255;
				data2[offset + 1] = 255;
				data2[offset + 2] = 255;
				data2[offset + 3] = 255;
				pfgd++;
			}
			else if (data[index] == cv::GC_PR_BGD){
				//cvMat.at<cv::Vec4b>(cv::Point(x, y)) == cv::Vec4b(255, 255, 255, 255);
				data2[offset] = 0;
				data2[offset + 1] = 0;
				data2[offset + 2] = 0;
				data2[offset + 3] = 255;
				pbgd++;
			}
		}
	}
	foreCount = pfgd;
	printf("fgd = %d, bgd = %d, pfgd = %d, pbgd = %d, total = %d, width * height = %d\n", fgd, bgd, pfgd, pbgd, fgd + bgd + pfgd + pbgd, w*h);
	printf("foreCount = %d\n", foreCount);
	return cvMat;
}

下一步,利用分割后的二值图和原图结合alpha Matting得到分割效果图,即:dstImage 除了主体之外,其余是透明效果

2、cv::grabCut(srcImg, maskGray, retangle, bgModle, fgModle, 5, cv::GC_INIT_WITH_MASK);

maskGray:用户输入标记的信息转换后结合上一步的mask得到像素种类信息图,retangle:输入为空,cv::GC_INIT_WITH_MASK:标志位。

maskGray获取过程图下,在这里用户的输入标记:红色标记表示背景像素,蓝色标记代表前景像素。

cv::Mat1b regularMask(cv::Mat maskImg){

	cv::Mat1b makers = mask;
	unsigned char *data = makers.data;
	unsigned char *tempdata = maskImg.data;
	int countFGD = 0, countBGD = 0, countRem = 0;

	for (int y = 0; y < maskImg.rows; y++){
		for (int x = 0; x < maskImg.cols; x++){
			int offset = 4 * (maskImg.cols* y + x);
			int red = tempdata[offset];
			int green = tempdata[offset + 1];
			int blue = tempdata[offset + 2];
			if (red == 255 && green == 255 && blue == 255){
				data[y * maskImg.cols + x] = cv::GC_FGD;
				countFGD++;
			}
			else if (red == 0 && green == 0 && blue == 0){
				data[y * maskImg.cols + x] = cv::GC_BGD;
				countBGD++;
			}
			else{
				countRem++;
			}
		}
	}
	foreCount = countFGD + foreCount;
	printf("countFGD = %d, countBGD = %d, countRem = %d,width * height = %d", countFGD, countBGD, countRem, maskImg.cols*maskImg.rows);
	printf("foreCount = %d", foreCount);
	return makers;
}

这样执行完之后,就会得到一副新的mask图,即:像素种类类型分布图,根据该mask 跟上述一样得到分割后的二值图,之后就是最后分割效果图。

第二种方案,是在抠图 app应用 都采用的方式(用户交互方式),天天抠图、美图秀秀以及图简。我主要参考lazysnapping的是实现方案,它也采用了 GraphCut以及图割理论,用到了超像素分割理论。最大的改进之处 是它建立图不再是基于像素而是基于像素块。文中提到先用 分水岭以及keans聚类得到聚类像素块。将这些像素块作为定点建立图,利用maxflow-mincut 进行分割。论文算法分两部分:1、前景、背景种子点像素标记 ,2、边缘处理。具体处理效果如下:

1.原图

2 scribble mask 图                                                            3、抠图效果图

此外,对于前景 于背景相差不大的物体,边界部分需要 背景、前景逐步标记来完善边界效果。

 

另外,对待证件照图片可以实现自动抠图

1、原图                                                                            2、自动抠像效果图

3、背景替换效果图

给出自己做的一个测试demon 百度网盘地址:点击打开链接

欢迎项目合租与交流,本博客为原创,如有转载请注明转载地址!联系邮箱:215667528@qq.com

阅读更多
换一批

没有更多推荐了,返回首页