由于中篇最后得到的图像还是需要手动去磨皮,边缘突出的部分还是没找好。
这里我再想办法处理一下:
现在我们已经得到了这样的一张掩模:
边缘找的不是很好
那么我们可以结合找边缘的方法对它进行处理。
第一步:
找边缘的方法常见的主要有三种:
1.1 Sobel
代码如下:
#include
#include
#include
#include
using namespace std;
using namespace cv;
int main()
{
Mat src, src_gray;
Mat grad;
const char* window_name = "Sobel Demo - Simple Edge Detector";
int scale = 1;
int delta = 0;
int ddepth = CV_16S;
/// Load an image
src = imread( "C:\\Users\\ltc\\Desktop\\data3\\mission2\\taowamirror.jpg");
if( src.empty() )
{
cout<<"where is your picture"<return -1;
}
GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );
/// Convert it to gray
cvtColor( src, src_gray, COLOR_RGB2GRAY );
/// Create window
namedWindow( window_name, WINDOW_AUTOSIZE );
/// Generate grad_x and grad_y
Mat grad_x, grad_y;
Mat abs_grad_x, abs_grad_y;
/// Gradient X
//Scharr( src_gray, grad_x, ddepth, 1, 0, scale, delta, BORDER_DEFAULT );
Sobel( src_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT );
convertScaleAbs( grad_x, abs_grad_x );
/// Gradient Y
//Scharr( src_gray, grad_y, ddepth, 0, 1, scale, delta, BORDER_DEFAULT );
Sobel( src_gray, grad_y, ddepth, 0, 1, 3, scale, delta, BORDER_DEFAULT );
convertScaleAbs( grad_y, abs_grad_y );
/// Total Gradient (approximate)
addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad );
imshow( window_name, grad );
imwrite( "C:\\Users\\ltc\\Desktop\\data3\\mission2\\taowasobel.jpg",grad);
waitKey(0);
return 0;
}
1.2 Laplacian
#include
#include
#include
#include
using namespace std;
using namespace cv;
int main()
{
Mat src, src_gray, dst;
int kernel_size = 3;
int scale = 1;
int delta = 0;
int ddepth = CV_16S;
const char* window_name = "Laplace Demo";
// 读图
src = imread( "C:\\Users\\ltc\\Desktop\\data3\\mission2\\taowamirror.jpg");
if( src.empty() )
{
cout<<"where is your picture"<return -1;
}
// 高斯滤波
GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );
// 灰度图
cvtColor( src, src_gray, COLOR_RGB2GRAY );
// 窗体
namedWindow( window_name, WINDOW_AUTOSIZE );
// Laplace 函数
Mat abs_dst;
Laplacian( src_gray, dst, ddepth, kernel_size, scale, delta, BORDER_DEFAULT );
convertScaleAbs( dst, abs_dst );
// 显示
imshow( window_name, abs_dst );
waitKey(0);
}
1.3Canny
#include
#include
#include
#include
using namespace std;
using namespace cv;
/// Global variables
Mat src, src_gray;
Mat dst, detected_edges;
int edgeThresh = 1;
int lowThreshold;
int const max_lowThreshold = 100;
int ratio = 3;
int kernel_size = 3;
const char* window_name = "Edge Map";
/**
* @function CannyThreshold
* @brief Trackbar callback - Canny thresholds input with a ratio 1:3
*/
static void CannyThreshold(int, void*)
{
/// Reduce noise with a kernel 3x3
blur( src_gray, detected_edges, Size(3,3) );
/// Canny detector
Canny( detected_edges, detected_edges, lowThreshold, lowThreshold*ratio, kernel_size );
/// Using Canny's output as a mask, we display our result
dst = Scalar::all(0);
src.copyTo( dst, detected_edges);
imshow( window_name, detected_edges );
}
int main()
{
/// Load an image
src = imread( "C:\\Users\\ltc\\Desktop\\data3\\taowamirror.jpg" );
if( src.empty() )
{
cout<<"where is your picture"<return -1;
}
/// Create a matrix of the same type and size as src (for dst)
dst.create( src.size(), src.type() );
/// Convert the image to grayscale
cvtColor( src, src_gray, COLOR_BGR2GRAY );
/// Create a window
namedWindow( window_name, WINDOW_NORMAL );
/// Create a Trackbar for user to enter threshold
createTrackbar( "Min Threshold:", window_name, &lowThreshold, max_lowThreshold, CannyThreshold );
/// Show the image
CannyThreshold(0, 0);
/// Wait until user exit program by pressing a key
waitKey(0);
return 0;
}
这里我用的Sobel算子,得到的边缘图像如下:
然后进行二值化,二值化的滑动条程序在中篇已经给出
我们得到的图片如下:
第二步:
这里我们可以进行一些膨胀腐蚀的操作,这部分代码可以看我之前的博客:点我开始跳转
代码如下:
#include
#include
#include
#include
//#include
using namespace std;
using namespace cv;
Mat g_srcImage, g_dstImage; //原始图和效果图
int g_nTrackbarNumer = 0; //0表示腐蚀erode, 1表示膨胀dilate
int g_nStructElementSize = 3; //结构元素(内核矩阵)的尺寸
int iteratorNumber = 3; //迭代次数
void Process(); //膨胀和腐蚀的处理函数
void on_TrackbarNumChange( int, void *); //回调函数
void on_ElementSizeChange( int, void *); //回调函数
void on_IteratorNumChange( int, void *); //回调函数
char *SourceImage = "【原始图】";
char *DstImage = "【效果图】";
int main()
{
system("color 5E");
g_srcImage = imread("source.jpg");
if(g_srcImage.empty()) { cout<< "Where is your picture"<2*g_nStructElementSize+1,2*g_nStructElementSize+1));
erode( g_srcImage,g_dstImage,element);
imshow(DstImage,g_dstImage);
createTrackbar("腐蚀/膨胀",DstImage,&g_nTrackbarNumer,1,on_TrackbarNumChange);
createTrackbar("内核尺寸",DstImage,&g_nStructElementSize, 21, on_ElementSizeChange);
createTrackbar("迭代次数",DstImage,&iteratorNumber, 10, on_IteratorNumChange);
cout<"\t嗯。运行成功,请调整滚动条观察图像效果~\n\n"
<<"\t按下“q”键时,程序退出~!\n"
<<"\n\n\t\t\t\tby浅墨";
while(char(waitKey(1))!= 'q'){}
return 0;
}
void Process()
{
//获取自定义核
Mat element = getStructuringElement(MORPH_RECT, Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1));
//进行腐蚀或膨胀操作
if(g_nTrackbarNumer == 0){
erode( g_srcImage, g_dstImage, element,Point( g_nStructElementSize,g_nStructElementSize ),iteratorNumber);
}
else{
dilate(g_srcImage,g_dstImage, element,Point( g_nStructElementSize,g_nStructElementSize ),iteratorNumber);
}
//显示效果图
imshow(DstImage,g_dstImage);
}
void on_TrackbarNumChange(int, void *)
{
Process();
}
void on_ElementSizeChange(int, void *)
{
Process();
}
void on_IteratorNumChange( int, void *)
{
Process();
}
然后我用上面的掩模减去这个腐蚀的图片得到:
这样你会发现边缘多余部分的掩模基本上都会被孤立出来,然后我们在调用之间二值图像删除多余小图形以及填充孔洞的代码,见中篇
这里就不贴了
我们可以把面积设的稍微大一些,然后得到:
再讲这张图像与上面腐蚀的图做与运算就可以把边缘补上,我忘了保存这一步图片,然后在进行一个膨胀的操作,(注意:这里膨胀的尺度要很小,我们不能把边缘整体膨胀了)只是为了把一些区域缝合起来。
得到的效果图如下:
有了这样一张图片然后又是你懂得,再次填充孔洞
就得到了下面的掩模
是不是觉得看着还不错
之前最前面小娃娃的掩模可以很好二值化前景图像得出来。然后做个减法就得到了中间三个娃娃的掩模。
分别如下:
以及
中间这个目前还没想到很好的办法将它分开,于是他们的模糊是一起加的,我也尝试着手动强行将他们分开(哈哈)
第三步
最后我们在分开进行与图像相与得到不同的图层,然后进行高斯模糊,这里不同图层高斯模糊的核半径我是根据他们深度的比例的平方关系进行模糊,这样得出的效果较为真实。
这里说一个加模糊的小技巧,我之前每次分开模糊然后相加,得到的图像是这样。
边缘有一圈黑的,很难受,最初我以为是边缘不对,对掩模膨胀了一下发现最后还是有这种现象。
最后我不加上背景,效果如下:
感觉边缘还不错,怎么最后会是那样,肯定是背景的原因。
果然,我们对背景进行高斯模糊,边上的黑色像素会进行一个串扰,导致最后会有一圈黑边。
可以很明显的看到边缘黑色像素对最后的干扰。
最初我把边缘提取出来,想对它进行一个模糊,效果不好。
因为这个边缘其实有很大的锯齿,并不平滑,导致最后合成的图像非常难看。
后面我想到了一个办法,我将得到的掩模与它进行相与,不就可以消除一些黑色像素串扰吗?
并且这里我们发现还是有一部分黑色像素的串扰,这里我们可以将后面两个图层单独的模糊调小一点,然后在整体进行一个小模糊,就会得到相对还不错的效果。
这里的边上一点点我们可以很好的用引导滤波进行一个修复。
附录:引导滤波
(关于引导滤波网上很多讲解,文章本身也不难看懂,但是却带来了惊人的效果)
何凯明博士的主页
这部分代码如下:
/*
功能:图像引导滤波操作
*/
#include
#include
#include
#include
using namespace std;
using namespace cv;
//-------------------【全局函数声明部分】--------------------------------------
Mat guidedFilter(Mat &srcMat, Mat &guidedMat, int radius, double eps);//引导滤波器
int main()
{
//------------【0】定义相关变量-------------
Mat resultMat; //最后结果图像
vector vSrcImage, vResultImage;
//------------【1】读取源图像并检查图像是否读取成功------------
Mat srcImage = imread("C:\\Users\\ltc\\Desktop\\data3\\中值滤波版本\\高斯模糊深度二次方1\\中间结果以及效果\\resultmid.jpg");
if (!srcImage.data)
{
cout << "读取图片错误,请重新输入正确路径!\n";
system("pause");
return -1;
}
namedWindow("【源图像】",WINDOW_NORMAL);
imshow("【源图像】", srcImage);
//-------【2】对源图像进行通道分离,并对每个分通道进行导向滤波操------
split(srcImage, vSrcImage);
for (int i = 0; i < 3; i++)
{
Mat tempImage;
vSrcImage[i].convertTo(tempImage, CV_64FC1, 1.0 / 255.0);//将分通道转换成浮点型数据
Mat cloneImage = tempImage.clone(); //将tempImage复制一份到cloneImage
Mat resultImage = guidedFilter(tempImage, cloneImage, 5, 0.01);//对分通道分别进行导向滤波,半径为1、3、5...等奇数
vResultImage.push_back(resultImage);//将分通道导向滤波后的结果存放到vResultImage中
}
//----------【3】将分通道导向滤波后结果合并-----------------------
merge(vResultImage, resultMat);
namedWindow("【引导滤波/导向滤波】",WINDOW_NORMAL);
imshow("【引导滤波/导向滤波】", resultMat);
imwrite("C:\\Users\\ltc\\Desktop\\data3\\中值滤波版本\\高斯模糊深度二次方1\\中间结果以及效果\\resultmidgd.jpg",resultMat*255);
waitKey(0);
return 0;
}
//导向滤波器
Mat guidedFilter(Mat &srcMat, Mat &guidedMat, int radius, double eps)
{
//------------【0】转换源图像信息,将输入扩展为64位浮点型,以便以后做乘法------------
srcMat.convertTo(srcMat, CV_64FC1);
guidedMat.convertTo(guidedMat, CV_64FC1);
//--------------【1】各种均值计算----------------------------------
Mat mean_p, mean_I, mean_Ip, mean_II;
boxFilter(srcMat, mean_p, CV_64FC1, Size(radius, radius));//生成待滤波图像均值mean_p
boxFilter(guidedMat, mean_I, CV_64FC1, Size(radius, radius));//生成引导图像均值mean_I
boxFilter(srcMat.mul(guidedMat), mean_Ip, CV_64FC1, Size(radius, radius));//生成互相关均值mean_Ip
boxFilter(guidedMat.mul(guidedMat), mean_II, CV_64FC1, Size(radius, radius));//生成引导图像自相关均值mean_II
//--------------【2】计算相关系数,计算Ip的协方差cov和I的方差var------------------
Mat cov_Ip = mean_Ip - mean_I.mul(mean_p);
Mat var_I = mean_II - mean_I.mul(mean_I);
//---------------【3】计算参数系数a、b-------------------
Mat a = cov_Ip / (var_I + eps);
Mat b = mean_p - a.mul(mean_I);
//--------------【4】计算系数a、b的均值-----------------
Mat mean_a, mean_b;
boxFilter(a, mean_a, CV_64FC1, Size(radius, radius));
boxFilter(b, mean_b, CV_64FC1, Size(radius, radius));
//---------------【5】生成输出矩阵------------------
Mat dstImage = mean_a.mul(srcMat) + mean_b;
return dstImage;
}
其实中间也有很多步骤我也用到了引导滤波,最后得到的效果如下:
好的,到此就结束了,代码的通用性并不强,我也没有整理出一个完整的工程,就当做一个小总结了。
好了,差不多回去睡觉了。~~~