学习OpenCV2——抠图及setmousecallback说明

版权声明:转载请注明出处 https://blog.csdn.net/GDFSG/article/details/50841127

  用鼠标截取图像区域是一种常用操作,我参考了网上众多实现的方法,觉得以下方法最简洁。特此学习并分享。


1.截取矩形区域

原文  http://www.cnblogs.com/tornadomeet/archive/2012/05/04/2483444.html

下面程序实现了从视频中选择一个区域并单独显示。

程序1

//**************本程序练习了鼠标回调函数*********************

#include <opencv2/highgui/highgui.hpp>
#include <opencv2/core/core.hpp>
#include <stdio.h>

using namespace cv;
using namespace std;

///--定义几个全局变量
Rect select;                 //鼠标选择的矩形框
bool mousedown_flag = false; //鼠标按下的标识符
bool select_flag = false;    //选择区域的标识符
Point origin;
Mat frame;

void onMouse(int event,int x,int y,int,void*)
{    
	//注意onMouse是void类型的,没有返回值!
	//为了把这些变量的值传回主函数,这些变量必须设置为全局变量	
	if(mousedown_flag)
	{
		select.x=MIN(origin.x,x);     //不一定要等鼠标弹起才计算矩形框,而应该在鼠标按下开始到弹起这段时间实时计算所选矩形框
		select.y=MIN(origin.y,y);
		select.width=abs(x-origin.x);                  //算矩形宽度和高度
		select.height=abs(y-origin.y);
		select&=Rect(0,0,frame.cols,frame.rows);       //保证所选矩形框在视频显示区域之内
	}
	if(event==CV_EVENT_LBUTTONDOWN)
	{
		mousedown_flag=true;        
		select_flag = false;                        
		origin=Point(x,y);             
		select=Rect(x,y,0,0);           //这里一定要初始化,宽和高为(0,0)是因为在opencv中Rect矩形框类内的点是包含左上角那个点的,但是不含右下角那个点
	}
	else if(event==CV_EVENT_LBUTTONUP)
	{
		mousedown_flag=false;
		select_flag = true;		
	}
}

int main(int argc, unsigned char* argv[])
{ 
	//打开摄像头
	VideoCapture cam(0);
	if (!cam.isOpened())
		return -1;

	//建立窗口
	namedWindow("camera",1);

	//捕捉鼠标
	setMouseCallback("camera",onMouse,0);

	Mat frameROI;      
	while(1)
	{
		//读取一帧图像
		cam>>frame;
		if(frame.empty())
			return -1;

		//显示选择的区域
		if(select_flag)
		{
			frameROI = frame(select);   //设置感兴趣区域ROI
			imshow("roi",frameROI);     //显示ROI
		}	 

		//在每一帧图像上画矩形框
		rectangle(frame,select,Scalar(255,0,0),1,8,0);
		//显示原视频帧
		imshow("camera",frame);

		//显示所选区域的值	 
		printf("\n X = %d, Y = %d, Width = %d, Height = %d",select.x, select.y, select.width, select.height);

		//键盘响应
		int c = waitKey(20);
		if(27==c)//ESC键
			return -1;
	}
	return 0;
}


2. 截取任意区域

程序2:

// http://blog.csdn.net/cv_yuippe/article/details/13035063

#include <iostream>  
#include <opencv2/highgui/highgui.hpp>  
#include <opencv2/imgproc/imgproc.hpp>   
  
using namespace std;  
using namespace cv; 

Point prev_pt = Point(-1, -1);  //如果将prev_pt写在on_mouse里,画的线会出问题,不知道怎么回事
Mat img,BG_mask,FG_mask;

void on_mouse(int event, int x, int y, int flags, void* )
{	
	if ( event == CV_EVENT_LBUTTONUP || !(flags & CV_EVENT_FLAG_LBUTTON))      //松开鼠标左键或者不是左拖拽
	{
		prev_pt = Point(-1, -1);
	}
	else if (event == CV_EVENT_LBUTTONDOWN)                                    //按下左键
	{
		prev_pt = Point(x,y);
	}
	else if ( event == CV_EVENT_MOUSEMOVE && (flags & CV_EVENT_FLAG_LBUTTON))  //移动鼠标并且左拖拽
	{
		Point pt = Point(x, y);		
		if ( prev_pt.x < 0)
		{
			prev_pt = pt;
		}
		line(BG_mask, prev_pt, pt, Scalar(0),1,8,0);        //背景模板上划线
		line(FG_mask, prev_pt, pt, Scalar(255),1,8,0);      //前景模板上划线
		line(img, prev_pt, pt, Scalar::all(255),1,8,0);     //原图上划线
		prev_pt = pt;
		imshow("image", img);
	}
	if (event == CV_EVENT_RBUTTONUP)                        //右键UP,截取所选区域
	{
		Mat BG,FG;     //若希望将圈选的结果相加,定义在外头  
        floodFill(BG_mask,Point(x,y),Scalar(0));   //point种子点所在的连通域被填充  
		floodFill(FG_mask,Point(x,y),Scalar(255));
        imshow("BG_mask",BG_mask);              //显示模板
		imshow("FG_mask",FG_mask);  
        img.copyTo(BG,BG_mask);//mask中所有不为零的点被dst对应的值填充  
		img.copyTo(FG,FG_mask);//mask中所有不为零的点被dst对应的值填充  
        imshow("FG",FG);  
		imshow("BG",BG); 
        img.copyTo(img);  
        BG_mask.setTo(Scalar(255));   //重新取值
		FG_mask.setTo(Scalar(0));     //重新取值
	}
}

