C++OpenCV(3):基础交互(视频与鼠标操作)

🔆 文章首发于我的个人博客:欢迎大佬们来逛逛
🔆 OpenCV项目地址及源代码:点击这里

鼠标交互

openCV中使用鼠标的交互的函数是:setMouseCallback

可以使得激活winname为标题的窗口进行onMouse回调函数执行的鼠标交互操作,并且可以传递用户自定义变量给userdata

void setMouseCallback(const String& winname, MouseCallback onMouse, void* userdata = 0);
/*******************************************************************
*			winname: 			监听窗口名称
*			onMouse:			  鼠标事件回调函数
*			userdata:			递给回调函数的可选参数
*********************************************************************/

关于MouseCallBack回调函数:

  • 就是一个函数指针
  • 传递的参数必须一致:event鼠标点击事件,

    (

    x

    ,

    y

    )

    **(x,y)**

    (x,y)坐标,flag鼠标拖拽事件,void* 类型的param可以转为用户自定义变量。

typedef void (*MouseCallback)(int event, int x, int y, int flags, void* userdata);
//MouseCallback onMouse
void onMouse(int event,int x,int y,int flag,void *param)
/*******************************************************************
*			event: 			事件类型
*			x:				鼠标所在图像的坐标
*			y:				
*		    flags:			代表拖拽事件
*			param:			自己定义的onMouse事件的ID
*********************************************************************/

关于event和flag的枚举类型:

  • event:鼠标点击
enum MouseEventTypes {
       EVENT_MOUSEMOVE      = 0, 	//鼠标移动
       EVENT_LBUTTONDOWN    = 1, 	//鼠标左键按下
       EVENT_RBUTTONDOWN    = 2,	//鼠标右键按下 
       EVENT_MBUTTONDOWN    = 3, 	//鼠标中键按下
       EVENT_LBUTTONUP      = 4, 	//鼠标左键弹起
       EVENT_RBUTTONUP      = 5, 	//鼠标右键弹起
       EVENT_MBUTTONUP      = 6, 	//鼠标中键弹起
       EVENT_LBUTTONDBLCLK  = 7, 	//鼠标左键双击
       EVENT_RBUTTONDBLCLK  = 8, 	//鼠标右键双击
       EVENT_MBUTTONDBLCLK  = 9, 	//鼠标中间双击
       EVENT_MOUSEWHEEL     = 10,	//鼠标滚轮 正数和负数分别表示向前和向后滚动
       EVENT_MOUSEHWHEEL    = 11 	//鼠标滚轮 正数和负数分别表示向右和向左滚动  
     };
  • flag:鼠标拖拽
enum MouseEventFlags {
       EVENT_FLAG_LBUTTON   = 1,    //左键拖动
       EVENT_FLAG_RBUTTON   = 2, 	//右键拖动
       EVENT_FLAG_MBUTTON   = 4, 	//中键拖动
       EVENT_FLAG_CTRLKEY   = 8, 	//ctr拖动
       EVENT_FLAG_SHIFTKEY  = 16,	//shift拖动
       EVENT_FLAG_ALTKEY    = 32 	//alt拖动
     };

案例

在一张图片中左键点击画圆,右键点击画矩形。

有关如何在openCV中绘制图形请参见:

OpenCV(2):图像处理基础概念与操作 | HugeYlh

关键之处就是要有一个鼠标回调函数:

可以声明为类的静态成员函数,然后利用params进行强制转换为本类类型。

接着判断event点击的事件进行绘制即可。

static void mouseEvent(int event, int x, int y, int flag, void* params) {
		DrawShape* obj = static_cast<DrawShape*>(params);
		if (event == cv::EVENT_FLAG_LBUTTON) {
			//左键画圆
			obj->drawCircle(x, y, 20);
		}
		else if (event == cv::EVENT_FLAG_RBUTTON) {
			obj->drawRectangle(x, y, 20, 20);
		}
	}

然后在主程序中我们要激活这个回调函数,设置鼠标点击:

  • 其中传递回调函数要使用函数指针的形式,即传递类的静态成员函数的地址
  • p表示我们需要传递自定义变量,以便在回调函数中params转换为我们需要操作的obj
int main()
{
	DrawShape* p = new DrawShape();

	//鼠标处理过程
	cv::namedWindow("mainWindow");
	//typedef void (*MouseCallback)
	//			(int event, int x, int y, int flags, void* userdata);
	cv::setMouseCallback("mainWindow", &DrawShape::mouseEvent, p);

	while (true) {
		p->show();
		if (cv::waitKey(10) == 27) {
			break;
		}
	}
	delete p;
	p = nullptr;
	return 0;
}

完整代码参见此Github项目


视频读写交互

使用openCV做视频操作可能不会如你想象的那么容易,因为openCV是一个强大的计算机视觉库,而不是专注于视频操作的多媒体库。

使用openCV做视频处理不能添加音频

也许FFmpeg 库会满足你做多媒体开发的需求。


**VideoCapture**类型:对视频进行读取或者打开摄像头。

