Meanshift filter实现简单图片的卡通化效果

时间:2021-11-12 18:27:38

    利用Meanshift filter和canny边缘检测的效果,可以实现简单的图片的卡通化效果。简单的说,就是用Meanshift filter的结果减去canny算法的结果得到卡通化的效果。

  代码如下:

#include <opencv2/core/core.hpp>
#include <opencv2/highgui//highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

#include <iostream>
using namespace std;
using namespace cv;

int main()
{
    cv::Mat img = cv::imread("../lenna.jpg");
    cv::namedWindow("image");
    cv::imshow("image", img);

    cv::Mat img1;
    img1 = img.clone();

    //meanshift filter
    cv::pyrMeanShiftFiltering(img1.clone(), img1, 10, 30);
    cv::namedWindow("image1");
    cv::imshow("image1", img1);

    cv::Mat img2;
    cv::Mat img3;
    cv::Mat img4;

    //canny
    cv::cvtColor(img, img2, CV_BGR2GRAY);
    cv::Canny(img2, img3, 150, 150);
    cv::cvtColor(img3, img4, CV_GRAY2BGR);

    cv::namedWindow("image4");
    cv::imshow("image4", img4);

    //卡通化的图片
    img4 = img1 - img4;
    cv::namedWindow("image4_1");
    cv::imshow("image4_1", img4);


    cv::waitKey(0);
}

下面分别为,原始图像,meanshift filter后的图像,canny边缘图像,以及最终的卡通化图像。

Meanshift filter实现简单图片的卡通化效果

下面我们看看meanshift filter算法的原理。

 

在OpenCV中,meanshift filter函数为 pyrMeanShiftFiltering, 它的函数调用格式如下:

C++: void pyrMeanShiftFiltering(InputArray src, OutputArray dst, double sp, double sr, int maxLevel=1, TermCriteriatermcrit=TermCriteria( TermCriteria::MAX_ITER+TermCriteria::EPS,5,1) )

Parameters:

  • src – The source 8-bit, 3-channel image. //三通道的输入图像
  • dst – The destination image of the same format and the same size as the source. //相同尺寸格式输出图像
  • sp – The spatial window radius.  //空间域半径
  • sr – The color window radius.  //颜色域半径
  • maxLevel – Maximum level of the pyramid for the segmentation. //分割用金字塔的最大层数
  • termcrit – Termination criteria: when to stop meanshift iterations. //迭代终止的条件

算法的描述大致如下:

对于输入图像的每个像素点(X,Y) ,在它的半径为sp的空间域,执行meanshift迭代算法,

Meanshift filter实现简单图片的卡通化效果

