如果要在OpenCV窗口是显示大图,OpenCV的namedWindow是不提供滚动条的,当然可以用CV_WINDOW_AUTOSIZE参数把图像缩小到指定窗口中,但这种处理使用了resize函数,窗口大小设置不合适就会产生失真(当然还可以加上参数CV_WINDOW_KEEPRATIO来固定长宽比),有时候希望在窗口中显示原图,这就需要自己画出滚动条,然后用onMouse事件来控制图像显示。
源码如下,主体是网上流行的,但里面的一些函数是低版本的OpenCV的(如CvRect、CvRectangleR、cvSetImageROI等等),我把它们改成了高版本的函数(如Rect, rectangle,删除cvSetImageROI),这样做是为了使用Mat结构体能自己释放内存的能力。
(以下代码在VS2015+OpenCV3.3上实现)
// Image_ScrollBar.cpp : Defines the entry point for the console application.
//
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc_c.h>
#include <iostream>
#include <vector>
using namespace std;
using namespace cv;
double mx = 0, my = 0;
int dx = 0, dy = 0, horizBar_x = 0, vertiBar_y = 0;
bool clickVertiBar = false, clickHorizBar = false, needScroll = false;
Rect rect_bar_horiz, rect_bar_verti;
void help()
{
printf(
"/n"
"This program demonstrated the use of the cvSetMouseCallback /n"
"for viewing large image with scroll bar in a small window/n"
"created by OpenCV highgui model. (chenyusiyuan, 2011-06-24)/n"
"Call:/n"
"./Image_ScrollBar [<img_filename default im.jpg> <window_width default 1400> <window_height default 700>]/n/n"
);
}
void mouse_callback( int event, int x, int y, int flags, void* param )
{
if (needScroll)
{
switch( event )
{
case CV_EVENT_LBUTTONDOWN:
mx = x, my = y;
dx = 0, dy = 0;
// 按下左键时光标定位在水平滚动条区域内
if (x >= rect_bar_horiz.x && x <= rect_bar_horiz.x+rect_bar_horiz.width
&& y >= rect_bar_horiz.y && y<= rect_bar_horiz.y+rect_bar_horiz.height)
{
clickHorizBar = true;
}
// 按下左键时光标定位在垂直滚动条区域内
if (x >= rect_bar_verti.x && x <= rect_bar_verti.x+rect_bar_verti.width
&& y >= rect_bar_verti.y && y<= rect_bar_verti.y+rect_bar_verti.height)
{
clickVertiBar = true;
}
break;
case CV_EVENT_MOUSEMOVE:
if (clickHorizBar)
{
dx = fabs(x-mx) > 1 ? (int)(x-mx) : 0;
dy = 0;
}
if (clickVertiBar)
{
dx = 0;
dy = fabs(y-my) > 1 ? (int)(y-my) : 0;
}
mx = x, my = y;
break;
case CV_EVENT_LBUTTONUP:
mx = x, my = y;
dx = 0, dy = 0;
clickHorizBar = false;
clickVertiBar = false;
break;
default:
dx = 0, dy = 0;
break;
}
}
}
void myShowImageScroll(char* title, Mat src_img)
{
Mat dst_img, dstROI;
Rect rect_dst, // 窗口中有效的图像显示区域
rect_src; // 窗口图像对应于源图像中的区域
int imgWidth = img.cols,
imgHeight = img.rows,
barWidth = 20; // 滚动条的宽度(像素)
CRect rect;
int winWidth = 1400;
int winHeight = 900;
double scale_w = (double)imgWidth / (double)winWidth, // 源图像与窗口的宽度比值 用以判断是否超出显示范围
scale_h = (double)imgHeight / (double)winHeight; // 源图像与窗口的高度比值 用以判断是否超出显示范围
if (scale_w<1) //如果小于1 说明原图比窗口小,窗口的宽度将重新赋值
winWidth = imgWidth + barWidth;
if (scale_h<1) //如果小于1 说明原图比窗口小,窗口的高度将重新赋值
winHeight = imgHeight + barWidth;
int showWidth = winWidth, showHeight = winHeight; // 窗口中有效的图像显示区域的宽和高
int src_x = 0, src_y = 0; // 源图像中 rect_src 的左上角位置
int horizBar_width = 0, horizBar_height = 0, //定义并初始化垂直于水平滑块的宽高
vertiBar_width = 0, vertiBar_height = 0;
needScroll = scale_w>1.0 || scale_h>1.0 ? true : false;
// 若图像大于设定的窗口大小,则显示滚动条
if (needScroll)
{
dst_img = Mat(Size(winWidth, winHeight), img.type());
// 源图像宽度大于窗口宽度,则显示水平滚动条
if (scale_w > 1.0) //宽度超出了
{
showHeight = winHeight - barWidth;
horizBar_width = (int)((double)winWidth / scale_w);
horizBar_height = winHeight - showHeight;
horizBar_x = min(
max(0, horizBar_x + dx),
winWidth - horizBar_width);
rect_bar_horiz = Rect(
horizBar_x,
showHeight + 1,
horizBar_width,
horizBar_height);
// 显示水平滚动条
rectangle(dst_img, rect_bar_horiz, cvScalarAll(255), -1);
}
// 源图像高度大于窗口高度,则显示垂直滚动条
if (scale_h > 1.0) //高度超出了
{
showWidth = winWidth - barWidth;
vertiBar_width = winWidth - showWidth;
vertiBar_height = (int)((double)winHeight / scale_h);
vertiBar_y = min(
max(0, vertiBar_y + dy),
winHeight - vertiBar_height);
rect_bar_verti = Rect(
showWidth + 1,
vertiBar_y,
vertiBar_width,
vertiBar_height); //确定垂直滚动条的白色部分的大小
// 显示垂直滚动条
rectangle(dst_img, rect_bar_verti, cvScalarAll(255), -1);
}
showWidth = min(showWidth, imgWidth);
showHeight = min(showHeight, imgHeight);
// 设置窗口显示区的 ROI
rect_dst = Rect(0, 0, showWidth, showHeight);
dstROI = dst_img(rect_dst);
// 设置源图像的 ROI
src_x = (int)((double)horizBar_x*scale_w);
src_y = (int)((double)vertiBar_y*scale_h);
src_x = min(src_x, imgWidth - showWidth);
src_y = min(src_y, imgHeight - showHeight);
rect_src = Rect(src_x, src_y, showWidth, showHeight);
// 将源图像内容复制到窗口显示区
img(rect_src).convertTo(dstROI, dstROI.type());
// 显示图像和滚动条
imshow(winName, dst_img);
}
// 源图像小于设定窗口,则直接显示图像,无滚动条
else
{
imshow(winName, img);
}
}
int main(int argc, char** argv)
{
help();
const char* filename = argc > 1 ? argv[1] : "1.bmp";
int width = 1200, height = 800;
if (4==argc)
{
sscanf( argv[2], "%u", &width );
sscanf( argv[3], "%u", &height );
}
namedWindow("Image Scroll Bar", 1);
setMouseCallback("Image Scroll Bar", mouse_callback);
Mat image = imread( filename);
if( !image )
{
fprintf( stderr, "Can not load %s and/or %s/n"
"Usage: Image_ScrollBar [<img_filename default im.jpg>]/n",
filename );
exit(-1);
}
while(1)
{
myShowImageScroll("Image Scroll Bar", image);
int KEY = cvWaitKey(10);
if( (char) KEY == 27 )
break;
}
return 0;
}
另外,这个方法只适用于用OpenCV自己的窗口显示图像,因为滚动条是画出来的,所以要在while中不停的调用,如果要在MFC框架上显示,最好还是在View是想办法,因为CScrollView直接提供滚动条。
MFC框架下OpenCV显示大图(CScrollView滚动条)已经实现,等有时间了把方法总结一下。