🔆 文章首发于我的个人博客:欢迎大佬们来逛逛
🔆 OpenCV项目地址及源代码:点击这里
文章目录
鼠标交互
openCV中使用鼠标的交互的函数是:setMouseCallback
可以使得激活 对winname
为标题的窗口进行onMouse
回调函数执行的鼠标交互操作,并且可以传递用户自定义变量给userdata
cpp
void setMouseCallback(const String& winname, MouseCallback onMouse, void* userdata = 0);
/*******************************************************************
* winname: 监听窗口名称
* onMouse: 鼠标事件回调函数
* userdata: 递给回调函数的可选参数
*********************************************************************/
关于MouseCallBack回调函数:
- 就是一个函数指针
- 传递的参数必须一致:event鼠标点击 事件, ∗ ∗ ( x , y ) ∗ ∗ **(x,y)** ∗∗(x,y)∗∗坐标,flag鼠标拖拽 事件,void* 类型的param可以转为用户自定义变量。
cpp
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:鼠标点击
cpp
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:鼠标拖拽
cpp
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点击的事件进行绘制即可。
cpp
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
cpp
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
**类型:对视频进行读取或者打开摄像头。
cpp
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
,则会打开摄像头(如果存在,否则报错)
cpp
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
函数
通过传递枚举类型来获取指定的信息:
cpp
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的布尔标记
.......
};
例如:
cpp
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
了
cpp
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:保存的图片
cpp
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:是否显示颜色(三通道)
cpp
save.open("save.avi", cv::VideoWriter::fourcc('M', 'J', 'P', 'G'),30, cv::Size(width,height), true);
具体操作如下:
cpp
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();
}
滑动条交互
对于滑动条交互也是和鼠标交互类似的:
cpp
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
回调函数:
cpp
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)就会越来越大,达到亮度提高的效果。
最后显示此图像即可。
对于此回调函数的实现:
cpp
//滑动条回调函数
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项目源码。