互联网@时事传播读书

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

2019-05-09  本文已影响6人  307656af5a04

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

一、函数详解

1、函数原型

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

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

3、参数详解

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

Note:
可以将函数中的参数mode设置为GC_INIT_WITH_MASK或者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对图像进行处理,如果大家对相关领域感兴趣的话,可以关注我,我这边会为大家进行解答哦!如果大家需要相关学习资料的话,可以私聊我哦!

上一篇下一篇

猜你喜欢

热点阅读