1、漫水填充
漫水填充法的基本思想:
简单来说,就是自动选中了和种子点相连的区域,接着将该区域替换成指定的颜色,这是个非常有用的功能,经常用来标记或者分离图像的一部分进行处理或分析.漫水填充也可以用来从输入图像获取掩码区域,掩码会加速处理过程,或者只处理掩码指定的像素点.
以此填充算法为基础,类似photoshop的魔术棒选择工具就很容易实现了。漫水填充(FloodFill)是查找和种子点联通的颜色相同的点,魔术棒选择工具则是查找和种子点联通的颜色相近的点,将和初始种子像素颜色相近的点压进栈作为新种子
在OpenCV中,漫水填充是填充算法中最通用的方法。且在OpenCV 2.X中,使用C++重写过的FloodFill函数有两个版本。一个不带掩膜mask的版本,和一个带mask的版本。这个掩膜mask,就是用于进一步控制哪些区域将被填充颜色。这两个版本的FloodFill,都必须在图像中选择一个种子点,然后把临近区域所有相似点填充上同样的颜色,不同的是,不一定将所有的邻近像素点都染上同一颜色,漫水填充操作的结果总是某个连续的区域。当邻近像素点位于给定的范围(从loDiff到upDiff)内或在原始seedPoint像素值范围内时,FloodFill函数就会为这个点涂上颜色。
第一个版本的floodFill:
int floodFill(InputOutputArray image, Point seedPoint, Scalar newVal, Rect* rect=0, Scalar loDiff=Scalar(), Scalar upDiff=Scalar(), int flags=4 )
第二个版本的floodFill:
int floodFill(InputOutputArray image, InputOutputArray mask, Point seedPoint,Scalar newVal, Rect* rect=0, Scalar loDiff=Scalar(), Scalar upDiff=Scalar(), int flags=4 )
下面是一起介绍的参数详解。除了第二个参数外,其他的参数都是共用的。
第一个参数,InputOutputArray类型的image, 输入/输出1通道或3通道,8位或浮点图像,具体参数由之后的参数具体指明。
第二个参数, InputOutputArray类型的mask,这是第二个版本的floodFill独享的参数,表示操作掩模,。它应该为单通道、8位、长和宽上都比输入图像 image 大两个像素点的图像。第二个版本的floodFill需要使用以及更新掩膜,所以这个mask参数我们一定要将其准备好并填在此处。需要注意的是,漫水填充不会填充掩膜mask的非零像素区域。例如,一个边缘检测算子的输出可以用来作为掩膜,以防止填充到边缘。同样的,也可以在多次的函数调用中使用同一个掩膜,以保证填充的区域不会重叠。另外需要注意的是,掩膜mask会比需填充的图像大,所以 mask 中与输入图像(x,y)像素点相对应的点的坐标为(x+1,y+1)。
第三个参数,Point类型的seedPoint,漫水填充算法的起始点。
第四个参数,Scalar类型的newVal,像素点被染色的值,即在重绘区域像素的新值。
第五个参数,Rect*类型的rect,有默认值0,一个可选的参数,用于设置floodFill函数将要重绘区域的最小边界矩形区域。
第六个参数,Scalar类型的loDiff,有默认值Scalar( ),表示当前观察像素值与其部件邻域像素值或者待加入该部件的种子像素之间的亮度或颜色之负差(lower brightness/color difference)的最大值。
第七个参数,Scalar类型的upDiff,有默认值Scalar( ),表示当前观察像素值与其部件邻域像素值或者待加入该部件的种子像素之间的亮度或颜色之正差(lower brightness/color difference)的最大值。简单说第六、第七个参数代表所选定的点的像素值与邻接点的像素值差的范围;
第八个参数,int类型的flags,操作标志符,此参数包含三个部分,比较复杂,我们一起详细看看。
低八位(第0~7位)用于控制算法的连通性,可取4 (4为缺省值) 或者 8。如果设为4,表示填充算法只考虑当前像素水平方向和垂直方向的相邻点;如果设为 8,除上述相邻点外,还会包含对角线方向的相邻点。
高八位部分(16~23位)可以为0 或者如下两种选项标识符的组合:
FLOODFILL_FIXED_RANGE - 如果设置为这个标识符的话,就会考虑当前像素与种子像素之间的差,否则就考虑当前像素与其相邻像素的差。也就是说,这个范围是浮动的。
FLOODFILL_MASK_ONLY - 如果设置为这个标识符的话,函数不会去填充改变原始图像 (也就是忽略第三个参数newVal), 而是去填充掩模图像(mask)。这个标识符只对第二个版本的floodFill有用,因第一个版本里面压根就没有mask参数。
中间八位,上面关于高八位FLOODFILL_MASK_ONLY标识符中已经说的很明显,需要输入符合要求的掩码。Floodfill的flags参数的中间八位的值就是用于指定填充掩码图像的值的。但如果flags中间八位的值为0,则掩码会用1来填充。
而所有flags可以用or操作符连接起来,即“|”。例如,如果想用8邻域填充,并填充固定像素值范围,填充掩码而不是填充源图像,以及设填充值为38,那么输入的参数是这样:
flags=8 | FLOODFILL_MASK_ONLY | FLOODFILL_FIXED_RANGE | (38<<8)
Floodfill的简单的调用范例:
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace cv;
int main( )
{
Mat src = imread("1.jpg");
imshow("【原始图】",src);
Rect ccomp;
floodFill(src, Point(50,300), Scalar(155, 255,55), &ccomp, Scalar(20, 20, 20),Scalar(20, 20, 20));
imshow("【效果图】",src);
waitKey(0);
return 0;
}
补充:SetMouseCallback函数
C++: void setMouseCallback(conststring& winname, MouseCallback onMouse, void* userdata=0 )
第一个参数,const string&类型的winname,为窗口的名字。
第二个参数,MouseCallback类型的onMouse,指定窗口里每次鼠标时间发生的时候,被调用的函数指针。这个函数的原型应该为voidFoo(int event, int x, int y, int flags, void* param);其中event是 CV_EVENT_*变量之一, x和y是鼠标指针在图像坐标系的坐标(不是窗口坐标系), flags是CV_EVENT_FLAG的组合, param是用户定义的传递到cvSetMouseCallback函数调用的参数。
第三个参数,void*类型的userdata,用户定义的传递到回调函数的参数,有默认值0。
下面是一段综合的代码案例:
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
//-----------------------------------【命名空间声明部分】---------------------------------------
// 描述:包含程序所使用的命名空间
//-----------------------------------------------------------------------------------------------
using namespace cv;
using namespace std;
//-----------------------------------【全局变量声明部分】--------------------------------------
// 描述:全局变量声明
//-----------------------------------------------------------------------------------------------
Mat g_srcImage, g_dstImage, g_grayImage, g_maskImage;//定义原始图、目标图、灰度图、掩模图
int g_nFillMode = 1;//漫水填充的模式
int g_nLowDifference = 20, g_nUpDifference = 20;//负差最大值、正差最大值
int g_nConnectivity = 4;//表示floodFill函数标识符低八位的连通值
int g_bIsColor = true;//是否为彩色图的标识符布尔值
bool g_bUseMask = false;//是否显示掩膜窗口的布尔值
int g_nNewMaskVal = 255;//新的重新绘制的像素值
//-----------------------------------【ShowHelpText( )函数】----------------------------------
// 描述:输出一些帮助信息
//----------------------------------------------------------------------------------------------
static void ShowHelpText()
{
//输出一些帮助信息
printf("\n\n\n\t欢迎来到漫水填充示例程序~\n\n");
printf( "\n\n\t按键操作说明: \n\n"
"\t\t鼠标点击图中区域- 进行漫水填充操作\n"
"\t\t键盘按键【ESC】- 退出程序\n"
"\t\t键盘按键【1】- 切换彩色图/灰度图模式\n"
"\t\t键盘按键【2】- 显示/隐藏掩膜窗口\n"
"\t\t键盘按键【3】- 恢复原始图像\n"
"\t\t键盘按键【4】- 使用空范围的漫水填充\n"
"\t\t键盘按键【5】- 使用渐变、固定范围的漫水填充\n"
"\t\t键盘按键【6】- 使用渐变、浮动范围的漫水填充\n"
"\t\t键盘按键【7】- 操作标志符的低八位使用4位的连接模式\n"
"\t\t键盘按键【8】- 操作标志符的低八位使用8位的连接模式\n"
"\n\n\t\t\t\t\t\t\t\t by浅墨\n\n\n"
);
}
//-----------------------------------【onMouse( )函数】--------------------------------------
// 描述:鼠标消息onMouse回调函数
//---------------------------------------------------------------------------------------------
static void onMouse( int event, int x, int y, int, void* )
{
// 若鼠标左键没有按下,便返回
if( event != CV_EVENT_LBUTTONDOWN )
return;
//-------------------【<1>调用floodFill函数之前的参数准备部分】---------------
Point seed = Point(x,y);
int LowDifference = g_nFillMode == 0 ? 0 : g_nLowDifference;//空范围的漫水填充,此值设为0,否则设为全局的g_nLowDifference
int UpDifference = g_nFillMode == 0 ? 0 : g_nUpDifference;//空范围的漫水填充,此值设为0,否则设为全局的g_nUpDifference
int flags = g_nConnectivity + (g_nNewMaskVal << 8) +
(g_nFillMode == 1 ? CV_FLOODFILL_FIXED_RANGE : 0);//标识符的0~7位为g_nConnectivity,8~15位为g_nNewMaskVal左移8位的值,16~23位为CV_FLOODFILL_FIXED_RANGE或者0。
//随机生成bgr值
int b = (unsigned)theRNG() & 255;//随机返回一个0~255之间的值
int g = (unsigned)theRNG() & 255;//随机返回一个0~255之间的值
int r = (unsigned)theRNG() & 255;//随机返回一个0~255之间的值
Rect ccomp;//定义重绘区域的最小边界矩形区域
Scalar newVal = g_bIsColor ? Scalar(b, g, r) : Scalar(r*0.299 + g*0.587 + b*0.114);//在重绘区域像素的新值,若是彩色图模式,取Scalar(b, g, r);若是灰度图模式,取Scalar(r*0.299 + g*0.587 + b*0.114)
Mat dst = g_bIsColor ? g_dstImage : g_grayImage;//目标图的赋值
int area;
//--------------------【<2>正式调用floodFill函数】-----------------------------
if( g_bUseMask )
{
threshold(g_maskImage, g_maskImage, 1, 128, CV_THRESH_BINARY);
area = floodFill(dst, g_maskImage, seed, newVal, &ccomp, Scalar(LowDifference, LowDifference, LowDifference),
Scalar(UpDifference, UpDifference, UpDifference), flags);
imshow( "mask", g_maskImage );
}
else
{
area = floodFill(dst, seed, newVal, &ccomp, Scalar(LowDifference, LowDifference, LowDifference),
Scalar(UpDifference, UpDifference, UpDifference), flags);
}
imshow("效果图", dst);
cout << area << " 个像素被重绘\n";
}
//-----------------------------------【main( )函数】--------------------------------------------
// 描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main( int argc, char** argv )
{
//改变console字体颜色
system("color 2F");
//载入原图
g_srcImage = imread("1.jpg", 1);
if( !g_srcImage.data ) { printf("Oh,no,读取图片image0错误~! \n"); return false; }
//显示帮助文字
ShowHelpText();
g_srcImage.copyTo(g_dstImage);//拷贝源图到目标图
cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);//转换三通道的image0到灰度图
g_maskImage.create(g_srcImage.rows+2, g_srcImage.cols+2, CV_8UC1);//利用image0的尺寸来初始化掩膜mask
namedWindow( "效果图",CV_WINDOW_AUTOSIZE );
//创建Trackbar
createTrackbar( "负差最大值", "效果图", &g_nLowDifference, 255, 0 );
createTrackbar( "正差最大值" ,"效果图", &g_nUpDifference, 255, 0 );
//鼠标回调函数
setMouseCallback( "效果图", onMouse, 0 );
//循环轮询按键
while(1)
{
//先显示效果图
imshow("效果图", g_bIsColor ? g_dstImage : g_grayImage);
//获取键盘按键
int c = waitKey(0);
//判断ESC是否按下,若按下便退出
if( (c & 255) == 27 )
{
cout << "程序退出...........\n";
break;
}
//根据按键的不同,进行各种操作
switch( (char)c )
{
//如果键盘“1”被按下,效果图在在灰度图,彩色图之间互换
case '1':
if( g_bIsColor )//若原来为彩色,转为灰度图,并且将掩膜mask所有元素设置为0
{
cout << "键盘“1”被按下,切换彩色/灰度模式,当前操作为将【彩色模式】切换为【灰度模式】\n";
cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);
g_maskImage = Scalar::all(0); //将mask所有元素设置为0
g_bIsColor = false; //将标识符置为false,表示当前图像不为彩色,而是灰度
}
else//若原来为灰度图,便将原来的彩图image0再次拷贝给image,并且将掩膜mask所有元素设置为0
{
cout << "键盘“1”被按下,切换彩色/灰度模式,当前操作为将【彩色模式】切换为【灰度模式】\n";
g_srcImage.copyTo(g_dstImage);
g_maskImage = Scalar::all(0);
g_bIsColor = true;//将标识符置为true,表示当前图像模式为彩色
}
break;
//如果键盘按键“2”被按下,显示/隐藏掩膜窗口
case '2':
if( g_bUseMask )
{
destroyWindow( "mask" );
g_bUseMask = false;
}
else
{
namedWindow( "mask", 0 );
g_maskImage = Scalar::all(0);
imshow("mask", g_maskImage);
g_bUseMask = true;
}
break;
//如果键盘按键“3”被按下,恢复原始图像
case '3':
cout << "按键“3”被按下,恢复原始图像\n";
g_srcImage.copyTo(g_dstImage);
cvtColor(g_dstImage, g_grayImage, COLOR_BGR2GRAY);
g_maskImage = Scalar::all(0);
break;
//如果键盘按键“4”被按下,使用空范围的漫水填充
case '4':
cout << "按键“4”被按下,使用空范围的漫水填充\n";
g_nFillMode = 0;
break;
//如果键盘按键“5”被按下,使用渐变、固定范围的漫水填充
case '5':
cout << "按键“5”被按下,使用渐变、固定范围的漫水填充\n";
g_nFillMode = 1;
break;
//如果键盘按键“6”被按下,使用渐变、浮动范围的漫水填充
case '6':
cout << "按键“6”被按下,使用渐变、浮动范围的漫水填充\n";
g_nFillMode = 2;
break;
//如果键盘按键“7”被按下,操作标志符的低八位使用4位的连接模式
case '7':
cout << "按键“7”被按下,操作标志符的低八位使用4位的连接模式\n";
g_nConnectivity = 4;
break;
//如果键盘按键“8”被按下,操作标志符的低八位使用8位的连接模式
case '8':
cout << "按键“8”被按下,操作标志符的低八位使用8位的连接模式\n";
g_nConnectivity = 8;
break;
}
}
return 0;
}
2、图像金字塔与图像尺寸的缩放
OpenCV为我们提供的如下两种放大或者缩小图片的尺寸的方式:
<1>resize函数。这是最直接的方式,
<2>pyrUp( )、pyrDown( )函数。即图像金字塔相关的两个函数,对图像进行向上采样,向下采样的操作。
pyrUp、pyrDown其实和专门用作放大缩小图像尺寸的resize在功能上差不多,对图像进行放大和缩小操作。pyrUp、pyrDown在OpenCV的imgproc模块中的Image Filtering子模块里。而resize在imgproc 模块的Geometric Image Transformations子模块里。
像金字塔是图像中多尺度表达的一种,最主要用于图像的分割,是一种以多分辨率来解释图像的有效但概念简单的结构。
一幅图像的金字塔是一系列以金字塔形状排列的分辨率逐步降低,且来源于同一张原始图的图像集合。
金字塔的底部是待处理图像的高分辨率表示,而顶部是低分辨率的近似。
我们将一层一层的图像比喻成金字塔,层级越高,则图像越小,分辨率越低。
一般情况下有两种类型的图像金字塔常常出现在文献和以及实际运用中。他们分别是:
高斯金字塔(Gaussianpyramid): 用来向下采样,主要的图像金字塔
拉普拉斯金字塔(Laplacianpyramid): 用来从金字塔低层图像重建上层未采样图像,在数字图像处理中也即是预测残差,可以对图像进行最大程度的还原,配合高斯金字塔一起使用。
两者的简要区别:高斯金字塔用来向下降采样图像,而拉普拉斯金字塔则用来从金字塔底层图像中向上采样重建一个图像。
要从金字塔第i层生成第i+1层(我们表示第i+1层为G_i+1),我们先要用高斯核对G_1进行卷积,然后删除所有偶数行和偶数列。当然的是,新得到图像面积会变为源图像的四分之一。按上述过程对输入图像G_0执行操作就可产生出整个金字塔。
对图像向上采样:pyrUp函数
对图像向下采样:pyrDown函数(和金字塔方向相反)
这里的向下与向上采样,是对图像的尺寸而言的(和金字塔的方向相反),向上就是图像尺寸加倍,向下就是图像尺寸减半
对图像的向下取样:
为了获取层级为 G_i+1 的金字塔图像,我们采用如下方法:
<1>对图像G_i进行高斯内核卷积
<2>将所有偶数行和列去除
对图像的向上取样
想放大图像,则需要通过向上取样操作得到,具体做法如下:
<1>将图像在每个方向扩大为原来的两倍,新增的行和列以0填充
<2>使用先前同样的内核(乘以4)与放大后的图像卷积,获得 “新增像素”的近似值
resize( )函数
resize( )为OpenCV中专门调整图像大小的函数。
函数原型:
void resize(InputArray src,OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR )
第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。
第二个参数,OutputArray类型的dst,输出图像,当其非零时,有着dsize(第三个参数)的尺寸,或者由src.size()计算出来。
第三个参数,Size类型的dsize,输出图像的大小;如果它等于零,由下式进行计算:
其中,dsize,fx,fy都不能为0。
第四个参数,double类型的fx,沿水平轴的缩放系数,有默认值0,且当其等于0时,由下式进行计算:
第五个参数,double类型的fy,沿垂直轴的缩放系数,有默认值0,且当其等于0时,由下式进行计算:
第六个参数,int类型的interpolation,用于指定插值方式,默认为INTER_LINEAR(线性插值)。
可选的插值方式如下:
INTER_NEAREST - 最近邻插值
INTER_LINEAR - 线性插值(默认值)
INTER_AREA - 区域插值(利用像素区域关系的重采样插值)
INTER_CUBIC –三次样条插值(超过4×4像素邻域内的双三次插值)
INTER_LANCZOS4 -Lanczos插值(超过8×8像素邻域的Lanczos插值)
若要缩小图像,一般情况下最好用CV_INTER_AREA来插值,
而若要放大图像,一般情况下最好用CV_INTER_CUBIC(效率不高,慢,不推荐使用)或CV_INTER_LINEAR(效率较高,速度较快,推荐使用)。
接着我们来看两种resize的调用范例。
方式一,调用范例:
Mat dst=Mat::zeros(512 ,512, CV_8UC3 );//新建一张512x512尺寸的图片
Mat src=imread(“1.jpg”);
//显式指定dsize=dst.size(),那么fx和fy会其计算出来,不用额外指定。
resize(src, dst, dst.size());
方式二、调用范例:
Mat dst; Mat src=imread(“1.jpg”) //指定fx和fy,让函数计算出目标图像的大小。 resize(src, dst, Size(), 0.5, 0.5);
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>
//-----------------------------------【命名空间声明部分】---------------------------------------
// 描述:包含程序所使用的命名空间
//-----------------------------------------------------------------------------------------------
using namespace cv;
//-----------------------------------【main( )函数】--------------------------------------------
// 描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main( )
{
//载入原始图
Mat srcImage = imread("1.jpg"); //工程目录下应该有一张名为1.jpg的素材图
Mat tmpImage,dstImage1,dstImage2;//临时变量和目标图的定义
tmpImage=srcImage;//将原始图赋给临时变量
//显示原始图
imshow("【原始图】", srcImage);
//进行尺寸调整操作
resize(tmpImage,dstImage1,Size( tmpImage.cols/2, tmpImage.rows/2 ),(0,0),(0,0),3);
resize(tmpImage,dstImage2,Size( tmpImage.cols*2, tmpImage.rows*2 ),(0,0),(0,0),3);
//显示效果图
imshow("【效果图】之一", dstImage1);
imshow("【效果图】之二", dstImage2);
waitKey(0);
return 0;
}
pyrUp()函数剖析
pyrUp( )函数的作用是向上采样并模糊一张图像,说白了就是放大一张图片。
void pyrUp(InputArray src, OutputArraydst, const Size& dstsize=Size(), int borderType=BORDER_DEFAULT )
第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。
第二个参数,OutputArray类型的dst,输出图像,和源图片有一样的尺寸和类型。
第三个参数,const Size&类型的dstsize,输出图像的大小;有默认值Size(),即默认情况下,由Size(src.cols*2,src.rows*2)来进行计算,且一直需要满足下列条件:
第四个参数,int类型的borderType,又来了,边界模式,一般我们不用去管它。
pyrUp函数执行高斯金字塔的采样操作,其实它也可以用于拉普拉斯金字塔的。
首先,它通过插入可为零的行与列,对源图像进行向上取样操作,然后将结果与pyrDown()乘以4的内核做卷积,就是这样。
直接看完整的示例程序:
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>
//-----------------------------------【命名空间声明部分】---------------------------------------
// 描述:包含程序所使用的命名空间
//-----------------------------------------------------------------------------------------------
using namespace cv;
//-----------------------------------【main( )函数】--------------------------------------------
// 描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main( )
{
//载入原始图
Mat srcImage = imread("1.jpg"); //工程目录下应该有一张名为1.jpg的素材图
Mat tmpImage,dstImage;//临时变量和目标图的定义
tmpImage=srcImage;//将原始图赋给临时变量
//显示原始图
imshow("【原始图】", srcImage);
//进行向上取样操作
pyrUp( tmpImage, dstImage, Size( tmpImage.cols*2, tmpImage.rows*2 ) );
//显示效果图
imshow("【效果图】", dstImage);
waitKey(0);
return 0;
}
pyrDown()函数剖析
pyrDown( )函数的作用是向下采样并模糊一张图片,说白了就是缩小一张图片。
void pyrDown(InputArray src,OutputArray dst, const Size& dstsize=Size(), int borderType=BORDER_DEFAULT)
第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。
第二个参数,OutputArray类型的dst,输出图像,和源图片有一样的尺寸和类型。
第三个参数,const Size&类型的dstsize,输出图像的大小;有默认值Size(),即默认情况下,由Size Size((src.cols+1)/2, (src.rows+1)/2)来进行计算,且一直需要满足下列条件:
该pyrDown函数执行了高斯金字塔建造的向下采样的步骤。首先,它将源图像与如下内核做卷积运算:
接着,它便通过对图像的偶数行和列做插值来进行向下采样操作。
依然是看看完整的示例程序:
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>
//-----------------------------------【命名空间声明部分】---------------------------------------
// 描述:包含程序所使用的命名空间
//-----------------------------------------------------------------------------------------------
using namespace cv;
//-----------------------------------【main( )函数】--------------------------------------------
// 描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main( )
{
//载入原始图
Mat srcImage = imread("1.jpg"); //工程目录下应该有一张名为1.jpg的素材图
Mat tmpImage,dstImage;//临时变量和目标图的定义
tmpImage=srcImage;//将原始图赋给临时变量
//显示原始图
imshow("【原始图】", srcImage);
//进行向下取样操作
pyrDown( tmpImage, dstImage, Size( tmpImage.cols/2, tmpImage.rows/2 ) );
//显示效果图
imshow("【效果图】", dstImage);
waitKey(0);
return 0;
}
综合示例
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
//-----------------------------------【宏定义部分】--------------------------------------------
// 描述:定义一些辅助宏
//------------------------------------------------------------------------------------------------
#define WINDOW_NAME "【程序窗口】" //为窗口标题定义的宏
//-----------------------------------【命名空间声明部分】--------------------------------------
// 描述:包含程序所使用的命名空间
//-----------------------------------------------------------------------------------------------
using namespace std;
using namespace cv;
//-----------------------------------【全局变量声明部分】--------------------------------------
// 描述:全局变量声明
//-----------------------------------------------------------------------------------------------
Mat g_srcImage, g_dstImage, g_tmpImage;
//-----------------------------------【全局函数声明部分】--------------------------------------
// 描述:全局函数声明
//-----------------------------------------------------------------------------------------------
static void ShowHelpText();
//-----------------------------------【ShowHelpText( )函数】----------------------------------
// 描述:输出一些帮助信息
//----------------------------------------------------------------------------------------------
static void ShowHelpText()
{
//输出一些帮助信息
printf("\n\n\n\t欢迎来到OpenCV图像金字塔和resize示例程序~\n\n");
printf( "\n\n\t按键操作说明: \n\n"
"\t\t键盘按键【ESC】或者【Q】- 退出程序\n"
"\t\t键盘按键【1】或者【W】- 进行基于【resize】函数的图片放大\n"
"\t\t键盘按键【2】或者【S】- 进行基于【resize】函数的图片缩小\n"
"\t\t键盘按键【3】或者【A】- 进行基于【pyrUp】函数的图片放大\n"
"\t\t键盘按键【4】或者【D】- 进行基于【pyrDown】函数的图片缩小\n"
"\n\n\t\t\t\t\t\t\t\t by浅墨\n\n\n"
);
}
//-----------------------------------【main( )函数】--------------------------------------------
// 描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main( )
{
//改变console字体颜色
system("color 2F");
//显示帮助文字
ShowHelpText();
//载入原图
g_srcImage = imread("1.jpg");//工程目录下需要有一张名为1.jpg的测试图像,且其尺寸需被2的N次方整除,N为可以缩放的次数
if( !g_srcImage.data ) { printf("Oh,no,读取srcImage错误~! \n"); return false; }
// 创建显示窗口
namedWindow( WINDOW_NAME, CV_WINDOW_AUTOSIZE );
imshow(WINDOW_NAME, g_srcImage);
//参数赋值
g_tmpImage = g_srcImage;
g_dstImage = g_tmpImage;
int key =0;
//轮询获取按键信息
while(1)
{
key=waitKey(9) ;//读取键值到key变量中
//根据key变量的值,进行不同的操作
switch(key)
{
//======================【程序退出相关键值处理】=======================
case 27://按键ESC
return 0;
break;
case 'q'://按键Q
return 0;
break;
//======================【图片放大相关键值处理】=======================
case 'a'://按键A按下,调用pyrUp函数
pyrUp( g_tmpImage, g_dstImage, Size( g_tmpImage.cols*2, g_tmpImage.rows*2 ) );
printf( ">检测到按键【A】被按下,开始进行基于【pyrUp】函数的图片放大:图片尺寸×2 \n" );
break;
case 'w'://按键W按下,调用resize函数
resize(g_tmpImage,g_dstImage,Size( g_tmpImage.cols*2, g_tmpImage.rows*2 ));
printf( ">检测到按键【W】被按下,开始进行基于【resize】函数的图片放大:图片尺寸×2 \n" );
break;
case '1'://按键1按下,调用resize函数
resize(g_tmpImage,g_dstImage,Size( g_tmpImage.cols*2, g_tmpImage.rows*2 ));
printf( ">检测到按键【1】被按下,开始进行基于【resize】函数的图片放大:图片尺寸×2 \n" );
break;
case '3': //按键3按下,调用pyrUp函数
pyrUp( g_tmpImage, g_dstImage, Size( g_tmpImage.cols*2, g_tmpImage.rows*2 ));
printf( ">检测到按键【3】被按下,开始进行基于【pyrUp】函数的图片放大:图片尺寸×2 \n" );
break;
//======================【图片缩小相关键值处理】=======================
case 'd': //按键D按下,调用pyrDown函数
pyrDown( g_tmpImage, g_dstImage, Size( g_tmpImage.cols/2, g_tmpImage.rows/2 ));
printf( ">检测到按键【D】被按下,开始进行基于【pyrDown】函数的图片缩小:图片尺寸/2\n" );
break;
case 's' : //按键S按下,调用resize函数
resize(g_tmpImage,g_dstImage,Size( g_tmpImage.cols/2, g_tmpImage.rows/2 ));
printf( ">检测到按键【S】被按下,开始进行基于【resize】函数的图片缩小:图片尺寸/2\n" );
break;
case '2'://按键2按下,调用resize函数
resize(g_tmpImage,g_dstImage,Size( g_tmpImage.cols/2, g_tmpImage.rows/2 ),(0,0),(0,0),2);
printf( ">检测到按键【2】被按下,开始进行基于【resize】函数的图片缩小:图片尺寸/2\n" );
break;
case '4': //按键4按下,调用pyrDown函数
pyrDown( g_tmpImage, g_dstImage, Size( g_tmpImage.cols/2, g_tmpImage.rows/2 ) );
printf( ">检测到按键【4】被按下,开始进行基于【pyrDown】函数的图片缩小:图片尺寸/2\n" );
break;
}
//经过操作后,显示变化后的图
imshow( WINDOW_NAME, g_dstImage );
//将g_dstImage赋给g_tmpImage,方便下一次循环
g_tmpImage = g_dstImage;
}
return 0;
}
3、阈值化
阈值被视作最简单的图像分割方法。主要是基于图像中物体和场景的灰度差异;
注意:阈值的选取依赖于具体的问题。即物体在不同的图像中有可能会有不同的灰度值;
opencv中:Threshold( )函数和adaptiveThreshold函数能完成这样的要求。
基本思想:给定一个数组和阈值,然后根据数组中的每个元素的值是高于还是低于阈值而进行一些处理。
3.1 给定阈值操作:Threshold( )函数
原型:
double threshold(InputArray src,OutputArray dst,double thresh,double maxval,int type)
参数说明:double类型的thresh,是阈值的具体值;double类型的maxval,当第五个参数阈值类型type取CV_THRESH_BINARY或者CV_THRESH_BINARY_INV时阈值类型的最大值;int类型的type,阈值类型。具体的方法:
1)CV_THRESH_BINARY:
如果 src(x,y)>threshold ,dst(x,y) = max_value; 否则,dst(x,y)=0;
2)CV_THRESH_BINARY_INV:
如果 src(x,y)>threshold,dst(x,y) = 0; 否则,dst(x,y) = max_value.
3)CV_THRESH_TRUNC:
如果 src(x,y)>threshold,dst(x,y) = max_value; 否则dst(x,y) = src(x,y).
4)CV_THRESH_TOZERO:
如果src(x,y)>threshold,dst(x,y) = src(x,y) ; 否则 dst(x,y) = 0。
5)CV_THRESH_TOZERO_INV:
如果 src(x,y)>threshold,dst(x,y) = 0 ; 否则dst(x,y) = src(x,y).
3.2自适应阈值操作:adaptiveThreshold( )函数
函数原型:
void adaptiveThreshold(InputArray src, OutputArray dst, double maxValue, int adaptiveMethod,int thresholdType, int blockSize, double C)
参数说明:
第四个参数,int类型的adaptiveMethod,用于指定要使用的自适应阈值算法,可取值为ADAPTIVE_THRESH_MEAN或者ADAPTIVE_THRESH_GAUSSIAN_C。
第五个参数:int类型的thresholdType,阈值类型。取值必须为THRESH_BINARY、THRESH_BINARY_INV其中之一。
第六个参数:int类型的blockSize,用于计算阈值大小的一个像素的领域尺寸,取值为3、5、7。
第七个参数:double类型的C,减去平均或者加权平均值后的常数值。通常其为正数,少数情况下为零和负数。
综合案例:
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
//-----------------------------------【宏定义部分】--------------------------------------------
// 描述:定义一些辅助宏
//------------------------------------------------------------------------------------------------
#define WINDOW_NAME "【程序窗口】" //为窗口标题定义的宏
//-----------------------------------【全局变量声明部分】--------------------------------------
// 描述:全局变量的声明
//-----------------------------------------------------------------------------------------------
int g_nThresholdValue = 100;
int g_nThresholdType = 3;
Mat g_srcImage, g_grayImage, g_dstImage;
//-----------------------------------【全局函数声明部分】--------------------------------------
// 描述:全局函数的声明
//-----------------------------------------------------------------------------------------------
static void ShowHelpText( );//输出帮助文字
void on_Threshold( int, void* );//回调函数
//-----------------------------------【main( )函数】--------------------------------------------
// 描述:控制台应用程序的入口函数,我们的程序从这里开始执行
//-----------------------------------------------------------------------------------------------
int main( )
{
//【1】读入源图片
g_srcImage = imread("1.jpg");
if(!g_srcImage.data ) { printf("读取图片错误,请确定目录下是否有imread函数指定的图片存在~! \n"); return false; }
imshow("原始图",g_srcImage);
//【2】存留一份原图的灰度图
cvtColor( g_srcImage, g_grayImage, COLOR_RGB2GRAY );
//【3】创建窗口并显示原始图
namedWindow( WINDOW_NAME, WINDOW_AUTOSIZE );
//【4】创建滑动条来控制阈值
createTrackbar( "模式",
WINDOW_NAME, &g_nThresholdType,
4, on_Threshold );
createTrackbar( "参数值",
WINDOW_NAME, &g_nThresholdValue,
255, on_Threshold );
//【5】初始化自定义的阈值回调函数
on_Threshold( 0, 0 );
// 【6】轮询等待用户按键,如果ESC键按下则退出程序
while(1)
{
int key;
key = waitKey( 20 );
if( (char)key == 27 ){ break; }
}
}
//-----------------------------------【on_Threshold( )函数】------------------------------------
// 描述:自定义的阈值回调函数
//-----------------------------------------------------------------------------------------------
void on_Threshold( int, void* )
{
//调用阈值函数
threshold(g_grayImage,g_dstImage,g_nThresholdValue,255,g_nThresholdType);
//更新效果图
imshow( WINDOW_NAME, g_dstImage );
}