【opencv450-samples】图像分割grabcut算法

46 篇文章 4 订阅

源图像:

 

右键操作:

处理后:

 

左键操作:

处理后:

 

 

源码:

#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"

#include <iostream>

using namespace std;
using namespace cv;

static void help(char** argv)
{
    cout << "\nThis program demonstrates GrabCut segmentation -- select an object in a region\n"
        "and then grabcut will attempt to segment it out.\n"
        "Call:\n"
        << argv[0] << " <image_name>\n"
        "\nSelect a rectangular area around the object you want to segment\n" <<
        "\nHot keys: \n"
        "\tESC - quit the program\n"
        "\tr - restore the original image\n"
        "\tn - next iteration\n"
        "\n"
        "\tleft mouse button - set rectangle\n"
        "\n"
        "\tCTRL+left mouse button - set GC_BGD pixels\n"
        "\tSHIFT+left mouse button - set GC_FGD pixels\n"
        "\n"
        "\tCTRL+right mouse button - set GC_PR_BGD pixels\n"
        "\tSHIFT+right mouse button - set GC_PR_FGD pixels\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);
//按键//
const int BGD_KEY = EVENT_FLAG_CTRLKEY; //ctrl 键---背景//
const int FGD_KEY = EVENT_FLAG_SHIFTKEY; //shift 键---前景//

//获取二值掩码//
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)");
    if (binMask.empty() || binMask.rows != comMask.rows || binMask.cols != comMask.cols)
        binMask.create(comMask.size(), CV_8UC1);
    binMask = comMask & 1;//
}
//grab cut应用程序类//
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:
    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();

    isInitialized = false;
    rectState = NOT_SET;//矩形框状态未设置/
    lblsState = NOT_SET;//标签状态-左键按下时//
    prLblsState = NOT_SET;//右键按下-标签状态//
    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);//res: 感兴趣区图像
    }

    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)//浅蓝色点:鼠标右键ctrl  背景点
        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);
}
//设置掩码里矩形框像素值
void GCApplication::setRectInMask()
{
    CV_Assert(!mask.empty());
    mask.setTo(GC_BGD);//黑色掩码
    rect.x = max(0, rect.x); //确保矩形左上角点的x>0
    rect.y = max(0, rect.y);//确保矩形左上角点的y>0
    rect.width = min(rect.width, image->cols - rect.x);//确保矩形右下角在image上
    rect.height = min(rect.height, image->rows - rect.y);
    (mask(rect)).setTo(Scalar(GC_PR_FGD));//掩码矩形区域设置为像素值3
}
//在掩码上绘制标记点
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;//前景点集目标像素值
    }
    if (flags & BGD_KEY)//ctrl按下
    {
        bpxls->push_back(p);//背景点
        circle(mask, p, radius, bvalue, thickness);//绘制圆点
    }
    if (flags & FGD_KEY)//shift按下
    {
        fpxls->push_back(p);//前景点
        circle(mask, p, radius, fvalue, thickness);//绘制圆点
    }
}
//鼠标单击事件
void GCApplication::mouseClick(int event, int x, int y, int flags, void*)
{
    // TODO add bad args check
    switch (event)
    {
    case EVENT_LBUTTONDOWN: //左键按下 set rect or GC_BGD(GC_FGD) labels
    {
        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);//初始矩形宽1 高1
        }
        if ((isb || isf) && rectState == SET)//没按ctrl,没按shift, 矩形绘制完毕。
            lblsState = IN_PROCESS;//切换到标签模式 
    }
    break;
    case EVENT_RBUTTONDOWN: //右键按下  set GC_PR_BGD(GC_PR_FGD) labels
    {
        bool isb = (flags & BGD_KEY) != 0,//没按ctrl
            isf = (flags & FGD_KEY) != 0;//没按shift
        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();//设置矩形掩码区域像素值为3
            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);  //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);//绘制标记点   false:绘制中
            showImage();
        }
        else if (prLblsState == IN_PROCESS)//右键模式 标记点模式
        {
            setLblsInMask(flags, Point(x, y), true);//true:  右键模式  prLblsState标记点模式
            showImage();
        }
        break;
    }
}

