使用opencv实现自定义抠图
- 导语
- 环境
- 原理
- 设计思路
- 代码实现
- 原图
- 运行效果图片
导语
寒假期间也都是基本学些比较基础的东西,也没有做些什么。
这次是突然想换头像,电脑上又没有Photoshop,就想着自己实现一个简单的抠图程序,纯属是好玩而已。
本次程序就直接采用暴力的方法对每个像素点进行修改,如果在算法上有什么指教的也可以相互讨论
环境
Qt Creator 4.8.1(community)
OpenCV 4.0版本
原理
原理也不写了,主要用到的知识点有(想学原理的话CSDN上面很多文章都有,可以自行百度):
--漫水填充
--canny算子
--图像二值化
--回调函数(其实回调函数这技术也是很牛逼的,setMouseCallback和createTrackbar会用到)
--OpenCV库函数([OpenCV官方手册](https://docs.opencv.org/4.0.0/))
设计思路
一、读入一张图片,这里我用哆啦A梦的图片
二、创建如下几个图片副本(大小和原始图片一样大),用于各个阶段显示效果
三、setMouseCallback函数的使用,主要实现的是在经过处理后的二值图上进行边框的描
绘(勾画出自己想要抠的图的区域),其对应的第二个参数(一个回调函数,代码中)
实现的是press函数。并在另外的Mat对象中存储勾画图的位置,用于下面第四点使用。
四、createTrackbar函数的使用(整个程序最核心的部分,用法在OpenCV官方手册里面有,也可
以去CSDN找,会有更详细的解释,主要的是第5个参数,find_contour_demo函数)
首先是使用canny进行边缘检测(这张是原图的二值化图像),然后使用findContours函数
找出图片的轮廓并使用drawContours画出轮廓图(画出来的为彩色图,再转为灰度图),接
着将灰度的轮廓图和自己画的轮廓图进行重合比较,如果既是自己画的轮廓,有事canny算子
算出来的轮廓,就是自己想要的那部分的轮廓,这样可以提高想要勾画的准确度(这里必须是
一个封闭的图形,不然无法进行下面的漫水填充)。经过上一步,就可以得到一张黑底的,白
色边框的轮廓图(下面第5张图片,窗口名称为roi_before),接着对这张图片进行漫水填充
(OpenCV中的floodFill函数),然后得到自己想抠的区域为白色的图片,将这张图片和原图
进行对比,如果这张图片值为0(即黑色),那么原图相应的点颜色就为Scalar(255,225,
255)(白色)。
代码实现
#include
#include
#include
#include
#include
using namespace cv;
using namespace std;
void find_contour_demo(int,void*);//回调函数,用于createTrackbar,进行边缘检测,并画出轮廓图
void press(int event,int x,int y,int flag,void* param);//回调函数,用于setMouseCallback,用于鼠标按下左键时画轮廓用的函数
static Mat base;//用于读取图片,作为原始图片
static Mat source,canny,copy1;//分别为资源图片,轮廓线在这张图上显示;canny用于边缘检测的图片,copy1用来显示为二值化的图片,即最初的那张图片。
static int value=100;//阈值变量的初始值,用于createTrackbar函数
static Mat contour;//显示自己用鼠标画出来的轮廓;
static Mat roi;//将canny处理后,画出来的轮廓图和contour自己画的轮廓重合的部分作为要抠选的区域
int main()
{
//图像二值化,初始化数据
base=imread("qq.jpg");
base.copyTo(source);
base.copyTo(copy1);
cvtColor(source,source,COLOR_BGR2GRAY);
contour=Mat::zeros(source.size(),source.type());
//设置窗口名称
namedWindow("source" /*,WINDOW_NORMAL*/);
namedWindow("roi" /*,WINDOW_NORMAL*/);
namedWindow("roi_before");
namedWindow("result" /*,WINDOW_NORMAL*/);
imshow("source",source);
//设置鼠标事件
setMouseCallback("source",press,(void*)&source);
//创建阈值的滑动按键,初始值为100
createTrackbar("threshold value","source",&value,255,find_contour_demo);
find_contour_demo(0,nullptr);
waitKey(0);
return 0;
}
void find_contour_demo(int,void*)
{
//使用canny边缘检测的图片
Canny(copy1,canny,value,value*2,3,false);
imshow("canny detection", canny);
//查找轮廓,并显示彩色轮廓,接下来将彩色轮廓转为灰度,用于roi设置
vector> contours;
vector> hierachy;
findContours(canny,contours,hierachy,RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
Mat drawImage = Mat::zeros(source.size(), CV_8UC3);//边缘检测后的轮廓图,用彩色画出来
RNG rng(12345);
for(int i=0;i(i)[j]==255) && (drawImage.ptr(i)[j]!=0) )
roi.ptr(i)[j]=255;
//设置种子,用于水漫填充,即找到roi区域内的任意一点黑点
Point seed;
int flag=0;
for(int i=0;i(i)[j]==255)&&(contour.ptr(i+5)[j]==0))
{
seed.y=5+i;
seed.x=j;
flag=1;
break;
}
}
if(flag==1)
break;
}
//进行漫水填充
imshow("roi_before",roi);
floodFill(roi, seed, Scalar(255,255,255), nullptr,Scalar(20, 20, 20),Scalar(20, 20, 20));
imshow("roi",roi);
//显示最终效果图片,底色为白色,用漫水填充的图片,如果值为0(黑色),那么就在原图上将原值改为255(白色),即为白底
for (int i=0;i(i)[j]==0)
{
base.ptr(i)[base.channels()*j]=255;
base.ptr(i)[base.channels()*j+1]=255;
base.ptr(i)[base.channels()*j+2]=255;
}
}
}
imshow("result",base);
return;
}
void press(int event,int y,int x,int flag,void* param)
{
//左键按下时候的事件,在二值图上画出黑线轮廓
Mat* pic=(Mat*)param;
if(flag==EVENT_LBUTTONDOWN)
{
if(!(*(pic->ptr(x)+y)==0))
{
cout<ptr(x)+y))<ptr(x+i)+y+j)=0;
contour.ptr(x+i)[y+j]=255;
}
}
}
// imshow("contour",contour);
imshow("source",source);
}
原图
运行效果图片