像素点(X,Y)的颜色值为(R,G,B), 它的空间邻域点(x,y)的颜色值为(r,g,b),如果点(x,y)的到(X,Y)的颜色距离小于sr,则满足条件,最终我们求得满足条件点的平均空间坐标(X’,Y’)以及平均颜色向量(R',G',B'),并把它们作为下一次迭代的输入。

Meanshift filter实现简单图片的卡通化效果

迭代结果后,我们把最初输入位置的颜色值用最终迭代的颜色值代替。

Meanshift filter实现简单图片的卡通化效果

算法代码如下:

#include <opencv2/core/core.hpp>#include <opencv2/highgui//highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

#include
<iostream>
using namespace std;
using namespace cv;

//forward声明
void gMeanShift(int x0, int y0, uchar *sptr, uchar *dptr,
int sstep, cv::Size size, int sp, int sr, int maxIter,
float eps, int *tab);

void gMeanShiftFilter(const cv::Mat src, cv::Mat &dst, int sp,
int sr, cv::TermCriteria crit = cv::TermCriteria(
cv::TermCriteria::MAX_ITER
+ cv::TermCriteria::EPS, 5, 1));

int main()
{
cv::Mat img
= cv::imread("../lenna.jpg");
cv::namedWindow(
"image");
cv::imshow(
"image", img);

cv::Mat img1;
img1
= img.clone();
//meanshift filter
//转化图像为4BGRA 4通道格式
cv::cvtColor(img1.clone(), img1, CV_BGR2BGRA);
gMeanShiftFilter(img1.clone(), img1,
10, 30);
cv::cvtColor(img1.clone(), img1, CV_BGRA2BGR);

//meanshift filter result image
cv::namedWindow("image1");
cv::imshow(
"image1", img1);

cv::Mat img2;
cv::Mat img3;
cv::Mat img4;

//canny
cv::cvtColor(img, img2, CV_BGR2GRAY);
cv::Canny(img2, img3,
150, 150);
cv::cvtColor(img3, img4, CV_GRAY2BGR);

cv::namedWindow(
"image4");
cv::imshow(
"image4", img4);

img4
= img1 - img4;
cv::namedWindow(
"image4_1");
cv::imshow(
"image4_1", img4);

cv::waitKey(
0);
}

void gMeanShift(int x0, int y0, uchar *sptr, uchar *dptr,
int sstep, cv::Size size, int sp, int sr, int maxIter,
float eps, int *tab)
{
int isr2 = sr * sr;
int c0, c1, c2, c3;
int iter;
uchar
*ptr = NULL;
uchar
*pstart = NULL;
int revx = 0, revy = 0;
c0
= sptr[0];
c1
= sptr[1];
c2
= sptr[2];
c3
= sptr[3];

/****************************************************************************
* Iterate meanshift procedure *
***************************************************************************
*/
for (iter = 0; iter < maxIter; iter++)
{
int count = 0;
int s0 = 0, s1 = 0, s2 = 0, sx = 0, sy = 0;

/****************************************************************************
* mean shift: process pixels in window (p-sigmaSp)x(p+sigmaSp) *
***************************************************************************
*/
int minx = x0 - sp;
int miny = y0 - sp;
int maxx = x0 + sp;
int maxy = y0 + sp;

/****************************************************************************
* Deal with the image boundary. *
***************************************************************************
*/
if (minx < 0)
{
minx
= 0;
}
if (miny < 0)
{
miny
= 0;
}
if (maxx >= size.width)
{
maxx
= size.width - 1;
}
if (maxy >= size.height)
{
maxy
= size.height - 1;
}
if (iter == 0)
{
pstart
= sptr;
}
else
{
pstart
= pstart + revy * sstep + (revx << 2); //point to the new position
}
ptr
= pstart;
//point to the start in the row
ptr = ptr + (miny - y0) * sstep + ((minx - x0) << 2);
for (int y = miny; y <= maxy; y++, ptr += sstep - ((maxx - minx + 1) << 2))
{
int rowCount = 0;
int temp, temp1;
temp1
= (maxx - minx + 1) << 2;
temp
= sstep - ((maxx - minx + 1) << 2);
int x = minx;
for (; x <= maxx; x++, ptr += 4)
{
int t0 = ptr[0], t1 = ptr[1], t2 = ptr[2];
if (tab[t0 - c0 + 255] + tab[t1 - c1 + 255] + tab[t2 - c2 + 255] <= isr2)
{
s0
+= t0;
s1
+= t1;
s2
+= t2;
sx
+= x;
rowCount
++;
}
}
if (rowCount == 0)
{
continue;
}
count
+= rowCount;
sy
+= y * rowCount;
}
if (count == 0)
{
break;
}

int x1 = sx / count;
int y1 = sy / count;
s0
= s0 / count;
s1
= s1 / count;
s2
= s2 / count;

bool stopFlag = (x0 == x1 && y0 == y1) || (abs(x1 - x0) + abs(y1 - y0) +
tab[s0
- c0 + 255] + tab[s1 - c1 + 255] + tab[s2 - c2 + 255] <= eps);

/****************************************************************************
* Revise the pointer corresponding to the new (y0,x0) *
***************************************************************************
*/
//
revx = x1 - x0;
revy
= y1 - y0;

x0
= x1;
y0
= y1;
c0
= s0;
c1
= s1;
c2
= s2;

if (stopFlag)
{
break;
}

}

dptr[
0] = (uchar)c0;
dptr[
1] = (uchar)c1;
dptr[
2] = (uchar)c2;
dptr[
3] = (uchar)c3;
}

void gMeanShiftFilter(const cv::Mat src, cv::Mat &dst,
int sp, int sr, cv::TermCriteria crit)
{
if (src.empty())
{
cout
<< "Source is null" << endl;
}

if (!(crit.type & cv::TermCriteria::MAX_ITER))
{
crit.maxCount
= 5;
}
int maxIter = std::min(std::max(crit.maxCount, 1), 100);
float eps;
if (!(crit.type & cv::TermCriteria::EPS))
{
eps
= 1.f;
}
eps
= (float)std::max(crit.epsilon, 0.0);

int tab[512];
for (int i = 0; i < 512; i++)
{
tab[i]
= (i - 255) * (i - 255);
}
uchar
*sptr = src.data;
uchar
*dptr = dst.data;
int sstep = (int)src.step;
int dstep = (int)dst.step;
cv::Size size
= src.size();

for (int i = 0; i < size.height; i++, sptr += sstep - (size.width << 2),
dptr
+= dstep - (size.width << 2))
{
int tt, tt1;
tt
= size.width << 2;
tt1
= sstep - (size.width << 2);
for (int j = 0; j < size.width; j++, sptr += 4, dptr += 4)
{
gMeanShift(j, i, sptr, dptr, sstep, size, sp,
sr, maxIter, eps, tab);
}
}
}