class VideoCapture
{
public:
    VideoCapture();
    explicit VideoCapture(const String& filename, int apiPreference = CAP_ANY);
    explicit VideoCapture(const String& filename, int apiPreference, const std::vector<int>& params);
    explicit VideoCapture(int index, int apiPreference = CAP_ANY);
    explicit VideoCapture(int index, int apiPreference, const std::vector<int>& params);
    virtual ~VideoCapture();
    virtual bool open(const String& filename, int apiPreference = CAP_ANY);
    virtual bool open(const String& filename, int apiPreference, const std::vector<int>& params);
    virtual bool open(int index, int apiPreference = CAP_ANY);
    virtual bool open(int index, int apiPreference, const std::vector<int>& params);
    virtual bool isOpened() const;
    virtual void release();
    virtual bool grab();
    virtual bool retrieve(OutputArray image, int flag = 0);
    virtual VideoCapture& operator >> (CV_OUT Mat& image);
    virtual VideoCapture& operator >> (CV_OUT UMat& image);
    virtual bool read(OutputArray image);
    virtual bool set(int propId, double value);
    virtual double get(int propId) const;
    String getBackendName() const;
    void setExceptionMode(bool enable) { throwOnFail = enable; }
    bool getExceptionMode() { return throwOnFail; }
    bool waitAny(const std::vector<VideoCapture>& streams,CV_OUT std::vector<int>& readyIndex,int64 timeoutNs = 0);
protected:
    Ptr<CvCapture> cap;
    Ptr<IVideoCapture> icap;
    bool throwOnFail;
    friend class internal::VideoCapturePrivateAccessor;
};

读取一个视频:传递给视频的文件地址即可,如果我们传递了 0,则会打开摄像头(如果存在,否则报错)

cv::VideoCapture vap("cat.MP4");
	if (!vap.isOpened()) {
		std::cout << "视频打开失败!n";
		return -1;
	}

cv::VideoCapture vap2(0);
	if (!vap2.isOpened()) { 
		std::cout << "摄像头打开失败!n";
		return;
	}

获取基本视频中的信息:get 函数

通过传递枚举类型来获取指定的信息:

enum VideoCaptureProperties {
       CAP_PROP_POS_MSEC       =0, //视频文件的当前位置,单位为毫秒  
       CAP_PROP_POS_FRAMES     =1, //解码/捕获的帧的基于0的索引
       CAP_PROP_POS_AVI_RATIO  =2, //视频文件的相对位置:0=影片开始,1=影片结束
       CAP_PROP_FRAME_WIDTH    =3, //视频宽度
       CAP_PROP_FRAME_HEIGHT   =4, //视频高度
       CAP_PROP_FPS            =5, //帧率
       CAP_PROP_FOURCC         =6, //4个字符的编解码器代码
       CAP_PROP_FRAME_COUNT    =7, //视频文件中的帧数
       CAP_PROP_FORMAT         =8, //视频格式
                                  
       CAP_PROP_MODE           =9, 
       CAP_PROP_BRIGHTNESS    =10, //图像亮度(摄像模式)
       CAP_PROP_CONTRAST      =11, //图像对比度(摄像模式)
       CAP_PROP_SATURATION    =12, //图像饱和度(摄像模式)
       CAP_PROP_HUE           =13, //图像的色调(摄像模式)
       CAP_PROP_GAIN          =14, //图像增益(摄像模式)
       CAP_PROP_EXPOSURE      =15, //曝光(摄像模式)
       CAP_PROP_CONVERT_RGB   =16, //图像是否应该转换为RGB的布尔标记
	     
				.......
     };

例如:

void testProerity(cv::VideoCapture vap) {
	std::cout << "宽度: " << vap.get(cv::CAP_PROP_FRAME_WIDTH) << 'n';
	std::cout << "高度: " << vap.get(cv::CAP_PROP_FRAME_HEIGHT) << 'n';
	std::cout << "帧数: " << vap.get(cv::CAP_PROP_FRAME_COUNT) << 'n';
	std::cout << "帧率: " << vap.get(cv::CAP_PROP_FPS) << 'n';
}

视频(摄像头)转图像显示

我们加载好视频后,注意到VideoCapture重载了 >> 运算符,因此可以将其重定向到一张Mat上面。

然后再一直显示这张Mat,就可以做到一张一张的图片显示,看起来像视频一样。

如果我们按下ESC则退出或者播放完成后,图片为null

void testCameraToImageShow(cv::VideoCapture vedio) {
	//显示视频
	cv::Mat image;
	while (true) {
		vedio >> image;
		if (image.empty() || cv::waitKey(10) == 27) {
			break;//为null则结束
		}
		cv::imshow("cat", image);
	}
	vedio.release();
}

对于摄像头的转图片显示,我们只需要传递一个 VideoCapture xxx(0) 即可其他的全是一样的。


视频转图片并且保存

基本操作与上类似,只不过在**imshow**的地方我们改成了保存的操作(当然你也可以一边显示一边保存)

保存过程:imwrite函数

  • name:第一个参数表示保存的路径,传递前缀文件名字后缀来完成:cat/1.png
  • image:保存的图片
