抠图算法-Alpha Matting

article/2025/9/28 4:11:37

目录

    • 概述
    • graph cut
    • Alpha Matting

概述

对于抠图,比较简单的方法是图像分割,这是很老的方法,但这其实算不上真正意义的抠图,因为他的主要目的是用于图像之间块与块的分割。典型的就是grabcut算法,opencv上面有相应的优化好的算法。还有一种就是对于前后景的分割,叫做Alpha Matting,这是抠图的主要实现方法,好的算法对头发丝也能处理得很好,最近主要实现了2010年的一篇论文《Shared Sampling for Real-Time Alpha Matting》,这是比较出名的效果比较好的经典前后景分割算法。

graph cut

这部分原理不是很麻烦,网上随便一搜就能搜到。这里主要借助opencv的接口函数grabcut去实现。grabcut是在graph cut基础上改进的一种图像分割算法,网上有很多grabcut方面的论文,opencv的grabcut算法也是在此基础上优化封装的。这种方法的实现,需要人工交互框出一个矩形表示待处理的区域,矩形外都被视为背景,还可以在人工交互上用画笔绘画,绘画区域表示前景或者后景。
代码如下:

#include <iostream>
#include <opencv2\opencv.hpp>
#include <opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp> 
#include "opencv2/imgproc/imgproc.hpp"using namespace std;
using namespace cv;static void help()
{cout << "\nThis program demonstrates GrabCut segmentation -- select an object in a region\n""and then grabcut will attempt to segment it out.\n""Call:\n""./grabcut <image_name>\n""\nSelect a rectangular area around the object you want to segment\n" <<"\nHot keys: \n""\tESC - quit the program\n""\tr - restore the original image\n""\tn - next iteration\n""\n""\tleft mouse button - set rectangle\n""\n""\tCTRL+left mouse button - set GC_BGD pixels\n""\tSHIFT+left mouse button - set CG_FGD pixels\n""\n""\tCTRL+right mouse button - set GC_PR_BGD pixels\n""\tSHIFT+right mouse button - set CG_PR_FGD pixels\n" << endl;
}const Scalar RED = Scalar(0, 0, 255);
const Scalar PINK = Scalar(230, 130, 255);
const Scalar BLUE = Scalar(255, 0, 0);
const Scalar LIGHTBLUE = Scalar(255, 255, 160);
const Scalar GREEN = Scalar(0, 255, 0);const int BGD_KEY = CV_EVENT_FLAG_CTRLKEY;  //Ctrl键
const int FGD_KEY = CV_EVENT_FLAG_SHIFTKEY; //Shift键static void getBinMask(const Mat& comMask, Mat& binMask)
{if (comMask.empty() || comMask.type() != CV_8UC1)CV_Error(CV_StsBadArg, "comMask is empty or has incorrect type (not CV_8UC1)");if (binMask.empty() || binMask.rows != comMask.rows || binMask.cols != comMask.cols)binMask.create(comMask.size(), CV_8UC1);binMask = comMask & 1;  //得到mask的最低位,实际上是只保留确定的或者有可能的前景点当做mask
}class GCApplication
{
public:enum{ NOT_SET = 0, IN_PROCESS = 1, SET = 2 };static const int radius = 2;static const int thickness = -1;void reset();void setImageAndWinName(const Mat& _image, const string& _winName);void showImage() const;void mouseClick(int event, int x, int y, int flags, void* param);int nextIter();int getIterCount() const { return iterCount; }
private:void setRectInMask();void setLblsInMask(int flags, Point p, bool isPr);const string* winName;const Mat* image;Mat mask;Mat bgdModel, fgdModel;uchar rectState, lblsState, prLblsState;bool isInitialized;Rect rect;vector<Point> fgdPxls, bgdPxls, prFgdPxls, prBgdPxls;int iterCount;
};/*给类的变量赋值*/
void GCApplication::reset()
{if (!mask.empty())mask.setTo(Scalar::all(GC_BGD));bgdPxls.clear(); fgdPxls.clear();prBgdPxls.clear();  prFgdPxls.clear();isInitialized = false;rectState = NOT_SET;    //NOT_SET == 0lblsState = NOT_SET;prLblsState = NOT_SET;iterCount = 0;
}/*给类的成员变量赋值而已*/
void GCApplication::setImageAndWinName(const Mat& _image, const string& _winName)
{if (_image.empty() || _winName.empty())return;image = &_image;winName = &_winName;mask.create(image->size(), CV_8UC1);reset();
}/*显示4个点,一个矩形和图像内容,因为后面的步骤很多地方都要用到这个函数,所以单独拿出来*/
void GCApplication::showImage() const
{if (image->empty() || winName->empty())return;Mat res;Mat binMask;if (!isInitialized)image->copyTo(res);else{getBinMask(mask, binMask);image->copyTo(res, binMask);  //按照最低位是0还是1来复制,只保留跟前景有关的图像,比如说可能的前景,可能的背景}vector<Point>::const_iterator it;/*下面4句代码是将选中的4个点用不同的颜色显示出来*/for (it = bgdPxls.begin(); it != bgdPxls.end(); ++it)  //迭代器可以看成是一个指针circle(res, *it, radius, BLUE, thickness);for (it = fgdPxls.begin(); it != fgdPxls.end(); ++it)  //确定的前景用红色表示circle(res, *it, radius, RED, thickness);for (it = prBgdPxls.begin(); it != prBgdPxls.end(); ++it)circle(res, *it, radius, LIGHTBLUE, thickness);for (it = prFgdPxls.begin(); it != prFgdPxls.end(); ++it)circle(res, *it, radius, PINK, thickness);/*画矩形*/if (rectState == IN_PROCESS || rectState == SET)rectangle(res, Point(rect.x, rect.y), Point(rect.x + rect.width, rect.y + rect.height), GREEN, 2);imshow(*winName, res);
}/*该步骤完成后,mask图像中rect内部是3,外面全是0*/
void GCApplication::setRectInMask()
{assert(!mask.empty());mask.setTo(GC_BGD);   //GC_BGD == 0rect.x = max(0, rect.x);rect.y = max(0, rect.y);rect.width = min(rect.width, image->cols - rect.x);rect.height = min(rect.height, image->rows - rect.y);(mask(rect)).setTo(Scalar(GC_PR_FGD));    //GC_PR_FGD == 3,矩形内部,为可能的前景点
}void GCApplication::setLblsInMask(int flags, Point p, bool isPr)
{vector<Point> *bpxls, *fpxls;uchar bvalue, fvalue;if (!isPr) //确定的点{bpxls = &bgdPxls;fpxls = &fgdPxls;bvalue = GC_BGD;    //0fvalue = GC_FGD;    //1}else    //概率点{bpxls = &prBgdPxls;fpxls = &prFgdPxls;bvalue = GC_PR_BGD; //2fvalue = GC_PR_FGD; //3}if (flags & BGD_KEY){bpxls->push_back(p);circle(mask, p, radius, bvalue, thickness);   //该点处为2}if (flags & FGD_KEY){fpxls->push_back(p);circle(mask, p, radius, fvalue, thickness);   //该点处为3}
}/*鼠标响应函数,参数flags为CV_EVENT_FLAG的组合*/
void GCApplication::mouseClick(int event, int x, int y, int flags, void*)
{// TODO add bad args checkswitch (event){case CV_EVENT_LBUTTONDOWN: // set rect or GC_BGD(GC_FGD) labels{bool isb = (flags & BGD_KEY) != 0,isf = (flags & FGD_KEY) != 0;if (rectState == NOT_SET && !isb && !isf)//只有左键按下时{rectState = IN_PROCESS; //表示正在画矩形rect = Rect(x, y, 1, 1);}if ((isb || isf) && rectState == SET) //按下了alt键或者shift键,且画好了矩形,表示正在画前景背景点lblsState = IN_PROCESS;}break;case CV_EVENT_RBUTTONDOWN: // set GC_PR_BGD(GC_PR_FGD) labels{bool isb = (flags & BGD_KEY) != 0,isf = (flags & FGD_KEY) != 0;if ((isb || isf) && rectState == SET) //正在画可能的前景背景点prLblsState = IN_PROCESS;}break;case CV_EVENT_LBUTTONUP:if (rectState == IN_PROCESS){rect = Rect(Point(rect.x, rect.y), Point(x, y));   //矩形结束rectState = SET;setRectInMask();assert(bgdPxls.empty() && fgdPxls.empty() && prBgdPxls.empty() && prFgdPxls.empty());showImage();}if (lblsState == IN_PROCESS)   //已画了前后景点{setLblsInMask(flags, Point(x, y), false);    //画出前景点lblsState = SET;showImage();}break;case CV_EVENT_RBUTTONUP:if (prLblsState == IN_PROCESS){setLblsInMask(flags, Point(x, y), true); //画出背景点prLblsState = SET;showImage();}break;case CV_EVENT_MOUSEMOVE:if (rectState == IN_PROCESS){rect = Rect(Point(rect.x, rect.y), Point(x, y));assert(bgdPxls.empty() && fgdPxls.empty() && prBgdPxls.empty() && prFgdPxls.empty());showImage();    //不断的显示图片}else if (lblsState == IN_PROCESS){setLblsInMask(flags, Point(x, y), false);showImage();}else if (prLblsState == IN_PROCESS){setLblsInMask(flags, Point(x, y), true);showImage();}break;}
}/*该函数进行grabcut算法,并且返回算法运行迭代的次数*/
int GCApplication::nextIter()
{if (isInitialized)//使用grab算法进行一次迭代,参数2为mask,里面存的mask位是:矩形内部除掉那些可能是背景或者已经确定是背景后的所有的点,且mask同时也为输出//保存的是分割后的前景图像grabCut(*image, mask, rect, bgdModel, fgdModel, 1);else{if (rectState != SET)return iterCount;if (lblsState == SET || prLblsState == SET)grabCut(*image, mask, rect, bgdModel, fgdModel, 1, GC_INIT_WITH_MASK);elsegrabCut(*image, mask, rect, bgdModel, fgdModel, 1, GC_INIT_WITH_RECT);isInitialized = true;}iterCount++;bgdPxls.clear(); fgdPxls.clear();prBgdPxls.clear(); prFgdPxls.clear();return iterCount;
}GCApplication gcapp;static void on_mouse(int event, int x, int y, int flags, void* param)
{gcapp.mouseClick(event, x, y, flags, param);
}int main(int argc, char** argv)
{string filename = "input.png";Mat image = imread(filename, 1);if (image.empty()){cout << "\n Durn, couldn't read image filename " << filename << endl;return 1;}help();const string winName = "image";cvNamedWindow(winName.c_str(), CV_WINDOW_AUTOSIZE);cvSetMouseCallback(winName.c_str(), on_mouse, 0);gcapp.setImageAndWinName(image, winName);gcapp.showImage();clock_t start, end;for (;;){char c = cvWaitKey(0);switch ((char)c){case '\x1b':cout << "Exiting ..." << endl;goto exit_main;case 'r':cout << endl;gcapp.reset();gcapp.showImage();break;case 'n':int iterCount = gcapp.getIterCount();//cout << "<" << iterCount << "... ";start = clock();int newIterCount = gcapp.nextIter();end = clock();double endtime = (double)(end - start) / CLOCKS_PER_SEC;cout << "NO." << newIterCount << ": " << endtime * 1000  << "ms" << endl;if (newIterCount > iterCount){gcapp.showImage();//cout << newIterCount << ">" << endl;}elsecout << "rect must be determined>" << endl;break;}}exit_main:cvDestroyWindow(winName.c_str());return 0;
}

代码很简单,使用方法都有注释。核心就是grabcut函数。
下面是运行结果:
输入:
在这里插入图片描述
输出:
在这里插入图片描述
耗时:
在这里插入图片描述
No.1-No.7分别表示算法多次迭代,每次迭代的耗时,迭代次数越多,每次添加新的前后景标志的话,抠图效果会更好。可以看出这种算法的时间效果不太好。

Alpha Matting

这个算法是重点想介绍和实现的。主要实现了2010年的一篇论文《Shared Sampling for Real-Time Alpha Matting》,这是比较出名的效果比较好的经典前后景分割算法。
总结的手稿贴出一下:
在这里插入图片描述

Alpha matting算法研究的是如何将一幅图像中的前景信息和背景信息分离的问题,即抠图。我们把图像I分割成一个前景对象图像F,一个背景图像B和一个alpha matte α,于是就有了digital matting的数学定义: I=α×F+(1-α)×B。
算法的输入:原始图片,三分图(trimap)或“乱画图”(scribble)。
《Shared Sampling for Real-Time Alpha Matting》这篇论文中算法大致步骤如下:
(1)Expansion,针对用户的输入,对已知区域(前景或背景)进行小规模的扩展;
(2)Sample and Gather,对剩余的未知区域内的每个点按一定的规则取样,并选择出最佳的一对前景和背景取样点;
(3)Refinement,在一定的领域范围内,对未知区域内的每个点的最佳配对重新进行组合。
(4)Local Smoothing,对得到的前景和背景对以及透明度值进行局部平滑,以减少噪音。
关于这篇论文的源码给出下载地址:code
关于这篇论文的数据下载及论文原文地址:Shared Sampling for Real-Time Alpha Matting
不过下载下来后运行的时候出了一点小问题,主要就是mat、cvmat、IplImage之间数据传递的问题,把他们统一改成mat类型就没问题了。
下面是运行结果:
输入:
在这里插入图片描述
在这里插入图片描述
输出:
在这里插入图片描述
耗时:
在这里插入图片描述
可以看到使用它的数据效果还是很好,不过他也有缺点,就是应用的抠图场合的背景应该比较简单。


http://chatgpt.dhexx.cn/article/uxUZANIh.shtml

相关文章

Background Matting V2 学习

论文&#xff1a; [2012.07810] Real-Time High-Resolution Background Matting (arxiv.org) GitHub项目源码&#xff1a;GitHub - PeterL1n/BackgroundMattingV2: Real-Time High-Resolution Background Matting 目录 论文学习 方法设计&#xff1a; 网络模型&#xff1a; 训…

matting系列论文笔记(三):Boosting Semantic Human Matting with Coarse Annotations

matting系列论文笔记&#xff08;三&#xff09;&#xff1a;Boosting Semantic Human Matting with Coarse Annotations 论文链接: CVPR2020 Boosting Semantic Human Matting with Coarse Annotations [1] 代码&#xff1a;暂无 文章目录 matting系列论文笔记&#xff08;三…

Image Matting 图像抠图技术与深度学习抠图

Image Matting: 图像抠图技术是指从静态图像或者视频序列中抽取感兴趣目标的过程&#xff0c;在ps和视频编辑中有重要的应用。 1.Image Matting Matting 技术可以表示为下面的图&#xff0c;与语义分割不同&#xff0c;它可以针对感兴趣前景物体进行细节处理、包括细微的毛发和…

论文阅读——Deep Image Matting

一、摘要 强调image matting&#xff08;抠图&#xff09;的现实意义。已有的算法在前景和背景颜色相似或者拥有复杂的纹理时表现较差&#xff0c;主要原因有两个&#xff0c;一个是只运用到低维特征&#xff0c;另一个是缺少高维语境。所以这篇论文提出了深度模型算法可以解决…

【CVPR2022】Boosting Robustness of Image Matting with Context Assembling and Strong Data Augmentation

Boosting Robustness of Image Matting with Context Assembling and Strong Data Augmentation 中文题目 利用上下文组合和强数据增强的增强鲁棒图像抠图 paper&#xff1a;https://openaccess.thecvf.com/content/CVPR2022/papers/Dai_Boosting_Robustness_of_Image_Mattin…

图像抠图Image Matting算法调研

目录 1.Trimap和Strokes 2. 相关数据集 3.论文算法调研 3.1 Deep Image Matting 3.2 Semantic Image Matting 3.3 Background Matting 3.4 Background Matting V2 3.5 Semantic Human Matting 3.6 HAttMatting 3.7 MMNet&#xff1a;Towards Real-Time Automatic Por…

抠图技术及方法简介(Image Matting Overview)

之前接触过语义分割&#xff0c;所以在刚接触图像抠图时以为两者是差不多。语义分割是端到端的&#xff0c;对像素按照语义进行多分类&#xff0c;而抠图就是按照前景和背景进行二分类嘛&#xff1f;实际上这是错误的理解。语义分割重在对每个像素的语义理解&#xff0c;将相同…

Portrait Matting

文章作者为 Google Research 的软件工程师 Sergio Orts Escolano 和 Jana Ehman&#xff0c;文章发表于 2022 年 1 月 24 日。 Portrait Matting 网络 抠图是提取精确的 alpha 遮罩的过程&#xff0c;抠图假设图像是前景和背景图像的合成&#xff0c;因此每个像素的强度是前景…

Background Matting详解

转自&#xff1a;https://zhuanlan.zhihu.com/p/148265115?from_voters_pagetruehttps://www.aiuai.cn/aifarm1462.html 使用人工智能技术实现类似PhotoShop等工具的抠图功能是一个非常有趣且有科研前景的一个方向。和分割算法只有 和 两个值相比&#xff0c;抠图得到的边缘…

【Matting】MODNet:实时人像抠图模型-onnx python部署

上一篇博客【Matting】MODNet&#xff1a;实时人像抠图模型-笔记分析了MODNet的原理&#xff0c;本篇博客将使用python部署MODNet官方提供的onnx模型&#xff0c;其效果如下&#xff1a; 在线人像抠图体验&#xff1a;CV案例 相关部署链接&#xff1a; 【Matting】MODNet&…

【笔记】Robust High-Resolution Video Matting with Temporal Guidance

Robust High-Resolution Video Matting with Temporal Guidance算法笔记 一、算法简介二、网络结构三、训练1、数据集2、训练过程3、损失函数 Robust High-Resolution Video Matting with Temporal Guidance 论文地址 RobustVideoMatting 代码地址 Robust High-Resolution Vide…

【Matting】MODNet:实时人像抠图模型-笔记

paper&#xff1a;MODNet: Real-Time Trimap-Free Portrait Matting via Objective Decomposition (AAAI 2022) github&#xff1a;https://github.com/ZHKKKe/MODNet 抠图在线体验&#xff1a;CV案例 部署教程&#xff1a; 【Matting】MODNet&#xff1a;实时人像抠图模型…

Image Matting 客观评价指标、数据集及主观评价

Image Matting 客观评价指标、数据集及主观评价 2021.7更新 PPM-100数据集已经开放&#xff0c;GitHub&#xff0c;详情见下文章节2.4 目录 Image Matting 客观评价指标、数据集及主观评价2021.7更新 客观评价指标1. 精度1.1 SAD1.2 MSE 均方误差1.3 Gradient error1.4 Conne…

深度学习(7)之图像抠图 Image Matting算法调研

目录 1.Trimap和Strokes 2. 相关数据集 3.论文算法调研 3.1 Deep Image Matting 3.2 Semantic Image Matting 3.3 Background Matting 3.4 Background Matting V2 3.5 Semantic Human Matting 3.6 HAttMatting 3.7 MMNet&#xff1a;Towards Real-Time Automatic Portrait Matt…

【SHM】Semantic Human Matting抠图算法调试

前言&#xff1a; 2018年阿里的论文《Semantatic Human Matting》给出了抠图领域的一个新方法&#xff0c;可惜阿里并没有公布源码&#xff0c;而牛人在Github上对这个论文进行了复现&#xff0c;我也是依赖Github上的工程进行钻研&#xff0c;而在调试的过程中&#xff0c;发…

[Matting]论文阅读:Deep Image Matting 详细解读

[Matting]论文阅读&#xff1a;Deep Image Matting 详细解读 一 、摘要 任务二、方法2.1 第一部分&#xff08;Matting encoder-decoder stage&#xff09;2.2 第二部分&#xff08;Matting refinement stage&#xff09;2.3 数据部分&#xff08;Composed Datasets&#xff09…

【CVPR2022】MatteFormer: Transformer-Based Image Matting via Prior-Tokens

MatteFormer: Transformer-Based Image Matting via Prior-Tokens 中文题目: 借助先验Token的基于Transformer的图像抠图 paper&#xff1a;https://arxiv.org/pdf/2203.15662v1.pdf code&#xff1a;https://github.com/webtoon/matteformer 摘要 本文提出了一个基于Tran…

Image Matting代码和算法效果总结

本文参考了&#xff1a;http://blog.leanote.com/post/610167078qq.com/Image-Matting。作者给出了大部分matting-code的链接&#xff0c;说明也比较细致、系统&#xff0c;在这里向作者表示由衷地感谢&#xff01;以下是博客的原文&#xff1a; 肖总博客&#xff1a;http://3…

matting笔记_一周小结

去年刚入坑的旧笔记&#xff0c;刚翻出来… 1. 利用神经网络做抠图的入坑之作《Deep Image Matting》 详情见之前的笔记 matting系列论文笔记&#xff08;一&#xff09;&#xff1a;Deep Image Matting 由于image matting的工作没有特别好的综述&#xff0c;有的综述也不是…

Matting 基于DeepLearning 入门

前言 是比较清晰的用思维导图的形式介绍了一些Matting 基于DeepLearning 的经典论文&#xff0c;如 Deep Image matting可以通过这篇初步了解深度学习Matting领域该篇论文可以看作是基于深度学习研究Matting的开山之作&#xff0c;之后的许多有效、可行度高的做法都是基于这篇…