美文网首页互联网@时事传播读书
【图像处理】OpenCV系列二十六 --- 图像分割(grabC

【图像处理】OpenCV系列二十六 --- 图像分割(grabC

作者: 307656af5a04 | 来源:发表于2019-05-09 16:33 被阅读6次

    上一节我们学习了距离变换算法,相信大家学习之后,对距离变换算法已经有了基本的认识,本节呢,我们对学习图像分割,对grabCut函数详解!

    一、函数详解

    1、函数原型

    void grabCut(InputArray img,
        InputOutputArray mask,
        Rect rect,
        InputOutputArray bgdModel,
        InputOutputArray fgdModel,
        int iterCount,
        int mode = GC_EVAL)     
    

    2、函数功能
    主要实现对图像背景的分割!

    3、参数详解

    • 第一个参数,InputArray img,输入图像,一个8位3通道的图像;

    • 第二个参数,InputOutputArray mask,输入/输出一个8位单通道的掩码图像;当函数的参数mode设置为GC_INIT_WITH_RECT,掩码由函数进行初始化;它的元素可能是GrabCutClasses中的一个;

    掩码的类型
    (1) GC_BGD,表示是背景;
    (2) GC_FGD,表示是前景;
    (3) GC_PR_BGD, 表示可能是背景;
    (4) GC_PR_FGD, 表示可能是前景;

    • 第三个参数,Rect rect,要分割对象的ROI,ROI外部的像素被标记为“明显的背景”;只有当函数的参数mode设置为GC_INIT_WITH_RECT时,才使用该参数;

    • 第四个参数,InputOutputArray bgdModel,背景模型的临时数组;在处理同一图像时,不要修改它;

    • 第五个参数,InputOutputArray fgdModel,前景模型的临时数组;在处理同一图像时,不要修改它;

    • 第六个参数,int iterCount,在返回结果之前,算法应该进行的迭代次数;

    Note:
    可以将函数中的参数mode设置为GC_INIT_WITH_MASK或者GC_EVAL进一步调用来细化结果;

    • 第七个参数,int mode = GC_EVAL,操作的模式;

    操作的模式有以下几种
    (1) GC_INIT_WITH_RECT,函数使用提供的矩形来初始化状态和掩码;之后,它运行算法iterCount次迭代。
    (2) GC_INIT_WITH_MASK,函数使用提供的掩码初始化状态;
    Note:
    可以组合使用GC_INIT_WITH_RECT和GC_INIT_WITH_MASK;
    (3) GC_EVAL,该值意味着该算法只需恢复;
    (4) GC_EVAL_FREEZE_MODEL,该值意味着该算法应该只运行具有固定模型的GrabCut算法(一次迭代);

    二、综合实例

    1、实验案例
    实现对人物的图像进行抠图

    辅助信息
    #include <opencv2/imgcodecs.hpp>
    #include <opencv2/highgui.hpp>
    #include <opencv2/imgproc.hpp>
    #include <iostream>
    
    using namespace std;
    using namespace cv;
    
    static void help()
    {
        cout << "\n在图像中选择一块区域,然后用grabCut尝试将其分割出来.\n"
            "\n在图像中选择一块你要分割的矩形区域\n" <<
            "\nHot keys: \n"
            "\tESC - 退出程序\n"
            "\tr - 回复原始图像\n"
            "\tn - 下一次迭代\n"
            "\n"
            "\t左键移动,设置要分割的矩形\n"
            "\n"
            "\tCTRL+left 鼠标按键 - 设置 GC_BGD 背景\n"
            "\tSHIFT+left 鼠标按键 - 设置 GC_FGD 前景\n"
            "\n"
            "\tCTRL+right 鼠标按键 - 设置 GC_PR_BGD 可能是背景\n"
            "\tSHIFT+right 鼠标按键 - 设置 GC_PR_FGD 可能是前景\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);
    
    // 背景模型按键 Ctrl键
    const int BGD_KEY = EVENT_FLAG_CTRLKEY;
    
    // 前景模型按键 shift键
    const int FGD_KEY = EVENT_FLAG_SHIFTKEY;
    
    // 获取二值化的掩码
    static void getBinMask( const Mat& comMask, Mat& binMask )
    {
        if( comMask.empty() || comMask.type()!=CV_8UC1 )
            CV_Error( Error::StsBadArg, 
            "comMask is empty or has incorrect type (not CV_8UC1)" );
        
        // 进行判断,如果为false,则重新创建binMask掩码
        if( binMask.empty() || 
            binMask.rows!=comMask.rows 
            || binMask.cols!=comMask.cols )
            binMask.create( comMask.size(), CV_8UC1 );
        
        binMask = comMask & 1; // 所有的像素&1
    }
    
    // 应用程序类
    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:
        // 在掩码中设置Rect
        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();
        
        // 初始化设置为false
        isInitialized = false;
        
        // 矩形,左键,右键三个状态设置为NOT_SET
        rectState = NOT_SET;
        lblsState = NOT_SET;
        prLblsState = NOT_SET;
        
        // 迭代次数设置为0
        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();
    }
    
    // 显示图像
    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 );
        }
        
        // 鼠标经过的点,用不同的颜色绘制出来,默认原点
        vector<Point>::const_iterator it;
        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 );
    }
    
    // 设置要分割的Rect
    void GCApplication::setRectInMask()
    {
        CV_Assert( !mask.empty() );
        
        // 掩码设置为背景
        mask.setTo( GC_BGD );
        
        // 矩形的大小
        rect.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) );
    }
    
    // 设置状态
    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;
            fvalue = GC_FGD;
        }
        // 可能是前景,背景
        else
        {
            bpxls = &prBgdPxls;
            fpxls = &prFgdPxls;
            bvalue = GC_PR_BGD;
            fvalue = GC_PR_FGD;
        }
        
        // 背景+Ctrl键
        if( flags & BGD_KEY )
        {
            // 点集压栈
            bpxls->push_back(p);
            
            // 绘制圆
            circle( mask, p, radius, bvalue, thickness );
        }
        
        // 前景+shift键
        if( flags & FGD_KEY )
        {
            // 点集压栈
            fpxls->push_back(p);
            
            // 绘制圆
            circle( mask, p, radius, fvalue, thickness );
        }
    }
    
    // 鼠标点击事件
    void GCApplication::mouseClick( int event, int x, int y, int flags, void* )
    {
        switch( event )
        {
            // set rect or GC_BGD(GC_FGD) labels 左键按下
        case EVENT_LBUTTONDOWN: 
            {
                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 )
                    // 标记左键正在占用
                    lblsState = IN_PROCESS;
            }
            break;
             // set GC_PR_BGD(GC_PR_FGD) labels 右键按下
        case EVENT_RBUTTONDOWN:
            {
                bool isb = (flags & BGD_KEY) != 0,
                     isf = (flags & FGD_KEY) != 0;
                     
                if ( (isb || isf) && rectState == SET )
                    // 标记右键正在被占用
                    prLblsState = IN_PROCESS;
            }
            break;
            
            // 左键松起
        case EVENT_LBUTTONUP:
            // 如果绘制矩形状态被占用
            if( rectState == IN_PROCESS )
            {
                // 绘制的矩形
                rect = Rect( Point(rect.x, rect.y), Point(x,y) );
                
                // 绘制矩形的状态标注为设置
                rectState = SET;
                
                // 设置掩码
                setRectInMask();
                
                // 对一些参数进行判断
                CV_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 EVENT_RBUTTONUP:
        
            // 如果右键正在被占用
            if( prLblsState == IN_PROCESS )
            {
                // 键盘加鼠标按下,画圆
                setLblsInMask(flags, Point(x,y), true);
                
                // 右键状态改为设置
                prLblsState = SET;
                
                // 显示图像
                showImage();
            }
            break;
            
            // 鼠标移动
        case EVENT_MOUSEMOVE:
        
            // 如果绘制矩形的状态正在被占用
            if( rectState == IN_PROCESS )
            {
                // 实时刷新矩形的大小
                rect = Rect( Point(rect.x, rect.y), Point(x,y) );
                
                // 对点集的参数进行判断
                CV_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;
        }
    }
    
    // 下一次迭代
    int GCApplication::nextIter()
    {
        // 如果已经初始化,直接进行分割图像
        if( isInitialized )
            // 分割图像
            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 );
            
            else
                // 分割图像
                grabCut( *image, mask, rect, bgdModel, fgdModel, 1, GC_INIT_WITH_RECT );
            
            // 设置图像已经初始化标记
            isInitialized = true;
        }
        
        // 迭代次数加1
        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 );
    }
    
    // mian函数
    int main( int argc, char** argv )
    {
        // 辅助信息
        help();
      
        // 载入图像
        Mat image = imread("lena.png", IMREAD_COLOR); 
        
        // 判断图像是否为空
        if( image.empty() )
        {
            cout << "\n image error!" << endl;
            return -1;
        }
        
        const string winName = "image";
        namedWindow( winName, WINDOW_AUTOSIZE );
        
        // 设置鼠标调用事件
        setMouseCallback( winName, on_mouse, 0 );
        
        // 设置窗口名称
        gcapp.setImageAndWinName( image, winName );
        
        // 显示图像
        gcapp.showImage();
        
        for(;;)
        {
            // 从键盘接收输入
            char c = (char)waitKey(0);
            switch( 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 << "... ";
                
                // 进行下一次迭代
                int newIterCount = gcapp.nextIter();
                
                if( newIterCount > iterCount )
                {
                    // 显示图像
                    gcapp.showImage();
                    cout << iterCount << ">" << endl;
                }
                else
                    cout << "rect must be determined>" << endl;
                break;
            }
        }
        
        // 退出程序,销毁窗口 
    exit_main:
        destroyWindow( winName );
        return 0;
    }
    

    2、实验结果

    原图 抠图的结果

    我是奕双,现在已经毕业将近两年了,从大学开始学编程,期间学习了C语言编程,C++语言编程,Win32编程,MFC编程,毕业之后进入一家图像处理相关领域的公司,掌握了用OpenCV对图像进行处理,如果大家对相关领域感兴趣的话,可以关注我,我这边会为大家进行解答哦!如果大家需要相关学习资料的话,可以私聊我哦!

    相关文章

      网友评论

        本文标题:【图像处理】OpenCV系列二十六 --- 图像分割(grabC

        本文链接:https://www.haomeiwen.com/subject/aojpoqtx.html