void testCameraToPngImageSave(cv::VideoCapture vedio,std::string& prefilename) {
	cv::Mat image;
	int index = 1;
	while (true) {
		vedio >> image;
		if (image.empty() || cv::waitKey(10) == 27) {
			break;
		}
		std::string name = prefilename + std::to_string(index++) + ".png";
		cv::imwrite(name, image);
	}
	vedio.release();
}

摄像头转图片并保存

如果是摄像头则我们不能用:imwrite

有一个**VideoWriter** 提供了这样的操作:通过创建一个VideoWriter类型的变量,然后通过 << 重载往它里面写入来完成。

注意**VideoWriter** 的创建:

  • “save.avi” :表示保存的视频路径
  • cv::VideoWriter::fourcc(‘M’, ‘J’, ‘P’, ‘G’):视频的解码器,MJPG表示mp4格式,点击了解更多格式
  • 30:fps帧率
  • cv::Size(width,height):保存的尺寸大小
  • true:是否显示颜色(三通道)
save.open("save.avi", cv::VideoWriter::fourcc('M', 'J', 'P', 'G'),30, cv::Size(width,height), true);

具体操作如下:

void CameraSave() {
	cv::VideoCapture cap(0);
	if (!cap.isOpened()) {
		std::cout << "摄像头打开失败!n";
		return;
	}
	//获取宽度和高度
	int width = cap.get(cv::CAP_PROP_FRAME_WIDTH);
	int height = cap.get(cv::CAP_PROP_FRAME_HEIGHT);
	cv::VideoWriter save{};
	save.open("save.avi", 
		cv::VideoWriter::fourcc('M', 'J', 'P', 'G'),
		30, cv::Size(width,height), true);
	cv::Mat image;
	while (true) {
		cap >> image;
		cv::imshow("摄像头", image);
		save << image; //往流中写入
		if (cv::waitKey(10) == 27) {
			break;
		}
	}
	cap.release();
	save.release();
}

滑动条交互

对于滑动条交互也是和鼠标交互类似的:

int createTrackbar(const String& trackbarname, const String& winname,int* value, int count,TrackbarCallback onChange = 0,void* userdata = 0);
/*******************************************************************
*			trackbarname: 		滑动条名字
*			winname:			    依附窗口名
*			value:				    滑块位置
*			count:				    滑块最大值(最小值是0)
*			onChange:			  滑块回调函数
*			userdata:			  用户回传给回调函数的数据
*********************************************************************/

TrackbarCallback 回调函数:

typedef void (*TrackbarCallback)(int pos, void* userdata);
void On_Trackbar(int pos, void* userdata);
/*******************************************************************
*			pos: 			    位置
*			userdata:			用户回传给回调函数的数据
*********************************************************************/

滑动条调整图片的亮度

涉及到对图片的像素操作

详细请看上节内容:

OpenCV(2):图像处理基础概念与操作 | HugeYlh

我们规定一个初始值:current和一个最大值:maxValue,通过调节可以调整此值:

定义:

  • 一个beta表示偏移量
  • alpha表示亮度的调整值
  • copyMat:对一个新的Mat进行像素运算操作,否则无法还原。

对于三通道RGB来说:

  • 如果我们调整为最小值:0
    • 则RGB为(30,30,30)此时为黑色,因此beta就是我们的最小颜色值,即偏移量
  • 然后alpha会根据我们当前滑动条的值进行调整,变大
    • 然后执行像素运算后(xxx,xxx,xxx)就会越来越大,达到亮度提高的效果。

最后显示此图像即可。


对于此回调函数的实现:

//滑动条回调函数
	static void OnTrack(int pos,void* params){
		TrackBar* obj = static_cast<TrackBar*>(params);
		cv::Mat copyMat = cv::Mat::zeros(obj->mt.size(), obj->mt.type());
		int dims = obj->mt.channels();
		float beta = 30, alpha = 0.1 + (float)pos / 10.0;
		for (int i = 0; i < obj->mt.rows; i++) {
			for (int j = 0; j < obj->mt.cols; j++) {
				if (dims == 1) {
					uchar pix = obj->mt.at<uchar>(i, j);
					copyMat.at<uchar>(i,j) = cv::saturate_cast<uchar>(pix * alpha + beta);
				}
				else if (dims == 3) {
					cv::Vec3b vec = obj->mt.at<cv::Vec3b>(i, j);
					float b = vec[0], g = vec[1], r = vec[2];
					copyMat.at<cv::Vec3b>(i, j)[0] = cv::saturate_cast<uchar>(b * alpha + beta);
					copyMat.at<cv::Vec3b>(i, j)[1] = cv::saturate_cast<uchar>(g * alpha + beta);
					copyMat.at<cv::Vec3b>(i, j)[2] = cv::saturate_cast<uchar>(r * alpha + beta);
				}
				else {
					return;
				}
			}		
		}
		cv::imshow("trackBarWindow", copyMat); //显示操作后的图像
	}

完整代码详见Github项目源码。

https://github.com/luumod/openCV-learning-record

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码

)">
< <上一篇
下一篇>>