int main( )
{
	Mat image = imread("D:/Programs_L/OpenCV249/sources/samples/cpp/tutorial_code/images/cat.jpg");
	if(!image.data)
	{
		cout<<"Can not open the image!"<<endl;
		system("pause");
	}	

	//初始化前景和背景模板	
	FG_mask = Mat(image.size(),CV_8UC1,Scalar(0));    //纯白
	BG_mask = Mat(image.size(),CV_8UC1,Scalar(255));  //纯黑
	
	image.copyTo(img);	
	imshow("image",img);  //显示原图
		
	//鼠标回调函数
	cvSetMouseCallback("image",on_mouse,0);	
	
	waitKey(0);
	return 0;
}
结果如下



程序3:

//http://blog.csdn.net/gxiaob/article/details/8330579
//截取任意形状
#include "cv.h"
#include "highgui.h"
#include "cxcore.h"  
#include <iostream>
  
using namespace std;
using namespace cv;

void MouseDraw(int event,int x,int y,int flags,void*param);
  
struct MouseArgs{
    IplImage* img;
    CvPoint p_start;
    CvPoint p_end;
    CvSeq* seq;
    CvMemStorage* storage;
    int points;
    // init
    MouseArgs():img(0),points(0){
        p_start = cvPoint(-1,-1);
        p_end = cvPoint(-1,-1);
        storage = cvCreateMemStorage(0);
        seq = cvCreateSeq( CV_32SC2,sizeof(CvSeq),sizeof(CvPoint), storage );
    }
    // destroy
    void Destroy(){
        if(!img)
            cvReleaseImage(&img);
        cvReleaseMemStorage(&storage );
        seq = NULL;
        img = NULL;
    }
};
  
int main( int argc,char** argv )
{
    // loading image
    char* imf = "D:/Programs_L/OpenCV249/sources/samples/cpp/tutorial_code/images/cat.jpg";
  
    IplImage* pImg_org = cvLoadImage(imf,1);
    if(!pImg_org){
        cout<<"cann't load image!"<<endl;
        return-1;
    }
  
    // 回调参数
    MouseArgs* m_arg =new MouseArgs();
    m_arg->img = cvCloneImage(pImg_org);
  
    // 画图窗口
    cvNamedWindow("Draw ROI",CV_WINDOW_AUTOSIZE);
  
    // 设置鼠标事件的回调函数
    cvSetMouseCallback("Draw ROI",MouseDraw,(void*)m_arg); 
  
    // 拖动鼠标作画
    while(1)
    {
        cvShowImage("Draw ROI",m_arg->img);
        // 按 esc 键退出绘图模式,获得矩形
        if(cvWaitKey(100)==27)
            break;
  
    }
  
    // 输出
    if(m_arg->points < 1)
        return 0;
    cout<<m_arg->points <<endl;
  
    // 获得掩模
    IplImage* mask = cvCreateImage( cvGetSize(pImg_org), 8, 1 );
    cvZero(mask);
  
    CvPoint* PointArr =new CvPoint[m_arg->points];
    cvCvtSeqToArray(m_arg->seq, PointArr);
    cvFillConvexPoly(mask,PointArr,m_arg->points,cvScalarAll(255),CV_AA,0);
    delete[] PointArr;
    cvNamedWindow("Mask",CV_WINDOW_AUTOSIZE);
    cvShowImage("Mask",mask);
  
    // 获得区域
    IplImage* roi = cvCreateImage( cvGetSize(pImg_org), 8, 3 );
    cvCopy(pImg_org,roi,mask);
    cvNamedWindow("ROI",CV_WINDOW_AUTOSIZE);
    cvShowImage("ROI",roi);
  
    //
    cvWaitKey(0);
    cvDestroyWindow("Draw ROI");
    cvDestroyWindow("Mask");
    cvDestroyWindow("ROI");
  
    //
    m_arg->Destroy ();
    delete m_arg;
    cvReleaseImage(&pImg_org);
    cvReleaseImage(&mask);
    cvReleaseImage(&roi);
    //
    getchar();
    return 0;
}
  