/** @brief 运行 GrabCut 算法。
该函数实现了【GrabCut 图像分割算法】(http://en.wikipedia.org/wiki/GrabCut)。
@param img 输入 8 位 3 通道图像。
@param mask 输入/输出 8 位单通道掩码。当模式设置为#GC_INIT_WITH_RECT 时,函数会初始化掩码。它的元素可能具有#GrabCutClasses 之一。
@param rect ROI 包含一个分段对象。 ROI 之外的像素被标记为“明显背景”。该参数仅在 mode==#GC_INIT_WITH_RECT 时使用。
@param bgdModel 背景模型的临时数组。在处理同一图像时不要修改它。
@param fgdModel 前景模型的临时数组。在处理同一图像时不要修改它。
@param iterCount 算法在返回结果之前应该进行的迭代次数。请注意,可以通过 mode==#GC_INIT_WITH_MASK 或 mode==GC_EVAL 进一步调用来优化结果。
@param mode 可能是#GrabCutModes 之一的操作模式
 */
 //图像分割
int GCApplication::nextIter()
{
    if (isInitialized)
        grabCut(*image, mask, rect, bgdModel, fgdModel, 1);//运行 GrabCut 算法
    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;//初始化完成
    }
    iterCount++;//迭代次数

    bgdPxls.clear(); fgdPxls.clear();
    prBgdPxls.clear(); prFgdPxls.clear();

    return iterCount;
}

GCApplication gcapp; //grab cut应用程序

/** @brief 鼠标事件的回调函数。 见 cv::setMouseCallback
@param 事件 cv::MouseEventTypes 常量之一。
@param x 鼠标事件的 x 坐标。
@param y 鼠标事件的 y 坐标。
@param 标记 cv::MouseEventFlags 常量之一。
@param userdata 可选参数。
  */

  /*  cv::MouseEventFlags
  * EVENT_FLAG_ALTKEY = 32		摁住Alt
  EVENT_FLAG_CTRLKEY = 8		摁住Ctrl

  EVENT_FLAG_LBUTTON = 1		摁住左键
  EVENT_FLAG_MBUTTON = 4		摁住中键
  EVENT_FLAG_RBUTTON = 2		摁住右键
  EVENT_FLAG_SHIFTKEY = 16	摁住Shift

  EVENT_LBUTTONDBLCLK = 7		左键双击
  EVENT_LBUTTONDOWN = 1		左键击下
  EVENT_LBUTTONUP = 4			左键弹起
  EVENT_MBUTTONDBLCLK = 9		中键双击
  EVENT_MBUTTONDOWN = 3		中键击下
  EVENT_MBUTTONUP = 6			中键弹起
  EVENT_MOUSEHWHEEL = 11		滚动条向左,flags>0。向右,flags<0
  EVENT_MOUSEMOVE = 0			鼠标移动
  EVENT_MOUSEWHEEL = 10		滚动条向上,flags>0。向下,flags<0
  EVENT_RBUTTONDBLCLK = 8		中键双击
  EVENT_RBUTTONDOWN = 2		中键击下
  EVENT_RBUTTONUP = 5			中键弹起
  */
static void on_mouse(int event, int x, int y, int flags, void* param)
{
    gcapp.mouseClick(event, x, y, flags, param);
}

int main(int argc, char** argv)
{
    cv::CommandLineParser parser(argc, argv, "{@input| messi5.jpg |}");//messi5.jpg
    help(argv);

    string filename = parser.get<string>("@input");
    if (filename.empty())
    {
        cout << "\nDurn, empty filename" << endl;
        return 1;
    }
    Mat image = imread(samples::findFile(filename), IMREAD_COLOR);//加载图片
    // Mat image = imread("01.jpg", IMREAD_COLOR);
    if (image.empty())
    {
        cout << "\n Durn, couldn't read image filename " << filename << endl;
        return 1;
    }
    //resize(image, image, Size(image.size() / 4));
    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': // \x1B 表示 ASCII 中的第 1B(27号)字符 ESC(Escape)也就是转义字符
            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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值