第四章 学习OpenCV——细说HighGUI
目录
例4-1 使用鼠标在窗口中画方形
调用cvSetMouseCallback()函数来注册鼠标回调函数,以响应鼠标事件,并根据具体的event来确定给出的响应。具体代码如下:
#include <cv.h>
#include <highgui.h>
#include <stdlib.h>
#include <stdio.h>
using namespace std;
void my_mouse_callback(int event, int x, int y,
int flags, void* param); //声明回调函数
CvRect box; //所画矩形的角点、宽高
bool drawing_box = false; //判断左键是否按下,按下为true
void draw_box(IplImage* img, CvRect rect) //绘制矩形
{
cvRectangle(img,
cvPoint(box.x,box.y), //两个对顶点
cvPoint(box.x+box.width,box.y+box.height),
cvScalar(0x00, 0x00, 0xff)); //BGR 设置为红色
}
int main(int argc, char* argv[])
{
box = cvRect(-1, -1, 0, 0); //(x,y,width,height)
IplImage* image = cvCreateImage(cvSize(200, 200), IPL_DEPTH_8U, 3); //200*200 8位 3通道
cvZero(image); //清零
IplImage* temp = cvCloneImage(image); //克隆图像,复制图像随原图像变化
cvNamedWindow("Box Example");
cvSetMouseCallback("Box Example", my_mouse_callback, (void*)image); //注册回调函数
//事件产生窗口、回调函数名、事件产生影响的类
while (1)
{
cvCopyImage(image, temp); //复制图像,不随原图像变化(清除temp图像上实时显示的那部分矩形)
if (drawing_box) //左键按下,开始绘制(左键按下时动态显示矩形)
draw_box(temp, box); //在temp图像上画矩形
cvShowImage("Box Example", temp); //显示temp
if (cvWaitKey(15) == 27) //15ms刷新一次
break;
}
cvReleaseImage(&image);
cvReleaseImage(&temp);
cvDestroyWindow("Box Example");
}
void my_mouse_callback(int event, int x, int y, int flags, void* param)
{
IplImage* image = (IplImage*)param;
switch (event)
{
case CV_EVENT_MOUSEMOVE:
{
if (drawing_box) //左键已按下标志,开始绘制
{
box.width = x - box.x; //x为当前鼠标的位置
box.height = y - box.y; //box.x为左键按下时的位置
}
}
break;
case CV_EVENT_LBUTTONDOWN:
{
drawing_box = true;
box = cvRect(x, y, 0, 0); //记录矩形起始位置
}
break;
case CV_EVENT_LBUTTONUP:
{
drawing_box = false; //左键已松开标志,结束绘制
if (box.width<0) //将width转化为正数,保证绘图从左向右进行
{
box.x += box.width; //box.x=box.x+box.width 回移起点位置
box.width *= -1; //box.x为左键按下时的位置
}
if (box.height<0) //将height转化为正数,保证绘图从上向下进行
{
box.y += box.height;
box.height *= -1;
}
draw_box(image, box);
}
break;
}
}
运行结果如下图:
例4-2 使用滚动条实现一个开关功能,用户可以选择打开或关闭
调用cvCreateTrackbar()函数来创建一个2值滚动条,以实现按钮开关功能。具体代码如下:
#include <cv.h>
#include <highgui.h>
#include <stdlib.h>
#include <stdio.h>
using namespace std;
int g_switch_value = 0;
void switch_off_function()
{
cout << "switch off" << endl;
cout << g_switch_value << endl;
}
void switch_on_function()
{
cout << "switch on" << endl;
cout << g_switch_value << endl;
}
void switch_callback(int position)
{
if (position == 0)
switch_off_function();
else
switch_on_function();
}
int main(int argc, char* argv[])
{
cvNamedWindow("Demo Window",1);
cvCreateTrackbar("Switch", "Demo Window", &g_switch_value, 1, switch_callback);
//创建滚动条(滚动条名称,所属窗口,当前位置指针【拖动时,替换指针指向的整数】,最大值,回调函数)
while (1)
{
if (cvWaitKey(15) == 27) //15ms刷新一次
break;
}
cvDestroyWindow("Demo Window");
}
运行结果如下图:
例4-3 视频图像读入、处理及合并显示
本例完成的工作如下:
1. 从视频文件中读入数据;
2. 将读入的图像转换为灰度图;
3. 对图像做Canny边缘检测;
4. 将三个过程的处理结果显示在不同的窗口中;
5. 将三个步骤显示在一个图像中;
6. 在图像的三个不同的部分上分别写上合适的文字标签;
具体代码如下:
#include <cv.h>
#include <highgui.h>
using namespace std;
IplImage* doCanny(IplImage *in, double lowThresh, double highThresh, double aperture)
{
if (in->nChannels != 1)
{
cout << in->nChannels << endl;
cout << "不能输出" << endl;
return(0); //Canny只能处理灰度图像
}
IplImage* out = cvCreateImage(cvGetSize(in), IPL_DEPTH_8U, 1); //cvSize(cvGetSize(image)),报错
cvCanny(in, out, lowThresh, highThresh, aperture);
//(输入,输出,控制边缘连接,强边缘初始分割,Sobel算子大内核大小)
return(out);
}
int main(int argc, char** argv)
{
cvNamedWindow("Natural", CV_WINDOW_AUTOSIZE);
cvNamedWindow("Gray", CV_WINDOW_AUTOSIZE);
cvNamedWindow("Canny", CV_WINDOW_AUTOSIZE);
cvNamedWindow("All", CV_WINDOW_AUTOSIZE);
CvCapture* capture = cvCreateFileCapture("D:\\Template\\OpenCV\\Template21_AVI2Gray_Canny_Text\\ConsoleApplication1\\好想告诉你.avi");
IplImage* frame1=cvQueryFrame(capture);
CvFont font; //字体变量
int width = (int)cvGetCaptureProperty(capture, CV_CAP_PROP_FRAME_WIDTH);
int height = (int)cvGetCaptureProperty(capture, CV_CAP_PROP_FRAME_HEIGHT);
CvSize size = cvSize(width,height);
CvSize size1 = cvSize(width * 3, height);
IplImage* frame2 = cvCreateImage(size, IPL_DEPTH_8U, 1); //单通道,8位,相同大小,灰度
IplImage* frame3 = cvCreateImage(size, IPL_DEPTH_8U, 1); //Canny
IplImage* frame_gray = cvCreateImage(size, IPL_DEPTH_8U, 3); //三通道
IplImage* frame_canny = cvCreateImage(size, IPL_DEPTH_8U, 3);
IplImage* frame4 = cvCreateImage(size1, IPL_DEPTH_8U, frame1->nChannels); //All
cvZero(frame4);
//创建三个新的图像头,通道、深度与原图相同,分别指向开始处、1/3处、2/3处
IplImage* imgheader1 = cvCreateImageHeader(size, frame1->depth, frame1->nChannels);
//IplImage* imgheader2 = cvCreateImageHeader(size, frame2->depth, frame2->nChannels);//错 压缩为1/3
//IplImage* imgheader3 = cvCreateImageHeader(size, frame3->depth, frame3->nChannels);
IplImage* imgheader2 = cvCreateImageHeader(size, frame2->depth, frame1->nChannels); //正确
IplImage* imgheader3 = cvCreateImageHeader(size, frame3->depth, frame1->nChannels);
imgheader1->widthStep = frame4->widthStep; //设置widthStep与原图像相同
imgheader2->widthStep = frame4->widthStep;
imgheader3->widthStep = frame4->widthStep;
imgheader1->origin = frame4->origin; //设置原点与原图像相同 左上
imgheader2->origin = frame4->origin;
imgheader3->origin = frame4->origin;
imgheader1->imageData = frame4->imageData; //指向开始像素位置
imgheader2->imageData = frame4->imageData + 1 * 3 * width * frame2->nChannels; //指向1/3像素位置
imgheader3->imageData = frame4->imageData + 2 * 3 * width * frame3->nChannels; //指向2/3像素位置
cvInitFont(&font, CV_FONT_HERSHEY_COMPLEX, 1.0, 1.0, 1.0);
while (1)
{
frame1 = cvQueryFrame(capture);
if (!frame1) break;
cvConvertImage(frame1, frame2, CV_BGR2GRAY); //图像强制转化为gray ****
frame3 = doCanny(frame2, 50, 150, 3); //灰度图做Canny边缘检测
//cvCopy(frame1, imgheader1); //错误,此时显示后两幅图像会被压缩为1/3
//cvCopy(frame2, imgheader2);
//cvCopy(frame3, imgheader3);
cvCopy(frame1, imgheader1); //正确
cvConvertImage(frame2, frame_gray, CV_GRAY2BGR);
cvCopy(frame_gray, imgheader2);
cvConvertImage(frame3, frame_canny, CV_GRAY2BGR);
cvCopy(frame_canny, imgheader3);
cvPutText(frame4, "Origin", cvPoint(100, 25), &font, cvScalar(255, 255, 255)); //文字标签
cvPutText(frame4, "Gray", cvPoint(450, 25), &font, cvScalar(255, 255, 255));
cvPutText(frame4, "Canny", cvPoint(750, 25), &font, cvScalar(255, 255, 255));
cvShowImage("Natural", frame1);
cvShowImage("Gray", frame2);
cvShowImage("Canny", frame3);
cvShowImage("All", frame4);
char c = cvWaitKey(32);
if (c == 27) break;
}
cvReleaseCapture(&capture);
cvDestroyWindow("Natural");
cvDestroyWindow("Gray");
cvDestroyWindow("Canny");
cvDestroyWindow("All");
}
运行结果如下图:
本例的难点在b中,即实现将所有三个步骤显示在一个图像中,书中提示创建三个图像头,使图像头分别指向图像数据的开始处,1/3处,2/3处,然后使用cvCopy()复制。此时,在创建总的显示图像和三个图像头时,图像的通道数设置很关键,因为三张图像同时显示在一张图片中,但三个图像的通道是不同的,图1位三通道,其后两个为单通道。如果总的显示图像创建为三通道,而三个图像头为一个三通道,两个单通道,上述代码中两部分代码做如下修改:
Part1:
IplImage* imgheader2 = cvCreateImageHeader(size, frame2->depth, frame2->nChannels);
IplImage* imgheader3 = cvCreateImageHeader(size, frame3->depth, frame3->nChannels);
Part2:
cvCopy(frame1, imgheader1);
cvCopy(frame2, imgheader2);
cvCopy(frame3, imgheader3);
此时的显示的整个图像,后两个图像头对应的图像区域会被压缩为1/3,究其原因就是未将后两个图像头转化为三通道图像,其结果如下图:
例4-4 读入图像,获取对应像素的BGR值、坐标值,并显示于图片上
本例完成的工作如下:
1. 读入并显示一张图片;
2. 鼠标点击图像时获取对应像素的颜色值(BGR)和对应像素的坐标;
3. 用文本将颜色值和对应像素的坐标显示在点击处;
本题a中要求将坐标显示到上例图像中,方法类似,具体代码如下:
#include <cv.h>
#include <highgui.h>
using namespace std;
void my_mouse_callback(int event, int x, int y, int flags, void* param); //声明回调函数
void Get_Show_BGR(IplImage* img, CvPoint point) //获取、显示像素点处的RGB
{
CvFont font; //字体变量
CvScalar s; //BGR值变量
char str[40] = { 0 };
cvInitFont(&font, CV_FONT_HERSHEY_COMPLEX, 1.0, 1.0, 1.0); //初始化字体相关参数
s = cvGet2D(img, point.x, point.y);
cout << " B: " << s.val[0] << " G: " << s.val[1] << " R: " << s.val[2];
//字符串格式化命令,格式化的数据写入某个字符串中
sprintf(str, "(%d,%d)(%.0f,%.0f,%.0f)", point.x, point.y, s.val[2], s.val[1], s.val[0]);
cvPutText(img, str, point, &font, cvScalar(255, 255, 255)); //输出相应的文字标签
}
int main(int argc, char** argv) //带参数的mian函数
{
IplImage *img =