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

Posted 迈克老狼2012

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Meanshift filter实现简单图片的卡通化效果相关的知识,希望对你有一定的参考价值。

    利用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算法的原理。

 

在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迭代算法,

(x,y): X- \\texttt{sp} \\le x  \\le X+ \\texttt{sp} , Y- \\texttt{sp} \\le y  \\le Y+ \\texttt{sp} , ||(R,G,B)-(r,g,b)||   \\le \\texttt{sr}

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

(X,Y)~(X\',Y\'), (R,G,B)~(R\',G\',B\').

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

I(X,Y) <- (R*,G*,B*)

算法代码如下:

#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);
        }
    }
}

以上是关于Meanshift filter实现简单图片的卡通化效果的主要内容,如果未能解决你的问题,请参考以下文章

实现图片的异步加载

GGXX的卡通渲染实现 真的好变态......

抖音很火的卡通表白动态页面

燃烧我的卡路里——Flutter瘦内存瘦包之图片渲染组件

m分别使用meanshift和camshift两种算法实现人员跟踪并输出人员移动曲线matlab仿真

Unite 2018 | 《崩坏3》:在Unity中实现高品质的卡通渲染(下)