// 描点式
/*
void MouseDraw(int event,int x,int y,int flags,void*param)
{
    MouseArgs* m_arg = (MouseArgs*) param;
    if( !m_arg->img )
        return;
  
    if( event == CV_EVENT_LBUTTONUP || !(flags & CV_EVENT_FLAG_LBUTTON) )
    {
        m_arg->p_end = m_arg->p_start;
    }
    else if( event == CV_EVENT_LBUTTONDOWN )
    {
        m_arg->p_start = cvPoint(x,y);
        cvSeqPush( m_arg->seq, &m_arg->p_start);  // 描点记录
        m_arg->points += 1;
        if(m_arg->p_start.x>0 && m_arg->p_end.x>0){
            cvLine( m_arg->img, m_arg->p_start, m_arg->p_end, cvScalar(0,0,255) );
            cvLine( m_arg->img, m_arg->p_start, m_arg->p_start, cvScalar(128,0,255) );
        }
    }
  
}
*/

// 拖动式
void MouseDraw(int event,int x,int y,int flags,void*param)
{
    MouseArgs* m_arg = (MouseArgs*) param;
    if( !m_arg->img )
        return;
  
    if( event == CV_EVENT_LBUTTONUP || !(flags & CV_EVENT_FLAG_LBUTTON) )
    {
        m_arg->p_start = cvPoint(x,y);
    }
    else if( event == CV_EVENT_LBUTTONDOWN )
    {
        m_arg->p_start = cvPoint(x,y);
        cvSeqPush( m_arg->seq, &m_arg->p_start);
        m_arg->points += 1;
        if(m_arg->p_start.x>0 && m_arg->p_end.x>0){
            cvLine( m_arg->img, m_arg->p_start, m_arg->p_start, cvScalar(128,0,255) );
        }
    }
    else if( event == CV_EVENT_MOUSEMOVE && (flags & CV_EVENT_FLAG_LBUTTON) )
    {
        CvPoint pt = cvPoint(x,y);
        if( m_arg->p_start.x > 0 ){
            cvLine( m_arg->img, m_arg->p_start, pt, cvScalar(128,0,255) );
            m_arg->p_start = pt;
            cvSeqPush( m_arg->seq, &m_arg->p_start);
            m_arg->points += 1;
        }  
    }  
}
程序3能用描点法截取区域。

3. setmousecallback的说明

void setMousecallback(const string& winname, MouseCallback onMouse, void* userdata=0)
      winname:窗口的名字
      onMouse:鼠标响应函数,回调函数。指定窗口里每次鼠标时间发生的时候,被调用的函数指针。 这个函数的原型应该为void onMouse(int event, int x, int y, int flags, void* param);
      userdate:传给回调函数的参数 

event:
#defineCV_EVENT_MOUSEMOVE          0      移动
#defineCV_EVENT_LBUTTONDOWN     1      左键按下
#defineCV_EVENT_RBUTTONDOWN     2      右键按下
#defineCV_EVENT_MBUTTONDOWN    3       中键按下
#defineCV_EVENT_LBUTTONUP            4      左键升起
#defineCV_EVENT_RBUTTONUP           5      右键升起
#defineCV_EVENT_MBUTTONUP           6      中键升起
#defineCV_EVENT_LBUTTONDBLCLK   7      左键双击
#defineCV_EVENT_RBUTTONDBLCLK   8      右键双击
#defineCV_EVENT_MBUTTONDBLCLK  9      中键双击
 flag:
#defineCV_EVENT_FLAG_LBUTTON      1      左键拖曳
#defineCV_EVENT_FLAG_RBUTTON     2      右键拖曳
#defineCV_EVENT_FLAG_MBUTTON     4      中键拖曳
#defineCV_EVENT_FLAG_CTRLKEY      8      (8~15)按Ctrl不放事件
#defineCV_EVENT_FLAG_SHIFTKEY    16      (16~31)按Shift不放事件
#defineCV_EVENT_FLAG_ALTKEY        32      (32~39)按Alt不放事件


event是鼠标动作,flag是鼠标动作标志符,两者结合就能描述鼠标的各种动作了。如果要描述鼠标拖拽画线的操作,可以这样判断

 event == CV_EVENT_MOUSEMOVE && (flags & CV_EVENT_FLAG_LBUTTON)


关于使用setmousecallback的几点注意事项

1)回调函数是void类型的,也就是说没有返回值,如果要把某变量的值传回main,有两种方法:1、把该变量定义为全局变量;2、把要回传的参数写成一个类,参照 http://blog.csdn.net/gxiaob/article/details/8213796

2)回调函数一旦运行就会监听鼠标动作,相当于开了一个和main并行的进程,该进程会随着main结束而结束。

      这一点可以从上面的程序得到印证。同样是抠图,程序1用的是视频,程序2和程序3用的是图片,程序1的图片显示在main中实现,程序2和程序3是在回调函数中实现。如果我们想用图片,并且在main中显示结果,那么必须将原main程序的主体放在一个循环里。也就是让程序不停地显示图片,不停的监听。否则,虽然回调函数还在监听鼠标动作,但main已经运行到程序结尾处。尽管回调函数获得的参数是对的,但main里已经没有变量来接受这些参数了。

3)一般情况下,回调函数一旦运行就会一直运行,除非主函数结束。而这会占用系统资源,我们可以在获得想要的参数后中止回调函数运行。方法是destroyWindow( ),只要监听的窗口没了,回调函数也就结束了。






猜你喜欢

转载自blog.csdn.net/GDFSG/article/details/50841127