毛星云(浅墨)-----【OpenCV】入门教程
/*
----------Mat----------
cv::Mat类是用于保存图像以及其他矩阵数据的数据结构。
默认情况下,其尺寸为0,我们也可以指定初始尺寸;
比如,比如定义一个Mat类对象,就要写cv::Mat pic(320, 640, cv::Scalar(100))。
Mat myMat = imread("dota.jpg");
表示从工程目录下把一幅名为dota.jpg的jpg类型的图像载入到Mat类型的myMat中。
*/
/*
----------imread----------
读取一个图像:
Mat imread(const string& filename, int flags = 1 );
第一个参数,const string&类型的filename,填我们需要载入的图片路径名。
第二个参数,int类型的flags,为载入标识,它指定一个加载图像的颜色类型;
可以看到它自带缺省值1,所以有时候这个参数在调用时我们可以忽略;
如果在调用时忽略这个参数,就表示载入三通道的彩色图像。
1)可以在OpenCV标识图像格式的枚举体中取值:
CV_LOAD_IMAGE_UNCHANGED,这个标识在新版本中被废置了,忽略。
CV_LOAD_IMAGE_ANYDEPTH- 如果取这个标识的话,若载入的图像的深度为16位或者32位,就返回对应深度的图像,否则,就转换为8位图像再返回。
CV_LOAD_IMAGE_COLOR- 如果取这个标识的话,总是转换图像到彩色一体
CV_LOAD_IMAGE_GRAYSCALE- 如果取这个标识的话,始终将图像转换成灰度1
如果输入有冲突的标志,将采用较小的数字值。比如CV_LOAD_IMAGE_COLOR | CV_LOAD_IMAGE_ANYCOLOR 将载入3通道图。
如果想要载入最真实的图像,选择CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR。
2)因为flags是int型的变量,如果我们不在这个枚举体中取值的话,还可以这样来:
flags > 0返回一个3通道的彩色图像。
flags = 0返回灰度图像。
flags < 0返回包含Alpha通道的加载的图像。
需要注意的点:输出的图像默认情况下是不载入Alpha通道进来的。
如果我们需要载入Alpha通道的话呢,这里就需要取负值。
示例:
Mat image0=imread("dota.jpg",CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR); //载入最真实的图像
Mat image1=imread("dota.jpg",0); //载入灰度图
Mat image2=imread("dota.jpg",199); //载入3通道的彩色图像
Mat logo=imread("dota_logo.jpg"); //载入3通道的彩色图像
*/
/*
----------namedWindow----------
创建一个窗口:
void namedWindow(const string& winname,int flags = WINDOW_AUTOSIZE );
第一个参数,const string&型的name,即填被用作窗口的标识符的窗口名称。
第二个参数,int 类型的flags ,窗口的标识,可以填如下的值:
WINDOW_NORMAL设置了这个值,用户便可以改变窗口的大小(没有限制)
WINDOW_AUTOSIZE如果设置了这个值,窗口大小会自动调整以适应所显示的图像,并且不能手动改变窗口大小。
WINDOW_OPENGL 如果设置了这个值的话,窗口创建的时候便会支持OpenGL。
函数剖析:
首先需要注意的是,它有默认值WINDOW_AUTOSIZE,所以,一般情况下,这个函数我们填一个变量就行了。
namedWindow函数的作用是,通过指定的名字,创建一个可以作为图像和进度条的容器窗口。
如果具有相同名称的窗口已经存在,则函数不做任何事情。
我们可以调用destroyWindow()或者destroyAllWindows()函数来关闭窗口,并取消之前分配的与窗口相关的所有内存空间。
但其实对于代码量不大的简单小程序来说,我们完全没有必要手动调用上述的destroyWindow()或者destroyAllWindows()函数,因为在退出时,所有的资源和应用程序的窗口会被操作系统会自动关闭。
*/
/*
----------imshow----------
在指定的窗口中显示一幅图像:
void imshow(const string& winname, InputArray mat);
第一个参数,const string&类型的winname,填需要显示的窗口标识名称。
第二个参数,InputArray类型的mat,填需要显示的图像。
*/
/*
----------imwrite----------
输出图像到文件:
bool imwrite(const string& filename,InputArray img, const vector& params=vector() );
第一个参数,const string&类型的filename,填需要写入的文件名就行了,带上后缀,比如“123.jpg”。
第二个参数,InputArray类型的img,一般填一个Mat类型的图像数据就行了。
第三个参数,const vector&类型的params,表示为特定格式保存的参数编码,它有默认值vector(),所以一般情况下不需要填写。
*/
//c3 图像的载入、显示和输出
//头文件
#include
#include
#include
//命名空间
using namespace cv;
using namespace std;
int main()
{
//图像的载入和显示
Mat girl = imread("girl.jpg"); //载入图像到Mat
namedWindow("[1]Girl"); //创建一个窗口
imshow("[1]Girl", girl); //显示这个窗口
//初级图像混合ROI
Mat image = imread("3.jpg");
Mat logo = imread("logo.jpg");
if (!image.data)
{
printf("读取image错误! \n");
return false;
}
if (!logo.data)
{
printf("读取logo错误! \n");
return false;
}
namedWindow("[2]原图");
imshow("[2]原图", image);
namedWindow("[3]logo图");
imshow("[3]logo图", logo);
Mat imageROI; //定义一个Mat类型,用于存放,图像的ROI
//方法一
imageROI = image(Rect(0, 0, logo.cols, logo.rows));
//cv::Rect表示一个矩形区域。
//指定矩形的左上角坐标(构造函数的前两个参数)和矩形的长宽(构造函数的后两个参数)就可以定义一个矩形区域。
//方法二
//imageROI=image(Range(350,350+logo.rows),Range(800,800+logo.cols));
//Range是指从起始索引到终止索引(不包括终止索引)的一连段连续序列。
//将logo加到原图上
addWeighted(imageROI, 0.5, logo, 0.3, 0., imageROI);
//第一个参数,InputArray类型的src1,表示需要加权的第一个数组,常常填一个Mat。
//第二个参数,alpha,表示第一个数组的权重
//第三个参数,src2,表示第二个数组,它需要和第一个数组拥有相同的尺寸和通道数。
//第四个参数,beta,表示第二个数组的权重值。
//第五个参数,dst,输出的数组,它和输入的两个数组拥有相同的尺寸和通道数。
//第六个参数,gamma,一个加到权重总和上的标量值。看下面的式子自然会理解。
//第七个参数,dtype,输出阵列的可选深度,有默认值 - 1。
//当两个输入数组具有相同的深度时,这个参数设置为 - 1(默认值),即等同于src1.depth()。
//显示结果
namedWindow("[4]混合图");
imshow("[4]混合图", image);
//图像的输出
imwrite("write.jpg", image);
waitKey();
return 0;
}
//c4 ROI区域图像叠加&初级图像混合 全剖析
//-----------------------------------【程序说明】----------------------------------------------
// 程序名称::【OpenCV入门教程之四】 ROI区域图像叠加&初级图像混合 全剖析 配套源码
// VS2010版 OpenCV版本:2.4.8
// 2014年3月10日 Create by 浅墨
// 图片素材出处:dota2原画 dota2logo
// 浅墨的微博:@浅墨_毛星云
//------------------------------------------------------------------------------------------------
//-----------------------------------【头文件包含部分】---------------------------------------
// 描述:包含程序所依赖的头文件
//----------------------------------------------------------------------------------------------
//#include "stdafx.h"
#include
#include
#include
#include
#include
//-----------------------------------【命名空间声明部分】---------------------------------------
// 描述:包含程序所使用的命名空间
//-----------------------------------------------------------------------------------------------
using namespace cv;
using namespace std;
//-----------------------------------【全局函数声明部分】--------------------------------------
// 描述:全局函数声明
//-----------------------------------------------------------------------------------------------
bool ROI_AddImage();
bool LinearBlending();
bool ROI_LinearBlending();
//-----------------------------------【main( )函数】--------------------------------------------
// 描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main()
{
system("color 5E");
if (ROI_AddImage() && LinearBlending() && ROI_LinearBlending())
{
cout << endl << "嗯。好了,得出了你需要的图像~! : )";
}
waitKey();
return 0;
}
//----------------------------------【ROI_AddImage( )函数】----------------------------------
// 函数名:ROI_AddImage()
// 描述:利用感兴趣区域ROI实现图像叠加
//----------------------------------------------------------------------------------------------
bool ROI_AddImage()
{
//【1】读入图像
Mat srcImage1 = imread("3.jpg");
Mat logoImage = imread("logo.jpg");
if (!srcImage1.data) { printf("读取srcImage1错误~! \n"); return false; }
if (!logoImage.data) { printf("读取logoImage错误~! \n"); return false; }
//【2】定义一个Mat类型并给其设定ROI区域
Mat imageROI = srcImage1(Rect(0, 0, logoImage.cols, logoImage.rows));
//【3】加载掩模(必须是灰度图)
Mat mask = imread("logo.jpg", 0);
//【4】将掩膜拷贝到ROI
logoImage.copyTo(imageROI, mask);
//【5】显示结果
namedWindow("<1>利用ROI实现图像叠加示例窗口");
imshow("<1>利用ROI实现图像叠加示例窗口", srcImage1);
return true;
}
//---------------------------------【LinearBlending()函数】-------------------------------------
// 函数名:LinearBlending()
// 描述:利用cv::addWeighted()函数实现图像线性混合
//--------------------------------------------------------------------------------------------
bool LinearBlending()
{
//【0】定义一些局部变量
double alphaValue = 0.5;
double betaValue;
Mat srcImage2, srcImage3, dstImage;
//【1】读取图像 ( 两幅图片需为同样的类型和尺寸 )
srcImage2 = imread("11.jpg");
srcImage3 = imread("22.jpg");
if (!srcImage2.data) { printf("读取srcImage2错误~! \n"); return false; }
if (!srcImage3.data) { printf("读取srcImage3错误~! \n"); return false; }
//【2】进行图像混合加权操作
betaValue = (1.0 - alphaValue);
addWeighted(srcImage2, alphaValue, srcImage3, betaValue, 0.0, dstImage);
//【3】创建并显示原图窗口
namedWindow("<2>线性混合示例窗口【原图】 by浅墨", 1);
imshow("<2>线性混合示例窗口【原图】 by浅墨", srcImage2);
namedWindow("<3>线性混合示例窗口【效果图】 by浅墨", 1);
imshow("<3>线性混合示例窗口【效果图】 by浅墨", dstImage);
return true;
}
//---------------------------------【ROI_LinearBlending()】-------------------------------------
// 函数名:ROI_LinearBlending()
// 描述:线性混合实现函数,指定区域线性图像混合.利用cv::addWeighted()函数结合定义
// 感兴趣区域ROI,实现自定义区域的线性混合
//--------------------------------------------------------------------------------------------
bool ROI_LinearBlending()
{
//【1】读取图像
Mat srcImage4 = imread("1.jpg", 1);
Mat logoImage = imread("logo.jpg");
if (!srcImage4.data) { printf("读取srcImage4错误~! \n"); return false; }
if (!logoImage.data) { printf("读取logoImage错误~! \n"); return false; }
//【2】定义一个Mat类型并给其设定ROI区域
Mat imageROI;
//方法一
imageROI = srcImage4(Rect(0, 0, logoImage.cols, logoImage.rows));
//方法二
//imageROI=srcImage4(Range(250,250+logoImage.rows),Range(200,200+logoImage.cols));
//【3】将logo加到原图上
addWeighted(imageROI, 0.5, logoImage, 0.3, 0., imageROI);
//【4】显示结果
namedWindow("<4>区域线性图像混合示例窗口 by浅墨");
imshow("<4>区域线性图像混合示例窗口 by浅墨", srcImage4);
return true;
}
//c5 分离颜色通道&多通道图像混合
#include
#include
#include
#include
#include
using namespace cv;
using namespace std;
//而为了更好的观察一些图像材料的特征,有时需要对RGB三个颜色通道的分量进行分别显示和调整。
//通过OpenCV的split和merge方法可以很方便的达到目的。
/*
----------split----------
将一个多通道数组分离成几个单通道数组:
void split(const Mat& src, Mat*mvbegin);
void split(InputArray m,OutputArrayOfArrays mv);
第一个参数,InputArray类型的m或者const Mat&类型的src,填我们需要进行分离的多通道数组。
第二个参数,OutputArrayOfArrays类型的mv,填函数的输出数组或者输出的vector容器。
*/
/*
----------merge----------
将多个数组组合合并成一个多通道的数组:
void merge(const Mat* mv, size_tcount, OutputArray dst)
void merge(InputArrayOfArrays mv,OutputArray dst)
第一个参数,mv,填需要被合并的输入矩阵或vector容器的阵列,这个mv参数中所有的矩阵必须有着一样的尺寸和深度。
第二个参数,count,当mv为一个空白的C数组时,代表输入矩阵的个数,这个参数显然必须大于1.
第三个参数,dst,即输出矩阵,和mv[0]拥有一样的尺寸和深度,并且通道的数量是矩阵阵列中的通道的总数。
*/
bool MultiChannelBlending();
int main()
{
system("color 5E");
if (MultiChannelBlending())
{
cout << endl << "得到混合值图像";
}
waitKey(0);
return 0;
}
//-----------------------------【MultiChannelBlending( )函数】--------------------------------
// 多通道混合的实现函数
//-----------------------------------------------------------------------------------------------
bool MultiChannelBlending()
{
//【0】定义相关变量
Mat srcImage;
Mat logoImage;
vector<Mat>channels;
Mat imageBlueChannel;
//=================【蓝色通道部分】=================
// 描述:多通道混合-蓝色分量部分
//============================================
//【1】读入图片
logoImage = imread("flower.jpg", 0);
srcImage = imread("7.jpg");
if (!logoImage.data) { printf("Oh,no,读取logoImage错误~!\n"); return false; }
if (!srcImage.data) { printf("Oh,no,读取srcImage错误~!\n"); return false; }
//【2】把一个3通道图像转换成3个单通道图像
split(srcImage, channels);//分离色彩通道
//【3】将原图的蓝色通道引用返回给imageBlueChannel,注意是引用,相当于两者等价,修改其中一个另一个跟着变
imageBlueChannel = channels.at(0);
//【4】将原图的蓝色通道的(0,0)坐标处右下方的一块区域和logo图进行加权操作,将得到的混合结果存到imageBlueChannel中
addWeighted(imageBlueChannel(Rect(0, 0, logoImage.cols, logoImage.rows)), 1.0,
logoImage, 0.5, 0, imageBlueChannel(Rect(0, 0, logoImage.cols, logoImage.rows)));
//【5】将三个单通道重新合并成一个三通道
merge(channels, srcImage);
//【6】显示效果图
namedWindow("<1>游戏原画+logo蓝色通道 by浅墨");
imshow("<1>游戏原画+logo蓝色通道 by浅墨", srcImage);
//=================【绿色通道部分】=================
// 描述:多通道混合-绿色分量部分
//============================================
//【0】定义相关变量
Mat imageGreenChannel;
//【1】重新读入图片
logoImage = imread("flower.jpg", 0);
srcImage = imread("7.jpg");
if (!logoImage.data) { printf("Oh,no,读取logoImage错误~!\n"); return false; }
if (!srcImage.data) { printf("Oh,no,读取srcImage错误~!\n"); return false; }
//【2】将一个三通道图像转换成三个单通道图像
split(srcImage, channels);//分离色彩通道
//【3】将原图的绿色通道的引用返回给imageBlueChannel,注意是引用,相当于两者等价,修改其中一个另一个跟着变
imageGreenChannel = channels.at(1);
//【4】将原图的绿色通道的(500,250)坐标处右下方的一块区域和logo图进行加权操作,将得到的混合结果存到imageGreenChannel中
addWeighted(imageGreenChannel(Rect(0, 0, logoImage.cols, logoImage.rows)), 1.0,
logoImage, 0.5, 0., imageGreenChannel(Rect(0, 0, logoImage.cols, logoImage.rows)));
//【5】将三个独立的单通道重新合并成一个三通道
merge(channels, srcImage);
//【6】显示效果图
namedWindow("<2>游戏原画+logo绿色通道 by浅墨");
imshow("<2>游戏原画+logo绿色通道 by浅墨", srcImage);
//=================【红色通道部分】=================
// 描述:多通道混合-红色分量部分
//============================================
//【0】定义相关变量
Mat imageRedChannel;
//【1】重新读入图片
logoImage = imread("flower.jpg", 0);
srcImage = imread("7.jpg");
if (!logoImage.data) { printf("Oh,no,读取logoImage错误~!\n"); return false; }
if (!srcImage.data) { printf("Oh,no,读取srcImage错误~!\n"); return false; }
//【2】将一个三通道图像转换成三个单通道图像
split(srcImage, channels);//分离色彩通道
//【3】将原图的红色通道引用返回给imageBlueChannel,注意是引用,相当于两者等价,修改其中一个另一个跟着变
imageRedChannel = channels.at(2);
//【4】将原图的红色通道的(500,250)坐标处右下方的一块区域和logo图进行加权操作,将得到的混合结果存到imageRedChannel中
addWeighted(imageRedChannel(Rect(0, 0, logoImage.cols, logoImage.rows)), 1.0,
logoImage, 0.5, 0., imageRedChannel(Rect(0, 0, logoImage.cols, logoImage.rows)));
//【5】将三个独立的单通道重新合并成一个三通道
merge(channels, srcImage);
//【6】显示效果图
namedWindow("<3>游戏原画+logo红色通道 by浅墨");
imshow("<3>游戏原画+logo红色通道 by浅墨", srcImage);
return true;
}
//c6 创建Trackbar & 图像对比度、亮度值调整
//-----------------------------------【头文件包含部分】---------------------------------------
// 描述:包含程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include
//-----------------------------------【命名空间声明部分】---------------------------------------
// 描述:包含程序所使用的命名空间
//-----------------------------------------------------------------------------------------------
using namespace cv;
using namespace std;
//-----------------------------------【全局函数声明部分】--------------------------------------
// 描述:全局函数声明
//-----------------------------------------------------------------------------------------------
Mat img;
int threshval = 160; //轨迹条滑块对应的值,给初值160
//-----------------------------【on_trackbar( )函数】------------------------------------
// 描述:轨迹条的回调函数
//-----------------------------------------------------------------------------------------------
static void on_trackbar(int, void*)
{
Mat bw = threshval < 128 ? (img < threshval) : (img > threshval);
//定义点和向量
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
//查找轮廓
findContours(bw, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);
//初始化dst
Mat dst = Mat::zeros(img.size(), CV_8UC3);
//开始处理
if (!contours.empty() && !hierarchy.empty())
{
//遍历所有顶层轮廓,随机生成颜色值绘制给各连接组成部分
int idx = 0;
for (; idx >= 0; idx = hierarchy[idx][0])
{
Scalar color((rand() & 255), (rand() & 255), (rand() & 255));
//绘制填充轮廓
drawContours(dst, contours, idx, color, CV_FILLED, 8, hierarchy);
}
}
//显示窗口
imshow("Connected Components", dst);
}
//-----------------------------------【main( )函数】--------------------------------------------
// 描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main()
{
system("color 5F");
//载入图片
img = imread("2.bmp", 0);
if (!img.data) { printf("Oh,no,读取img图片文件错误~! \n"); return -1; }
//显示原图
namedWindow("Image", 1);
imshow("Image", img);
//创建处理窗口
namedWindow("Connected Components", 1);
//创建轨迹条
createTrackbar("Threshold", "Connected Components", &threshval, 255, on_trackbar);
on_trackbar(threshval, 0);//轨迹条回调函数
waitKey(0);
return 0;
}
/*
createTrackbar
这个函数我们以后会经常用到,
它创建一个可以调整数值的轨迹条,
并将轨迹条附加到指定的窗口上,
使用起来很方便。
首先大家要记住,它往往会和一个回调函数配合起来使用。
int createTrackbar(conststring& trackbarname, conststring& winname,int* value, int count, TrackbarCallback onChange=0,void* userdata=0);
第一个参数,const string&类型的trackbarname,表示轨迹条的名字,用来代表我们创建的轨迹条。
第二个参数,const string&类型的winname,填窗口的名字,表示这个轨迹条会依附到哪个窗口上,即对应namedWindow()创建窗口时填的某一个窗口名。
第三个参数,int* 类型的value,一个指向整型的指针,表示滑块的位置。并且在创建时,滑块的初始位置就是该变量当前的值。
第四个参数,int类型的count,表示滑块可以达到的最大位置的值。PS:滑块最小的位置的值始终为0。
第五个参数,TrackbarCallback类型的onChange,首先注意他有默认值0。这是一个指向回调函数的指针,每次滑块位置改变时,这个函数都会进行回调。并且这个函数的原型必须为void XXXX(int,void*);其中第一个参数是轨迹条的位置,第二个参数是用户数据(看下面的第六个参数)。如果回调是NULL指针,表示没有回调函数的调用,仅第三个参数value有变化。
第六个参数,void*类型的userdata,他也有默认值0。这个参数是用户传给回调函数的数据,用来处理轨迹条事件。如果使用的第三个参数value实参是全局变量的话,完全可以不去管这个userdata参数。
*/
//c6 创建Trackbar & 图像对比度、亮度值调整
#include
#include
#include"opencv2/imgproc/imgproc.hpp"
#include
using namespace std;
using namespace cv;
static void ContrastAndBright(int, void *);
int g_nContrastValue; //对比度值
int g_nBrightValue; //亮度值
Mat g_srcImage, g_dstImage;
int main()
{
//改变控制台前景色和背景色
system("color 5F");
//读入用户提供的图像
g_srcImage = imread("2.bmp");
if (!g_srcImage.data) { printf("Oh,no,读取g_srcImage图片错误~!\n"); return false; }
g_dstImage = Mat::zeros(g_srcImage.size(), g_srcImage.type());
//设定对比度和亮度的初值
g_nContrastValue = 80;
g_nBrightValue = 80;
//创建窗口
namedWindow("【效果图窗口】", 1);
//创建轨迹条
createTrackbar("对比度:", "【效果图窗口】", &g_nContrastValue, 300, ContrastAndBright);
createTrackbar("亮 度:", "【效果图窗口】", &g_nBrightValue, 200, ContrastAndBright);
//调用回调函数
ContrastAndBright(g_nContrastValue, 0);
ContrastAndBright(g_nBrightValue, 0);
//输出一些帮助信息
cout << endl << "\t嗯。好了,请调整滚动条观察图像效果~\n\n"
<< "\t按下“q”键时,程序退出~!\n"
<< "\n\n\t\t\t\tby浅墨";
//按下“q”键时,程序退出
while (char(waitKey(1)) != 'q') {}
return 0;
}
//-----------------------------【ContrastAndBright( )函数】------------------------------------
// 描述:改变图像对比度和亮度值的回调函数
//-----------------------------------------------------------------------------------------------
static void ContrastAndBright(int, void *)
{
//创建窗口
namedWindow("【原始图窗口】", 1);
//三个for循环,执行运算 g_dstImage(i,j) =a*g_srcImage(i,j) + b
for (int y = 0; y < g_srcImage.rows; y++)
{
for (int x = 0; x < g_srcImage.cols; x++)
{
for (int c = 0; c < 3; c++)
{
g_dstImage.at<Vec3b>(y, x)[c] = saturate_cast<uchar>((g_nContrastValue*0.01)*(g_srcImage.at<Vec3b>(y, x)[c]) + g_nBrightValue);
}
}
}
//显示图像
imshow("【原始图窗口】", g_srcImage);
imshow("【效果图窗口】", g_dstImage);
}
/*
为了访问图像的每一个像素,我们使用这样的语法: image.at(y,x)[c]
其中,y是像素所在的行, x是像素所在的列, c是R、G、B(对应0、1、2)其中之一。
因为我们的运算结果可能超出像素取值范围(溢出),还可能是非整数(如果是浮点数的话),所以我们要用saturate_cast对结果进行转换,以确保它为有效值。
这里的a也就是对比度,一般为了观察的效果,取值为0.0到3.0的浮点值,
但是我们的轨迹条一般取值都会整数,
所以在这里我们可以,将其代表对比度值的nContrastValue参数设为0到300之间的整型,
在最后的式子中乘以一个0.01,这样就可以完成轨迹条中300个不同取值的变化。
所以在式子中,我们会看到saturate_cast( (g_nContrastValue*0.01)*(image.at(y,x)[c] ) + g_nBrightValue )中的g_nContrastValue*0.01。
*/
/*
平滑处理/模糊处理:
最常见的作用是减少图像上的噪点或失真;
也可以降低图像分辨率。
*/
/*
图像滤波:
在尽量保留图像细节特征的条件下,对目标图像的噪声进行抑制;
其处理效果的好坏直接影响到后续图像处理和分析的有效性和可靠性。
消除图像中的噪声成分叫作图像的平滑化或滤波操作。
信号或图像的能量大部分集中在幅度谱的低频和中频段是很常见的,
而在较高频段,感兴趣的信息经常被噪声淹没。
因此一个能降低高频成分幅度的滤波器就能够减弱噪声的影响。
图像滤波的目的:
(1)抽出对象的特征作为图像识别的特征模式;
(2)适应图像处理的要求,消除图像数字化时所混入的噪声。
对滤波处理的要求:
(1)不能损坏图像的轮廓及边缘等重要信息;
(2)使图像清晰,视觉效果好。
空间域的平滑滤波一般采用简单平均法进行,
就是求邻近像元点的平均亮度值。
邻域的大小与平滑的效果直接相关,
邻域越大平滑的效果越好,
但邻域过大,平滑会使边缘信息损失的越大,
从而使输出的图像变得模糊,
因此需合理选择邻域的大小。
关于滤波器,一种形象的比喻法是:
我们可以把滤波器想象成一个包含加权系数的窗口,
当使用这个滤波器平滑处理图像时,
就把这个窗口放到图像之上,透过这个窗口来看我们得到的图像。
常用方法:
(1)线性
方框滤波——boxFilter函数
均值滤波(邻域平均滤波)——blur函数
高斯滤波——GaussianBlur函数
(2)非线性
中值滤波——medianBlur函数
双边滤波——bilateralFilter函数
*/
/*
线性滤波器:
常用于剔除信号中不想要的频率或从许多频率中选择一个想要的频率。
常见线性滤波器:
允许低频率通过的低通滤波器。
允许高频率通过的高通滤波器。
允许一定范围频率通过的带通滤波器。
阻止一定范围频率通过并且允许其它频率通过的带阻滤波器。
允许所有频率通过、仅仅改变相位关系的全通滤波器。
阻止一个狭窄频率范围通过的特殊带阻滤波器,陷波滤波器(Band-stop filter)。
*/
/*
关于滤波和模糊:
滤波是将信号中特定波段频率滤除的操作,是抑制和防止干扰的一项重要措施。
例如高斯滤波:
滤波可分低通滤波和高通滤波两种;
而高斯滤波是指用高斯函数作为滤波函数的滤波操作,
至于是不是模糊,要看是高斯低通还是高斯高通,
低通就是模糊,高通就是锐化。
高斯模糊就是高斯低通滤波。
*/
/*
邻域算子/局部算子:
利用给定像素周围的像素值,决定此像素的最终输出值。
线性邻域滤波:
一种常用的领域算子,
像素的输出值取决于输入像素的加权和。
邻域算子除了用于局部色调调整以外,还可以用于图像滤波,实现图像的平滑和锐化,图像边缘增强或者图像噪声的去除。
g=f⊙h(叉乘/卷积)
输出像素值g是输入像素值f的加权和
h表示加权系数“核”
*/
/*
方框滤波(box Filter):
封装函数——boxblur
从src输入,从dst输出
void boxFilter(InputArray src,OutputArray dst, int ddepth, Size ksize, Point anchor=Point(-1,-1), boolnormalize=true, int borderType=BORDER_DEFAULT )
第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。该函数对通道是独立处理的,且可以处理任意通道数的图片,但需要注意,待处理的图片深度应该为CV_8U, CV_16U, CV_16S, CV_32F 以及 CV_64F之一。
第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。
第三个参数,int类型的ddepth,输出图像的深度,-1代表使用原图深度,即src.depth()。
第四个参数,Size类型(对Size类型稍后有讲解)的ksize,内核的大小。一般这样写Size( w,h )来表示内核的大小( 其中,w 为像素宽度, h为像素高度)。Size(3,3)就表示3x3的核大小,Size(5,5)就表示5x5的核大小
第五个参数,Point类型的anchor,表示锚点(即被平滑的那个点),注意他有默认值Point(-1,-1)。如果这个点坐标是负值的话,就表示取核的中心为锚点,所以默认值Point(-1,-1)表示这个锚点在核的中心。
第六个参数,bool类型的normalize,默认值为true,一个标识符,表示内核是否被其区域归一化(normalized)了。
第七个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。有默认值BORDER_DEFAULT,我们一般不去管它。
//载入原图
Mat image=imread("2.jpg");
//进行均值滤波操作
Mat out;
boxFilter(image, out, -1,Size(5, 5));
均值滤波是方框滤波归一化(normalized)后的特殊情况。
其中,归一化就是把要处理的量都缩放到一个范围内,比如(0,1),以便统一处理和直观量化。
而非归一化(Unnormalized)的方框滤波用于计算每个像素邻域内的积分特性,
比如密集光流算法(dense optical flow algorithms)中用到的图像倒数的协方差矩阵(covariance matrices of image derivatives)。
如果我们要在可变的窗口中计算像素总和,可以使用integral()函数。
*/
/*
均值滤波:
均值滤波,是最简单的一种滤波操作,
输出图像的每一个像素是核窗口内输入图像对应像素的像素的平均值( 所有像素加权系数相等),
即归一化后的方框滤波。
主要方法为邻域平均法,
即用一片图像区域的各个像素的均值来代替原图像中的各个像素值。
一般需要在图像上对目标像素给出一个模板(内核),
该模板包括了其周围的临近像素(比如以目标像素为中心的周围8(3x3-1)个像素,构成一个滤波模板,即去掉目标像素本身);
再用模板中的全体像素的平均值来代替原来像素值。
均值滤波本身存在着固有的缺陷,
即它不能很好地保护图像细节,
在图像去噪的同时也破坏了图像的细节部分,
从而使图像变得模糊,不能很好地去除噪声点。
blur函数
作用:对输入的图像src进行均值滤波后用dst输出。
void blur(InputArray src, OutputArraydst, Size ksize, Point anchor=Point(-1,-1), int borderType=BORDER_DEFAULT )
第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。该函数对通道是独立处理的,且可以处理任意通道数的图片,但需要注意,待处理的图片深度应该为CV_8U, CV_16U, CV_16S, CV_32F 以及 CV_64F之一。
第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。比如可以用Mat::Clone,以源图片为模板,来初始化得到如假包换的目标图。
第三个参数,Size类型(对Size类型稍后有讲解)的ksize,内核的大小。一般这样写Size( w,h )来表示内核的大小( 其中,w 为像素宽度, h为像素高度)。Size(3,3)就表示3x3的核大小,Size(5,5)就表示5x5的核大小
第四个参数,Point类型的anchor,表示锚点(即被平滑的那个点),注意他有默认值Point(-1,-1)。如果这个点坐标是负值的话,就表示取核的中心为锚点,所以默认值Point(-1,-1)表示这个锚点在核的中心。
第五个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。有默认值BORDER_DEFAULT,我们一般不去管它。
*/
/*
高斯滤波:
高斯滤波是一种线性平滑滤波,适用于消除高斯噪声,
广泛应用于图像处理的减噪过程。
高斯滤波就是对整幅图像进行加权平均的过程,
每一个像素点的值,都由其本身和其邻域内的其他像素值经过加权平均后得到。
高斯滤波的具体操作:
用一个模板(或称卷积、掩膜)扫描图像中的每一个像素,
用模板确定的邻域内像素的加权平均灰度值去替代模板中心像素点的值。
高斯模糊技术生成的图像,其视觉效果就像是经过一个半透明屏幕在观察图像,
这与镜头焦外成像效果散景以及普通照明阴影中的效果都明显不同。
高斯平滑也用于计算机视觉算法中的预先处理阶段,
以增强图像在不同比例大小下的图像效果(参见尺度空间表示以及尺度空间实现)。
从数学的角度来看,图像的高斯模糊过程就是图像与正态分布做卷积。
由于正态分布又叫作高斯分布,所以这项技术就叫作高斯模糊。
图像与圆形方框模糊做卷积将会生成更加精确的焦外成像效果。
由于高斯函数的傅立叶变换是另外一个高斯函数,
所以高斯模糊对于图像来说就是一个低通滤波操作。
GaussianBlur函数
作用:用高斯滤波器来模糊一张图片,对输入的图像src进行均值滤波后用dst输出。
它将源图像和指定的高斯核函数做卷积运算,并且支持就地过滤。
void GaussianBlur(InputArray src,OutputArray dst, Size ksize, double sigmaX, double sigmaY=0, intborderType=BORDER_DEFAULT )
第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。它可以是单独的任意通道数的图片,但需要注意,图片深度应该为CV_8U,CV_16U, CV_16S, CV_32F 以及 CV_64F之一。
第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。比如可以用Mat::Clone,以源图片为模板,来初始化得到如假包换的目标图。
第三个参数,Size类型的ksize高斯内核的大小。其中ksize.width和ksize.height可以不同,但他们都必须为正数和奇数。或者,它们可以是零的,它们都是由sigma计算而来。
第四个参数,double类型的sigmaX,表示高斯核函数在X方向的的标准偏差。
第五个参数,double类型的sigmaY,表示高斯核函数在Y方向的的标准偏差。若sigmaY为零,就将它设为sigmaX,如果sigmaX和sigmaY都是0,那么就由ksize.width和ksize.height计算出来。
为了结果的正确性着想,最好是把第三个参数Size,第四个参数sigmaX和第五个参数sigmaY全部指定到。
第六个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。有默认值BORDER_DEFAULT,我们一般不去管它。
*/
//c8 线性邻域滤波专场:方框滤波、均值滤波与高斯滤波
#include
#include
#include
#include
using namespace cv;
using namespace std;
//-----------------------------------【全局变量声明部分】--------------------------------------
Mat g_srcImage, g_dstImage1, g_dstImage2, g_dstImage3;//存储图片的Mat类型
int g_nBoxFilterValue = 3; //方框滤波参数值
int g_nMeanBlurValue = 3; //均值滤波参数值
int g_nGaussianBlurValue = 3; //高斯滤波参数值
//-----------------------------------【全局函数声明部分】--------------------------------------
//轨迹条的回调函数
static void on_BoxFilter(int, void *); //方框滤波
static void on_MeanBlur(int, void *); //均值滤波
static void on_GaussianBlur(int, void *); //高斯滤波
int main()
{
//改变console字体颜色
system("color 5E");
//载入原图
g_srcImage = imread("girl.jpg", 1);
if (!g_srcImage.data) { printf("读取srcImage错误!\n"); return false; }
//克隆原图到三个Mat类型中
g_dstImage1 = g_srcImage.clone();
g_dstImage2 = g_srcImage.clone();
g_dstImage3 = g_srcImage.clone();
//显示原图
namedWindow("【<0>原图窗口】", 1);
imshow("【<0>原图窗口】", g_srcImage);
//=================【<1>方框滤波】==================
//创建窗口
namedWindow("【<1>方框滤波】", 1);
//创建轨迹条
createTrackbar("内核值:", "【<1>方框滤波】", &g_nBoxFilterValue, 40, on_BoxFilter);
on_MeanBlur(g_nBoxFilterValue, 0);
imshow("【<1>方框滤波】", g_dstImage1);
//================================================
//=================【<2>均值滤波】==================
//创建窗口
namedWindow("【<2>均值滤波】", 1);
//创建轨迹条
createTrackbar("内核值:", "【<2>均值滤波】", &g_nMeanBlurValue, 40, on_MeanBlur);
on_MeanBlur(g_nMeanBlurValue, 0);
//================================================
//=================【<3>高斯滤波】=====================
//创建窗口
namedWindow("【<3>高斯滤波】", 1);
//创建轨迹条
createTrackbar("内核值:", "【<3>高斯滤波】", &g_nGaussianBlurValue, 40, on_GaussianBlur);
on_GaussianBlur(g_nGaussianBlurValue, 0);
//================================================
//输出一些帮助信息
cout << endl << "\t请调整滚动条观察图像效果\n\n";
waitKey(0);
return 0;
}
//-----------------------------【on_BoxFilter( )函数】-----------------------------------
// 描述:方框滤波操作的回调函数
//---------------------------------------------------------------------------------------
static void on_BoxFilter(int, void *)
{
//方框滤波操作
boxFilter(g_srcImage, g_dstImage1, -1, Size(g_nBoxFilterValue + 1, g_nBoxFilterValue + 1));
//显示窗口
imshow("【<1>方框滤波】", g_dstImage1);
}
//-----------------------------【on_MeanBlur( )函数】------------------------------------
// 描述:均值滤波操作的回调函数
//---------------------------------------------------------------------------------------
static void on_MeanBlur(int, void *)
{
//均值滤波操作
blur(g_srcImage, g_dstImage2, Size(g_nMeanBlurValue + 1, g_nMeanBlurValue + 1), Point(-1, -1));
//显示窗口
imshow("【<2>均值滤波】", g_dstImage2);
}
//-----------------------------【on_GaussianBlur( )函数】---------------------------------
// 描述:高斯滤波操作的回调函数
//----------------------------------------------------------------------------------------
static void on_GaussianBlur(int, void *)
{
//高斯滤波操作
GaussianBlur(g_srcImage, g_dstImage3, Size(g_nGaussianBlurValue * 2 + 1, g_nGaussianBlurValue * 2 + 1), 0, 0);
//显示窗口
imshow("【<3>高斯滤波】", g_dstImage3);
}
/* 非线性滤波 */
/*
中值滤波:
中值滤波的基本思想是用像素点邻域灰度值的中值来代替该像素点的灰度值,
该方法在去除脉冲噪声、椒盐噪声的同时又能保留图像边缘细节。
中值滤波是基于排序统计理论的一种能有效抑制噪声的非线性信号处理技术;
其基本原理是把数字图像或数字序列中一点的值用该点的一个邻域中各店值的中值代替,
让周围的像素值接近的真实值,
从而消除孤立的噪声点;
对于斑点噪声(speckle noise)和椒盐噪声(salt-and-pepper noise)来说尤其有用,
因为它不依赖于邻域内那些与典型值差别很大的值。
中值滤波器在处理连续图像窗函数时与线性滤波器的工作方式类似,
但滤波过程却不再是加权运算。
中值滤波在一定的条件下可以克服常见线性滤波器如最小均方滤波、方框滤波器、均值滤波等带来的图像细节模糊,
而且对滤除脉冲干扰及图像扫描噪声非常有效,
也常用于保护边缘信息,
保存边缘的特性使它在不希望出现边缘模糊的场合也很有用,
是非常经典的平滑噪声处理方法。
中值滤波器与均值滤波器比较的优势:
在均值滤波器中,由于噪声成分被放入平均计算中,所以输出受到了噪声的影响,
但是在中值滤波器中,由于噪声成分很难选上,所以几乎不会影响到输出。
因此同样用3x3区域进行处理,中值滤波消除的噪声能力更胜一筹。
中值滤波无论是在消除噪声还是保存边缘方面都是一个不错的方法。
中值滤波器与均值滤波器比较的劣势:
中值滤波花费的时间是均值滤波的5倍以上。
中值滤波选择每个像素的邻域像素中的中值作为输出,
或者说中值滤波将每一像素点的灰度值设置为该点某邻域窗口内的所有像素点灰度值的中值。
例如,取3 x 3的函数窗,计算以点[i,j]为中心的函数窗像素中值步骤如下:
(1) 按强度值大小排列像素点;
(2) 选择排序像素集的中间值作为点[i,j]的新值。
一般采用奇数点的邻域来计算中值,
但如果像素点数为偶数时,中值就取排序像素中间两点的平均值。
medianBlur函数:
使用中值滤波器来平滑(模糊)处理一张图片,
从src输入,而结果从dst输出。
且对于多通道图片,每一个通道都单独进行处理,
并且支持就地操作(In-placeoperation)。
void medianBlur(InputArray src,OutputArray dst, int ksize)
第一个参数,InputArray类型的src,函数的输入参数,填1、3或者4通道的Mat类型的图像;当ksize为3或者5的时候,图像深度需为CV_8U,CV_16U,或CV_32F其中之一,而对于较大孔径尺寸的图片,它只能是CV_8U。
第二个参数,OutputArray类型的dst,即目标图像,函数的输出参数,需要和源图片有一样的尺寸和类型。我们可以用Mat::Clone,以源图片为模板,来初始化得到如假包换的目标图。
第三个参数,int类型的ksize,孔径的线性尺寸(aperture linear size),注意这个参数必须是大于1的奇数,比如:3,5,7,9 ...
*/
/*
双边滤波:
双边滤波(Bilateral filter)是结合图像的空间邻近度和像素值相似度的一种折衷处理,
同时考虑空域信息和灰度相似性,达到保边去噪的目的。
具有简单、非迭代、局部的特点。
双边滤波器的好处是可以做边缘保存(edge preserving),
一般过去用的维纳滤波或者高斯滤波去降噪,
都会较明显地模糊边缘,对于高频细节的保护效果并不明显。
双边滤波器顾名思义比高斯滤波多了一个高斯方差sigma-d,
它是基于空间分布的高斯滤波函数,所以在边缘附近,
离的较远的像素不会太多影响到边缘上的像素值,
这样就保证了边缘附近像素值的保存。
但是由于保存了过多的高频信息,
对于彩色图像里的高频噪声,
双边滤波器不能够干净的滤掉,只能够对于低频信息进行较好的滤波。
在双边滤波器中,输出像素的值依赖于邻域像素值的加权值组合。
而加权系数取决于定义域核和值域核的乘积。
bilateralFilter函数:
用双边滤波器来处理一张图片,
由src输入图片,结果于dst输出。
void bilateralFilter(InputArray src, OutputArraydst, int d, double sigmaColor, double sigmaSpace, int borderType=BORDER_DEFAULT)
第一个参数,InputArray类型的src,输入图像,即源图像,需要为8位或者浮点型单通道、三通道的图像。
第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。
第三个参数,int类型的d,表示在过滤过程中每个像素邻域的直径。如果这个值我们设其为非正数,那么OpenCV会从第五个参数sigmaSpace来计算出它来。
第四个参数,double类型的sigmaColor,颜色空间滤波器的sigma值。这个参数的值越大,就表明该像素邻域内有更宽广的颜色会被混合到一起,产生较大的半相等颜色区域。
第五个参数,double类型的sigmaSpace坐标空间中滤波器的sigma值,坐标空间的标注方差。他的数值越大,意味着越远的像素会相互影响,从而使更大的区域足够相似的颜色获取相同的颜色。当d>0,d指定了邻域大小且与sigmaSpace无关。否则,d正比于sigmaSpace。
第六个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_DEFAULT。
*/
//c9 非线性滤波
#include
#include
#include
#include
using namespace cv;
using namespace std;
//-----------------------------------【全局变量声明部分】--------------------------------------
Mat g_srcImage, g_dstImage1, g_dstImage2, g_dstImage3, g_dstImage4, g_dstImage5;
int g_nBoxFilterValue = 6; //方框滤波内核值
int g_nMeanBlurValue = 10; //均值滤波内核值
int g_nGaussianBlurValue = 6; //高斯滤波内核值
int g_nMedianBlurValue = 10; //中值滤波参数值
int g_nBilateralFilterValue = 10; //双边滤波参数值
//-----------------------------------【全局函数声明部分】--------------------------------------
//轨迹条回调函数
static void on_BoxFilter(int, void *); //方框滤波
static void on_MeanBlur(int, void *); //均值块滤波器
static void on_GaussianBlur(int, void *); //高斯滤波器
static void on_MedianBlur(int, void *); //中值滤波器
static void on_BilateralFilter(int, void*); //双边滤波器
int main()
{
system("color 5E");
//载入原图
g_srcImage = imread("girl.jpg", 1);
if (!g_srcImage.data) { printf("Oh,no,读取srcImage错误~!\n"); return false; }
//克隆原图到四个Mat类型中
g_dstImage1 = g_srcImage.clone();
g_dstImage2 = g_srcImage.clone();
g_dstImage3 = g_srcImage.clone();
g_dstImage4 = g_srcImage.clone();
g_dstImage5 = g_srcImage.clone();
//显示原图
namedWindow("【<0>原图窗口】", 1);
imshow("【<0>原图窗口】", g_srcImage);
//=================【<1>方框滤波】=========================
//创建窗口
namedWindow("【<1>方框滤波】", 1);
//创建轨迹条
createTrackbar("内核值:", "【<1>方框滤波】", &g_nBoxFilterValue, 50, on_BoxFilter);
on_MeanBlur(g_nBoxFilterValue, 0);
imshow("【<1>方框滤波】", g_dstImage1);
//=====================================================
//=================【<2>均值滤波】==========================
//创建窗口
namedWindow("【<2>均值滤波】", 1);
//创建轨迹条
createTrackbar("内核值:", "【<2>均值滤波】", &g_nMeanBlurValue, 50, on_MeanBlur);
on_MeanBlur(g_nMeanBlurValue, 0);
//======================================================
//=================【<3>高斯滤波】===========================
//创建窗口
namedWindow("【<3>高斯滤波】", 1);
//创建轨迹条
createTrackbar("内核值:", "【<3>高斯滤波】", &g_nGaussianBlurValue, 50, on_GaussianBlur);
on_GaussianBlur(g_nGaussianBlurValue, 0);
//=======================================================
//=================【<4>中值滤波】===========================
//创建窗口
namedWindow("【<4>中值滤波】", 1);
//创建轨迹条
createTrackbar("参数值:", "【<4>中值滤波】", &g_nMedianBlurValue, 50, on_MedianBlur);
on_MedianBlur(g_nMedianBlurValue, 0);
//=======================================================
//=================【<5>双边滤波】===========================
//创建窗口
namedWindow("【<5>双边滤波】", 1);
//创建轨迹条
createTrackbar("参数值:", "【<5>双边滤波】", &g_nBilateralFilterValue, 50, on_BilateralFilter);
on_BilateralFilter(g_nBilateralFilterValue, 0);
//=======================================================
//输出一些帮助信息
cout << endl << "\t嗯。好了,请调整滚动条观察图像效果~\n\n"
<< "\t按下“q”键时,程序退出~!\n"
<< "\n\n\t\t\t\tby浅墨";
while (char(waitKey(1)) != 'q') {}
return 0;
}
//-----------------------------【on_BoxFilter( )函数】------------------------------------
// 描述:方框滤波操作的回调函数
//-----------------------------------------------------------------------------------------------
static void on_BoxFilter(int, void *)
{
//方框滤波操作
boxFilter(g_srcImage, g_dstImage1, -1, Size(g_nBoxFilterValue + 1, g_nBoxFilterValue + 1));
//显示窗口
imshow("【<1>方框滤波】", g_dstImage1);
}
//-----------------------------【on_MeanBlur( )函数】------------------------------------
// 描述:均值滤波操作的回调函数
//-----------------------------------------------------------------------------------------------
static void on_MeanBlur(int, void *)
{
blur(g_srcImage, g_dstImage2, Size(g_nMeanBlurValue + 1, g_nMeanBlurValue + 1), Point(-1, -1));
imshow("【<2>均值滤波】", g_dstImage2);
}
//-----------------------------【on_GaussianBlur( )函数】------------------------------------
// 描述:高斯滤波操作的回调函数
//-----------------------------------------------------------------------------------------------
static void on_GaussianBlur(int, void *)
{
GaussianBlur(g_srcImage, g_dstImage3, Size(g_nGaussianBlurValue * 2 + 1, g_nGaussianBlurValue * 2 + 1), 0, 0);
imshow("【<3>高斯滤波】", g_dstImage3);
}
//-----------------------------【on_MedianBlur( )函数】------------------------------------
// 描述:中值滤波操作的回调函数
//-----------------------------------------------------------------------------------------------
static void on_MedianBlur(int, void *)
{
medianBlur(g_srcImage, g_dstImage4, g_nMedianBlurValue * 2 + 1);
imshow("【<4>中值滤波】", g_dstImage4);
}
//-----------------------------【on_BilateralFilter( )函数】------------------------------------
// 描述:双边滤波操作的回调函数
//-----------------------------------------------------------------------------------------------
static void on_BilateralFilter(int, void *)
{
bilateralFilter(g_srcImage, g_dstImage5, g_nBilateralFilterValue, g_nBilateralFilterValue * 2, g_nBilateralFilterValue / 2);
imshow("【<5>双边滤波】", g_dstImage5);
}
/* 形态学图像处理(一):膨胀与腐蚀 */
/*
形态学:
数学形态学(Mathematical morphology) 是一门建立在格论和拓扑学基础之上的图像分析学科,
是数学形态学图像处理的基本理论。
其基本的运算包括:二值腐蚀和膨胀、二值开闭运算、骨架抽取、极限腐蚀、击中击不中变换、形态学梯度、Top-hat变换、颗粒分析、流域变换、灰值腐蚀和膨胀、灰值开闭运算、灰值形态学梯度等。
简单来讲,形态学操作就是基于形状的一系列图像处理操作。
OpenCV为进行图像的形态学变换提供了快捷、方便的函数。
*/
/*
膨胀与腐蚀:
最基本的形态学操作有两种:膨胀与腐蚀(Dilation与Erosion)。
膨胀与腐蚀能实现多种多样的功能,主要如下:
消除噪声
分割(isolate)出独立的图像元素,在图像中连接(join)相邻的元素
寻找图像中的明显的极大值区域或极小值区域
求出图像的梯度
腐蚀和膨胀是对白色部分(高亮部分)而言的,不是黑色部分。
膨胀就是图像中的高亮部分进行膨胀,“领域扩张”,效果图拥有比原图更大的高亮区域。
腐蚀就是原图中的高亮部分被腐蚀,“领域被蚕食”,效果图拥有比原图更小的高亮区域。
膨胀:
膨胀就是求局部最大值的操作。
从数学方面角度上看,
膨胀或者腐蚀操作就是将图像(或图像的一部分区域,我们称之为A)与核(我们称之为B)进行卷积。
核可以是任何的形状和大小,它拥有一个单独定义出来的参考点,我们称其为锚点(anchorpoint)。
多数情况下,核是一个小的中间带有参考点和实心正方形或者圆盘,
其实,我们可以把核视为模板或者掩码。
而膨胀就是求局部最大值的操作,核B与图形卷积,
即计算核B覆盖的区域的像素点的最大值,并把这个最大值赋值给参考点指定的像素。
这样就会使图像中的高亮区域逐渐增长。
void dilate(
InputArray src,
OutputArray dst,
InputArray kernel,
Point anchor=Point(-1,-1),
int iterations=1,
int borderType=BORDER_CONSTANT,
const Scalar& borderValue=morphologyDefaultBorderValue()
);
第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。图像通道的数量可以是任意的,但图像深度应为CV_8U,CV_16U,CV_16S,CV_32F或 CV_64F其中之一。
第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。
第三个参数,InputArray类型的kernel,膨胀操作的核。若为NULL时,表示的是使用参考点位于中心3x3的核。
第四个参数,Point类型的anchor,锚的位置,其有默认值(-1,-1),表示锚位于中心。
第五个参数,int类型的iterations,迭代使用erode()函数的次数,默认值为1。
第六个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_DEFAULT。
第七个参数,const Scalar&类型的borderValue,当边界为常数时的边界值,有默认值morphologyDefaultBorderValue(),一般我们不用去管他。需要用到它时,可以看官方文档中的createMorphologyFilter()函数得到更详细的解释。
一般我们只需要填前面的三个参数,后面的四个参数都有默认值。
而且往往结合getStructuringElement一起使用。
腐蚀:
腐蚀就是求局部最小值的操作。
void erode(
InputArray src,
OutputArray dst,
InputArray kernel,
Point anchor=Point(-1,-1),
int iterations=1,
int borderType=BORDER_CONSTANT,
const Scalar& borderValue=morphologyDefaultBorderValue()
);
第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。图像通道的数量可以是任意的,但图像深度应为CV_8U,CV_16U,CV_16S,CV_32F或 CV_64F其中之一。
第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。
第三个参数,InputArray类型的kernel,腐蚀操作的内核。若为NULL时,表示的是使用参考点位于中心3x3的核。我们一般使用函数 getStructuringElement配合这个参数的使用。getStructuringElement函数会返回指定形状和尺寸的结构元素(内核矩阵)。(具体看上文中浅出部分dilate函数的第三个参数讲解部分)
第四个参数,Point类型的anchor,锚的位置,其有默认值(-1,-1),表示锚位于单位(element)的中心,我们一般不用管它。
第五个参数,int类型的iterations,迭代使用erode()函数的次数,默认值为1。
第六个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_DEFAULT。
第七个参数,const Scalar&类型的borderValue,当边界为常数时的边界值,有默认值morphologyDefaultBorderValue(),一般我们不用去管他。需要用到它时,可以看官方文档中的createMorphologyFilter()函数得到更详细的解释。
其中,对于第三个参数:
我们一般使用函数 getStructuringElement配合这个参数的使用。
getStructuringElement函数会返回指定形状和尺寸的结构元素(内核矩阵)。
其中,getStructuringElement函数的第一个参数表示内核的形状,
我们可以选择如下三种形状之一:
矩形: MORPH_RECT
交叉形: MORPH_CROSS
椭圆形: MORPH_ELLIPSE
而getStructuringElement函数的第二和第三个参数分别是内核的尺寸以及锚点的位置。
我们一般在调用erode以及dilate函数之前,
先定义一个Mat类型的变量来获得getStructuringElement函数的返回值。
对于锚点的位置,有默认值Point(-1,-1),表示锚点位于中心。
且需要注意,十字形的element形状唯一依赖于锚点的位置。
而在其他情况下,锚点只是影响了形态学运算结果的偏移。
getStructuringElement函数相关的调用示例代码如下:
int g_nStructElementSize = 3; //结构元素(内核矩阵)的尺寸
//获取自定义核
Mat element = getStructuringElement(MORPH_RECT,
Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),
Point( g_nStructElementSize, g_nStructElementSize ));
调用这样之后,我们便可以在接下来调用erode或dilate函数时,
第三个参数填保存了getStructuringElement返回值的Mat类型变量。
对应于我们上面的示例,就是填element变量。
*/
//c10 形态学图像处理(一):膨胀与腐蚀
#include
#include
#include
#include
using namespace cv;
using namespace std;
//-----------------------------------【全局变量声明部分】--------------------------------------
Mat g_srcImage, g_dstImage;//原始图和效果图
int g_nTrackbarNumer = 0;//0表示腐蚀erode, 1表示膨胀dilate
int g_nStructElementSize = 3; //结构元素(内核矩阵)的尺寸
//-----------------------------------【全局函数声明部分】--------------------------------------
void Process();//膨胀和腐蚀的处理函数
void on_TrackbarNumChange(int, void *);//回调函数
void on_ElementSizeChange(int, void *);//回调函数
int main()
{
//改变console字体颜色
system("color 5E");
//载入原图
g_srcImage = imread("1.jpg");
if (!g_srcImage.data) { printf("读取srcImage错误~!\n"); return false; }
//显示原始图
namedWindow("【原始图】");
imshow("【原始图】", g_srcImage);
//进行初次腐蚀操作并显示效果图
namedWindow("【效果图】");
//获取自定义核
Mat element = getStructuringElement(MORPH_RECT, Size(2 * g_nStructElementSize + 1, 2 * g_nStructElementSize + 1), Point(g_nStructElementSize, g_nStructElementSize));
erode(g_srcImage, g_dstImage, element);
imshow("【效果图】", g_dstImage);
//轨迹条1:0为腐蚀/1为膨胀
//轨迹条2:调整程度
//创建轨迹条
createTrackbar("腐蚀/膨胀", "【效果图】", &g_nTrackbarNumer, 1, on_TrackbarNumChange);
createTrackbar("内核尺寸", "【效果图】", &g_nStructElementSize, 21, on_ElementSizeChange);
cout << endl << "\t请调整滚动条观察图像效果~\n\n";
waitKey(0);
return 0;
}
//-----------------------------【Process( )函数】------------------------------------
// 描述:进行自定义的腐蚀和膨胀操作
//-----------------------------------------------------------------------------------------
void Process()
{
//获取自定义核
Mat element = getStructuringElement(MORPH_RECT, Size(2 * g_nStructElementSize + 1, 2 * g_nStructElementSize + 1), Point(g_nStructElementSize, g_nStructElementSize));
//进行腐蚀或膨胀操作
if (g_nTrackbarNumer == 0) {
erode(g_srcImage, g_dstImage, element);
}
else{
dilate(g_srcImage, g_dstImage, element);
}
//显示效果图
imshow("【效果图】", g_dstImage);
}
//-----------------------------【on_TrackbarNumChange( )函数】------------------------------------
// 描述:腐蚀和膨胀之间切换开关的回调函数
//-----------------------------------------------------------------------------------------------------
void on_TrackbarNumChange(int, void *)
{
//腐蚀和膨胀之间效果已经切换,回调函数体内需调用一次Process函数,使改变后的效果立即生效并显示出来
Process();
}
//-----------------------------【on_ElementSizeChange( )函数】-------------------------------------
// 描述:腐蚀和膨胀操作内核改变时的回调函数
//-----------------------------------------------------------------------------------------------------
void on_ElementSizeChange(int, void *)
{
//内核尺寸已改变,回调函数体内需调用一次Process函数,使改变后的效果立即生效并显示出来
Process();
}
/* 形态学图像处理(二):开运算、闭运算、形态学梯度、顶帽、黑帽合辑 */
/*
Opening Operation
开运算:
先腐蚀后膨胀
dst = open(src,element) = dilate(erode(src,element))
可以用来消除小物体、在纤细点处分离物体、平滑较大物体的边界的同时并不明显改变其面积。
更少高亮。
*/
/*
Closing Operation
闭运算:
先膨胀后腐蚀
dst = close(src,element) = erode(dilate(src,element))
可以排除小型黑洞(黑色区域)。
更多高亮。
*/
/*
MorphologicalGradient
形态学梯度:
膨胀图与腐蚀图之差
dst = morph_grad(src,element) = dilate(src,element) - erode(src,element)
对二值图像进行这一操作可以将团块的边缘突出出来,
可以保留物体的边缘轮廓。
*/
/*
Top Hat
顶帽(礼帽):
原图像与“开运算”结果图之差
dst = tophat(src,element) = src - open(src,element)
因为开运算带来的结果是放大了裂缝或者局部低亮度的区域,
因此,从原图中减去开运算后的图,
得到的效果图突出了比原图轮廓周围的区域更明亮的区域,
且这一操作和选择的核的大小相关。
顶帽运算往往用来分离比邻近点亮一些的斑块。
当一幅图像具有大幅的背景而微小物品比较有规律的情况下,
可以使用顶帽运算进行背景提取。
*/
/*
Black Hat
黑帽:
“闭运算”结果图与原图像之差
dst = blackhat(src,element) = close(src,element) - src
黑帽运算后的效果图突出了比原图轮廓周围的区域更暗的区域,
且这一操作和选择的核的大小相关。
黑帽运算往往用来分离比邻近点暗一些的斑块。
*/
/*
morphologyEx函数
利用基本的膨胀和腐蚀技术,来执行更加高级形态学变换,
如开闭运算,形态学梯度,“顶帽”、“黑帽”等等。
void morphologyEx
(
InputArray src,
OutputArray dst,
int op,
InputArraykernel,
Pointanchor=Point(-1,-1),
intiterations=1,
intborderType=BORDER_CONSTANT,
constScalar& borderValue=morphologyDefaultBorderValue()
);
第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。图像位深应该为以下五种之一:CV_8U, CV_16U,CV_16S, CV_32F 或CV_64F。
第二个参数,OutputArray类型的dst,即目标图像,函数的输出参数,需要和源图片有一样的尺寸和类型。
第三个参数,int类型的op,表示形态学运算的类型,可以是如下之一的标识符:
MORPH_OPEN – 开运算(Opening operation)
MORPH_CLOSE – 闭运算(Closing operation)
MORPH_GRADIENT -形态学梯度(Morphological gradient)
MORPH_TOPHAT - “顶帽”(“Top hat”)
MORPH_BLACKHAT - “黑帽”(“Black hat“)
第四个参数,InputArray类型的kernel,形态学运算的内核。
若为NULL时,表示的是使用参考点位于中心3x3的核。
我们一般使用函数 getStructuringElement配合这个参数的使用。
getStructuringElement函数会返回指定形状和尺寸的结构元素(内核矩阵)。
getStructuringElement函数的第一个参数表示内核的形状,我们可以选择如下三种形状之一:
矩形: MORPH_RECT
交叉形: MORPH_CROSS
椭圆形: MORPH_ELLIPSE
而getStructuringElement函数的第二和第三个参数分别是内核的尺寸以及锚点的位置。
我们一般在调用erode以及dilate函数之前,先定义一个Mat类型的变量来获得getStructuringElement函数的返回值。
对于锚点的位置,有默认值Point(-1,-1),表示锚点位于中心。
且需要注意,十字形的element形状唯一依赖于锚点的位置。
而在其他情况下,锚点只是影响了形态学运算结果的偏移。
getStructuringElement函数:
int g_nStructElementSize = 3; //结构元素(内核矩阵)的尺寸
//获取自定义核
Mat element =getStructuringElement(MORPH_RECT,
Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),
Point(g_nStructElementSize, g_nStructElementSize ));
调用这样之后,
我们便可以在接下来调用erode、dilate或morphologyEx函数时,
kernel参数填保存getStructuringElement返回值的Mat类型变量。
对应于我们上面的示例,就是填element变量。
第五个参数,Point类型的anchor,锚的位置,其有默认值(-1,-1),表示锚位于中心。
第六个参数,int类型的iterations,迭代使用函数的次数,默认值为1。
第七个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_ CONSTANT。
第八个参数,const Scalar&类型的borderValue,当边界为常数时的边界值,有默认值morphologyDefaultBorderValue(),一般我们不用去管他。需要用到它时,可以看官方文档中的createMorphologyFilter()函数得到更详细的解释。
其中的这些操作都可以进行就地(in-place)操作。
且对于多通道图像,每一个通道都是单独进行操作。
*/
//c11 形态学图像处理(二):开运算、闭运算、形态学梯度、顶帽、黑帽合辑
#include
#include
#include
#include
using namespace cv;
using namespace std;
//-----------------------------------【全局变量声明部分】--------------------------------------
Mat g_srcImage, g_dstImage;//原始图和效果图
int g_nElementShape = MORPH_RECT;//元素结构的形状
//变量接收的TrackBar位置参数
int g_nMaxIterationNum = 10;
int g_nOpenCloseNum = 0;
int g_nErodeDilateNum = 0;
int g_nTopBlackHatNum = 0;
//-----------------------------------【全局函数声明部分】--------------------------------------
static void on_OpenClose(int, void*);//回调函数
static void on_ErodeDilate(int, void*);//回调函数
static void on_TopBlackHat(int, void*);//回调函数
static void ShowHelpText();//帮助文字显示
int main()
{
//改变console字体颜色
system("color 2F");
ShowHelpText();
//载入原图
g_srcImage = imread("girl.jpg");//工程目录下需要有一张名为1.jpg的素材图
if (!g_srcImage.data) { printf("Oh,no,读取srcImage错误~! \n"); return false; }
//显示原始图
namedWindow("【原始图】");
imshow("【原始图】", g_srcImage);
//创建三个窗口
namedWindow("【开运算/闭运算】", 1);
namedWindow("【腐蚀/膨胀】", 1);
namedWindow("【顶帽/黑帽】", 1);
//参数赋值
g_nOpenCloseNum = 9;
g_nErodeDilateNum = 9;
g_nTopBlackHatNum = 2;
//分别为三个窗口创建滚动条
createTrackbar("迭代值", "【开运算/闭运算】", &g_nOpenCloseNum, g_nMaxIterationNum * 2 + 1, on_OpenClose);
createTrackbar("迭代值", "【腐蚀/膨胀】", &g_nErodeDilateNum, g_nMaxIterationNum * 2 + 1, on_ErodeDilate);
createTrackbar("迭代值", "【顶帽/黑帽】", &g_nTopBlackHatNum, g_nMaxIterationNum * 2 + 1, on_TopBlackHat);
//轮询获取按键信息
while (1)
{
int c;
//执行回调函数
on_OpenClose(g_nOpenCloseNum, 0);
on_ErodeDilate(g_nErodeDilateNum, 0);
on_TopBlackHat(g_nTopBlackHatNum, 0);
//获取按键
c = waitKey(0);
//按下键盘按键Q或者ESC,程序退出
if ((char)c == 'q' || (char)c == 27)
break;
//按下键盘按键1,使用椭圆(Elliptic)结构元素结构元素MORPH_ELLIPSE
if ((char)c == 49)//键盘按键1的ASII码为49
g_nElementShape = MORPH_ELLIPSE;
//按下键盘按键2,使用矩形(Rectangle)结构元素MORPH_RECT
else if ((char)c == 50)//键盘按键2的ASII码为50
g_nElementShape = MORPH_RECT;
//按下键盘按键3,使用十字形(Cross-shaped)结构元素MORPH_CROSS
else if ((char)c == 51)//键盘按键3的ASII码为51
g_nElementShape = MORPH_CROSS;
//按下键盘按键space,在矩形、椭圆、十字形结构元素中循环
else if ((char)c == ' ')
g_nElementShape = (g_nElementShape + 1) % 3;
}
return 0;
}
//-----------------------------------【on_OpenClose( )函数】----------------------------------
// 描述:【开运算/闭运算】窗口的回调函数
//-----------------------------------------------------------------------------------------------
static void on_OpenClose(int, void*)
{
//偏移量的定义
int offset = g_nOpenCloseNum - g_nMaxIterationNum;//偏移量
int Absolute_offset = offset > 0 ? offset : -offset;//偏移量绝对值
//自定义核
Mat element = getStructuringElement(g_nElementShape, Size(Absolute_offset * 2 + 1, Absolute_offset * 2 + 1), Point(Absolute_offset, Absolute_offset));
//进行操作
if (offset < 0)
morphologyEx(g_srcImage, g_dstImage, CV_MOP_OPEN, element);
else
morphologyEx(g_srcImage, g_dstImage, CV_MOP_CLOSE, element);
//显示图像
imshow("【开运算/闭运算】", g_dstImage);
}
//-----------------------------------【on_ErodeDilate( )函数】----------------------------------
// 描述:【腐蚀/膨胀】窗口的回调函数
//-----------------------------------------------------------------------------------------------
static void on_ErodeDilate(int, void*)
{
//偏移量的定义
int offset = g_nErodeDilateNum - g_nMaxIterationNum; //偏移量
int Absolute_offset = offset > 0 ? offset : -offset;//偏移量绝对值
//自定义核
Mat element = getStructuringElement(g_nElementShape, Size(Absolute_offset * 2 + 1, Absolute_offset * 2 + 1), Point(Absolute_offset, Absolute_offset));
//进行操作
if (offset < 0)
erode(g_srcImage, g_dstImage, element);
else
dilate(g_srcImage, g_dstImage, element);
//显示图像
imshow("【腐蚀/膨胀】", g_dstImage);
}
//-----------------------------------【on_TopBlackHat( )函数】--------------------------------
// 描述:【顶帽运算/黑帽运算】窗口的回调函数
//----------------------------------------------------------------------------------------------
static void on_TopBlackHat(int, void*)
{
//偏移量的定义
int offset = g_nTopBlackHatNum - g_nMaxIterationNum;//偏移量
int Absolute_offset = offset > 0 ? offset : -offset;//偏移量绝对值
//自定义核
Mat element = getStructuringElement(g_nElementShape, Size(Absolute_offset * 2 + 1, Absolute_offset * 2 + 1), Point(Absolute_offset, Absolute_offset));
//进行操作
if (offset < 0)
morphologyEx(g_srcImage, g_dstImage, MORPH_TOPHAT, element);
else
morphologyEx(g_srcImage, g_dstImage, MORPH_BLACKHAT, element);
//显示图像
imshow("【顶帽/黑帽】", g_dstImage);
}
//-----------------------------------【ShowHelpText( )函数】----------------------------------
// 描述:输出一些帮助信息
//----------------------------------------------------------------------------------------------
static void ShowHelpText()
{
//输出一些帮助信息
printf("\n\n\n\t请调整滚动条观察图像效果~\n\n");
printf("\n\n\t按键操作说明: \n\n"
"\t\t键盘按键【ESC】或者【Q】- 退出程序\n"
"\t\t键盘按键【1】- 使用椭圆(Elliptic)结构元素\n"
"\t\t键盘按键【2】- 使用矩形(Rectangle )结构元素\n"
"\t\t键盘按键【3】- 使用十字型(Cross-shaped)结构元素\n"
"\t\t键盘按键【空格SPACE】- 在矩形、椭圆、十字形结构元素中循环\n"
"\n\n\t\t\t\t\t\t\t\t by浅墨"
);
}
/* OpenCV边缘检测:Canny算子,Sobel算子,Laplace算子,Scharr滤波器合辑 */
/*
边缘检测的一般步骤:
1)滤波
边缘检测的算法主要是基于图像强度的一阶和二阶导数,
但导数通常对噪声很敏感,
因此必须采用滤波器来改善与噪声有关的边缘检测器的性能。
常见的滤波方法主要有高斯滤波,
即采用离散化的高斯函数产生一组归一化的高斯核,
然后基于高斯核函数对图像灰度矩阵的每一点进行加权求和。
2)增强
增强边缘的基础是确定图像各点邻域强度的变化值。
增强算法可以将图像灰度点邻域强度值有显著变化的点凸显出来。
在具体编程实现时,可通过计算梯度幅值来确定。
3)检测
经过增强的图像,往往邻域中有很多点的梯度值比较大,
而在特定的应用中,这些点并不是我们要找的边缘点,
所以应该采用某种方法来对这些点进行取舍。
实际工程中,常用的方法是通过阈值化方法来检测。
另外,需要注意,下文中讲到的Laplace算子,sobel算子和Scharr算子都是带方向的,
所以,示例中我们分别写了X方向,Y方向和最终合成的的效果图。
*/
/*
canny算子:
canny算子简介:
Canny边缘检测算子是John F.Canny于 1986 年开发出来的一个多级边缘检测算法。
更为重要的是 Canny 创立了边缘检测计算理论(Computational theory ofedge detection),
解释了这项技术是如何工作的。
Canny边缘检测算法以Canny的名字命名,被很多人推崇为当今最优的边缘检测的算法。
其中,Canny 的目标是找到一个最优的边缘检测算法,
让我们看一下最优边缘检测的三个主要评价标准:
1)低错误率: 标识出尽可能多的实际边缘,同时尽可能的减少噪声产生的误报。
2)高定位性: 标识出的边缘要与图像中的实际边缘尽可能接近。
3)最小响应: 图像中的边缘只能标识一次,并且可能存在的图像噪声不应标识为边缘。
为了满足这些要求 Canny 使用了变分法,这是一种寻找满足特定功能的函数的方法。
最优检测使用四个指数函数项的和表示,但是它非常近似于高斯函数的一阶导数。
Canny边缘检测的步骤:
1)消除噪声
一般情况下,使用高斯平滑滤波器卷积降噪。
2)计算梯度幅值和方向
3)非极大值抑制
排除非边缘像素,仅仅保留了一些细线条(候选边缘)。
4)滞后阈值
滞后阈值需要两个阈值(高阈值和低阈值):
Ⅰ.如果某一像素位置的幅值超过高阈值, 该像素被保留为边缘像素。
Ⅱ.如果某一像素位置的幅值小于低阈值, 该像素被排除。
Ⅲ.如果某一像素位置的幅值在两个阈值之间,该像素仅仅在连接到一个高于高阈值的像素时被保留。
tips:对于Canny函数的使用,推荐的高低阈值比在2:1到3:1之间。
Canny函数:
void Canny
(
InputArray image,
OutputArray edges,
double threshold1,
double threshold2,
int apertureSize = 3,
bool L2gradient = false
)
第一个参数,InputArray类型的image,输入图像,即源图像,填Mat类的对象即可,且需为单通道8位图像。
第二个参数,OutputArray类型的edges,输出的边缘图,需要和源图片有一样的尺寸和类型。
第三个参数,double类型的threshold1,第一个滞后性阈值。
第四个参数,double类型的threshold2,第二个滞后性阈值。
第五个参数,int类型的apertureSize,表示应用Sobel算子的孔径大小,其有默认值3。
第六个参数,bool类型的L2gradient,一个计算图像梯度幅值的标识,有默认值false。
需要注意的是,
这个函数阈值1和阈值2两者的小者用于边缘连接,而大者用来控制强边缘的初始段,
推荐的高低阈值比在2:1到3:1之间。
Mat src = imread("1.jpg");
Canny(src, src, 3, 9,3 );
imshow("【效果图】Canny边缘检测", src);
*/
/*
sobel算子:
sobel算子基本概念:
Sobel算子是一个主要用作边缘检测的离散微分算子 (discrete differentiation operator)。
它结合了高斯平滑和微分求导,用来计算图像灰度函数的近似梯度。
在图像的任何一点使用此算子,将会产生对应的梯度矢量或是其法矢量。
Sobel函数:
void Sobel
(
InputArray src, //输入图
OutputArray dst, //输出图
int ddepth, //输出图像的深度
int dx,
int dy,
int ksize=3,
double scale=1,
double delta=0,
int borderType=BORDER_DEFAULT
);
第一个参数,InputArray 类型的src,为输入图像,填Mat类型即可。
第二个参数,OutputArray类型的dst,即目标图像,函数的输出参数,需要和源图片有一样的尺寸和类型。
第三个参数,int类型的ddepth,输出图像的深度,支持如下src.depth()和ddepth的组合:
若src.depth() = CV_8U, 取ddepth =-1/CV_16S/CV_32F/CV_64F
若src.depth() = CV_16U/CV_16S, 取ddepth =-1/CV_32F/CV_64F
若src.depth() = CV_32F, 取ddepth =-1/CV_32F/CV_64F
若src.depth() = CV_64F, 取ddepth = -1/CV_64F
第四个参数,int类型dx,x 方向上的差分阶数。
第五个参数,int类型dy,y方向上的差分阶数。
第六个参数,int类型ksize,有默认值3,表示Sobel核的大小;必须取1,3,5或7。
第七个参数,double类型的scale,计算导数值时可选的缩放因子,默认值是1,表示默认情况下是没有应用缩放的。我们可以在文档中查阅getDerivKernels的相关介绍,来得到这个参数的更多信息。
第八个参数,double类型的delta,表示在结果存入目标图(第二个参数dst)之前可选的delta值,有默认值0。
第九个参数, int类型的borderType,我们的老朋友了(万年是最后一个参数),边界模式,默认值为BORDER_DEFAULT。这个参数可以在官方文档中borderInterpolate处得到更详细的信息。
一般情况下,都是用ksize x ksize内核来计算导数的。
然而,有一种特殊情况——当ksize为1时,往往会使用3 x 1或者1 x 3的内核。
且这种情况下,并没有进行高斯平滑操作。
当内核大小为3时, 我们的Sobel内核可能产生比较明显的误差(毕竟,Sobel算子只是求取了导数的近似值而已)。
为解决这一问题,OpenCV提供了Scharr 函数,但该函数仅作用于大小为3的内核。
因为Sobel算子结合了高斯平滑和分化(differentiation),
因此结果会具有更多的抗噪性。
大多数情况下,
我们使用sobel函数时,
【xorder = 1,yorder = 0,ksize = 3】来计算图像X方向的导数,
【xorder = 0,yorder = 1,ksize = 3】来计算图像y方向的导数。
*/
/*
Laplace算子:
Laplace算子概念:
Laplacian 算子是n维欧几里德空间中的一个二阶微分算子,定义为梯度grad()的散度div()。
需要点破的是,由于Laplacian使用了图像梯度,它内部的代码其实是调用了Sobel算子的。
另附一个小tips:让一幅图像减去它的Laplacian可以增强对比度。
Laplacian函数:
void Laplacian
(
InputArray src,
OutputArray dst,
int ddepth,
int ksize=1,
double scale=1,
double delta=0,
intborderType=BORDER_DEFAULT
);
第一个参数,InputArray类型的image,输入图像,即源图像,填Mat类的对象即可,且需为单通道8位图像。
第二个参数,OutputArray类型的edges,输出的边缘图,需要和源图片有一样的尺寸和通道数。
第三个参数,int类型的ddept,目标图像的深度。
第四个参数,int类型的ksize,用于计算二阶导数的滤波器的孔径尺寸,大小必须为正奇数,且有默认值1。
第五个参数,double类型的scale,计算拉普拉斯值的时候可选的比例因子,有默认值1。
第六个参数,double类型的delta,表示在结果存入目标图(第二个参数dst)之前可选的delta值,有默认值0。
第七个参数, int类型的borderType,边界模式,默认值为BORDER_DEFAULT。这个参数可以在官方文档中borderInterpolate()处得到更详细的信息。
Laplacian( )函数其实主要是利用sobel算子的运算。
它通过加上sobel算子运算出的图像x方向和y方向上的导数,来得到我们载入图像的拉普拉斯变换结果。
*/
/*
scharr滤波器:
scharr一般我就直接称它为滤波器,而不是算子。
它在OpenCV中主要是配合Sobel算子的运算而存在的,是一个万年备胎。
Scharr函数:
void Scharr
(
InputArray src, //源图
OutputArray dst, //目标图
int ddepth, //图像深度
int dx, // x方向上的差分阶数
int dy, //y方向上的差分阶数
double scale=1, //缩放因子
double delta=0, // delta值
intborderType=BORDER_DEFAULT // 边界模式
)
使用Scharr滤波器运算符计算x或y方向的图像差分。
其实它的参数变量和Sobel基本上是一样的,除了没有ksize核的大小。
以下两者等价:
Scharr(src, dst, ddepth, dx, dy, scale,delta, borderType);
Sobel (src, dst, ddepth, dx, dy, CV_SCHARR,scale, delta, borderType);
*/
//Canny
#include
#include
#include
using namespace cv;
int main()
{
//载入原始图
Mat src = imread("11.jpg");
Mat src1 = src.clone();
Mat src2 = src.clone();
//显示原始图
imshow("【原始图】Canny边缘检测", src);
//----------------------------------------------------------------------------------
// 一、最简单的canny用法,拿到原图后直接用。
//----------------------------------------------------------------------------------
Canny(src, src2, 200, 100, 3);
imshow("1/Canny边缘检测", src2);
//----------------------------------------------------------------------------------
// 二、高阶的canny用法,转成灰度图,降噪,用canny,最后将得到的边缘作为掩码,拷贝原图到效果图上,得到彩色的边缘图
//----------------------------------------------------------------------------------
Mat dst, edge, gray;
// 【1】创建与src同类型和大小的矩阵(dst)
dst.create(src1.size(), src1.type());
// 【2】将原图像转换为灰度图像
cvtColor(src1, gray, CV_BGR2GRAY);
// 【3】先用使用 3x3内核来降噪
blur(gray, edge, Size(3, 3));
// 【4】运行Canny算子
Canny(edge, edge, 3, 9, 3);
//【5】将g_dstImage内的所有元素设置为0
dst = Scalar::all(0);
//【6】使用Canny算子输出的边缘图g_cannyDetectedEdges作为掩码,来将原图g_srcImage拷到目标图g_dstImage中
src1.copyTo(dst, edge);
//【7】显示效果图
imshow("2/Canny边缘检测", dst);
waitKey(0);
return 0;
}
//Sobel
#include
#include
#include
using namespace cv;
int main()
{
//【0】创建 grad_x 和 grad_y 矩阵
Mat grad_x, grad_y;
Mat abs_grad_x, abs_grad_y, dst;
//【1】载入原始图
Mat src = imread("11.jpg"); //工程目录下应该有一张名为1.jpg的素材图
//【2】显示原始图
imshow("【原始图】sobel边缘检测", src);
//【3】求 X方向梯度
Sobel(src, grad_x, CV_16S, 1, 0, 3, 1, 1, BORDER_DEFAULT);
convertScaleAbs(grad_x, abs_grad_x);
imshow("X方向Sobel", abs_grad_x);
//【4】求Y方向梯度
Sobel(src, grad_y, CV_16S, 0, 1, 3, 1, 1, BORDER_DEFAULT);
convertScaleAbs(grad_y, abs_grad_y);
imshow("Y方向Sobel", abs_grad_y);
//【5】合并梯度(近似)
addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, dst);
imshow("整体方向Sobel", dst);
waitKey(0);
return 0;
}
//Laplace
#include
#include
#include
using namespace cv;
int main()
{
//【0】变量的定义
Mat src, src_gray, dst, abs_dst;
//【1】载入原始图
src = imread("11.jpg");
//【2】显示原始图
imshow("【原始图】图像Laplace变换", src);
//【3】使用高斯滤波消除噪声
GaussianBlur(src, src, Size(3, 3), 0, 0, BORDER_DEFAULT);
//【4】转换为灰度图
cvtColor(src, src_gray, CV_RGB2GRAY);
//【5】使用Laplace函数
Laplacian(src_gray, dst, CV_16S, 3, 1, 0, BORDER_DEFAULT);
//【6】计算绝对值,并将结果转换成8位
convertScaleAbs(dst, abs_dst);
//【7】显示效果图
imshow("【效果图】图像Laplace变换", abs_dst);
waitKey(0);
return 0;
}
//Scharr
#include
#include
#include
using namespace cv;
int main()
{
//【0】创建 grad_x 和 grad_y 矩阵
Mat grad_x, grad_y;
Mat abs_grad_x, abs_grad_y, dst;
//【1】载入原始图
Mat src = imread("flower.jpg");
//【2】显示原始图
imshow("【原始图】Scharr滤波器", src);
//【3】求 X方向梯度
Scharr(src, grad_x, CV_16S, 1, 0, 1, 0, BORDER_DEFAULT);
convertScaleAbs(grad_x, abs_grad_x);
imshow("X方向Scharr", abs_grad_x);
//【4】求Y方向梯度
Scharr(src, grad_y, CV_16S, 0, 1, 1, 0, BORDER_DEFAULT);
convertScaleAbs(grad_y, abs_grad_y);
imshow("Y方向Scharr", abs_grad_y);
//【5】合并梯度(近似)
addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, dst);
//【6】显示效果图
imshow("合并梯度后Scharr", dst);
waitKey(0);
return 0;
}
//实例
#include
#include
#include
#include
using namespace cv;
using namespace std;
//-----------------------------------【全局变量声明部分】--------------------------------------
//原图,原图的灰度版,目标图
Mat g_srcImage, g_srcGrayImage, g_dstImage;
//Canny边缘检测相关变量
Mat g_cannyDetectedEdges;
int g_cannyLowThreshold = 1;//TrackBar位置参数
//Sobel边缘检测相关变量
Mat g_sobelGradient_X, g_sobelGradient_Y;
Mat g_sobelAbsGradient_X, g_sobelAbsGradient_Y;
int g_sobelKernelSize = 1;//TrackBar位置参数
//Scharr滤波器相关变量
Mat g_scharrGradient_X, g_scharrGradient_Y;
Mat g_scharrAbsGradient_X, g_scharrAbsGradient_Y;
//-----------------------------------【全局函数声明部分】--------------------------------------
static void ShowHelpText();
static void on_Canny(int, void*);//Canny边缘检测窗口滚动条的回调函数
static void on_Sobel(int, void*);//Sobel边缘检测窗口滚动条的回调函数
void Scharr();//封装了Scharr边缘检测相关代码的函数
//-----------------------------------【main( )函数】--------------------------------------------
// 描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main(int argc, char** argv)
{
//改变console字体颜色
system("color 2F");
//显示欢迎语
ShowHelpText();
//载入原图
g_srcImage = imread("flower.jpg");
if (!g_srcImage.data) { printf("Oh,no,读取srcImage错误~! \n"); return false; }
//显示原始图
namedWindow("【原始图】");
imshow("【原始图】", g_srcImage);
// 创建与src同类型和大小的矩阵(dst)
g_dstImage.create(g_srcImage.size(), g_srcImage.type());
// 将原图像转换为灰度图像
cvtColor(g_srcImage, g_srcGrayImage, CV_BGR2GRAY);
// 创建显示窗口
namedWindow("Canny边缘检测", CV_WINDOW_AUTOSIZE);
namedWindow("Sobel边缘检测", CV_WINDOW_AUTOSIZE);
// 创建trackbar
createTrackbar("参数值:", "Canny边缘检测", &g_cannyLowThreshold, 120, on_Canny);
createTrackbar("参数值:", "Sobel边缘检测", &g_sobelKernelSize, 3, on_Sobel);
// 调用回调函数
on_Canny(0, 0);
on_Sobel(0, 0);
//调用封装了Scharr边缘检测代码的函数
Scharr();
//轮询获取按键信息,若按下Q,程序退出
while ((char(waitKey(1)) != 'q')) {}
return 0;
}
//-----------------------------------【ShowHelpText( )函数】----------------------------------
// 描述:输出一些帮助信息
//----------------------------------------------------------------------------------------------
static void ShowHelpText()
{
//输出一些帮助信息
printf("\n\n\t嗯。运行成功,请调整滚动条观察图像效果~\n\n"
"\t按下“q”键时,程序退出~!\n"
"\n\n\t\t\t\t by浅墨");
}
//-----------------------------------【on_Canny( )函数】----------------------------------
// 描述:Canny边缘检测窗口滚动条的回调函数
//-----------------------------------------------------------------------------------------------
void on_Canny(int, void*)
{
// 先使用 3x3内核来降噪
blur(g_srcGrayImage, g_cannyDetectedEdges, Size(3, 3));
// 运行我们的Canny算子
Canny(g_cannyDetectedEdges, g_cannyDetectedEdges, g_cannyLowThreshold, g_cannyLowThreshold * 3, 3);
//先将g_dstImage内的所有元素设置为0
g_dstImage = Scalar::all(0);
//使用Canny算子输出的边缘图g_cannyDetectedEdges作为掩码,来将原图g_srcImage拷到目标图g_dstImage中
g_srcImage.copyTo(g_dstImage, g_cannyDetectedEdges);
//显示效果图
imshow("Canny边缘检测", g_dstImage);
}
//-----------------------------------【on_Sobel( )函数】----------------------------------
// 描述:Sobel边缘检测窗口滚动条的回调函数
//-----------------------------------------------------------------------------------------
void on_Sobel(int, void*)
{
// 求 X方向梯度
Sobel(g_srcImage, g_sobelGradient_X, CV_16S, 1, 0, (2 * g_sobelKernelSize + 1), 1, 1, BORDER_DEFAULT);
convertScaleAbs(g_sobelGradient_X, g_sobelAbsGradient_X);//计算绝对值,并将结果转换成8位
// 求Y方向梯度
Sobel(g_srcImage, g_sobelGradient_Y, CV_16S, 0, 1, (2 * g_sobelKernelSize + 1), 1, 1, BORDER_DEFAULT);
convertScaleAbs(g_sobelGradient_Y, g_sobelAbsGradient_Y);//计算绝对值,并将结果转换成8位
// 合并梯度
addWeighted(g_sobelAbsGradient_X, 0.5, g_sobelAbsGradient_Y, 0.5, 0, g_dstImage);
//显示效果图
imshow("Sobel边缘检测", g_dstImage);
}
//-----------------------------------【Scharr( )函数】----------------------------------
// 描述:封装了Scharr边缘检测相关代码的函数
//-----------------------------------------------------------------------------------------
void Scharr()
{
// 求 X方向梯度
Scharr(g_srcImage, g_scharrGradient_X, CV_16S, 1, 0, 1, 0, BORDER_DEFAULT);
convertScaleAbs(g_scharrGradient_X, g_scharrAbsGradient_X);//计算绝对值,并将结果转换成8位
// 求Y方向梯度
Scharr(g_srcImage, g_scharrGradient_Y, CV_16S, 0, 1, 1, 0, BORDER_DEFAULT);
convertScaleAbs(g_scharrGradient_Y, g_scharrAbsGradient_Y);//计算绝对值,并将结果转换成8位
// 合并梯度
addWeighted(g_scharrAbsGradient_X, 0.5, g_scharrAbsGradient_Y, 0.5, 0, g_dstImage);
//显示效果图
imshow("Scharr滤波器", g_dstImage);
}
/* OpenCV图像金字塔:高斯金字塔、拉普拉斯金字塔与图片尺寸缩放 */
/*
我们经常会将某种尺寸的图像转换为其他尺寸的图像,如放大或者缩小图片的尺寸。
笼统来说的话,可以使用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执行操作就可产生出整个金字塔。
当图像向金字塔的上层移动时,尺寸和分辨率就降低。
OpenCV中,从金字塔中上一级图像生成下一级图像的可以用PyrDown。
而通过PyrUp将现有的图像在每个维度都放大两遍。
图像金字塔中的向上和向下采样分别通过OpenCV函数pyrUp和pyrDown实现。
概括起来就是:
对图像向上采样:pyrUp函数
对图像向下采样:pyrDown函数
这里的向下与向上采样,是对图像的尺寸而言的(和金字塔的方向相反),
向上就是图像尺寸加倍,向下就是图像尺寸减半。
而如果我们按上图中演示的金字塔方向来理解,金字塔向上图像其实在缩小,这样刚好是反过来了。
但需要注意的是,PryUp和PryDown不是互逆的,即PryUp不是降采样的逆操作。
这种情况下,图像首先在每个维度上扩大为原来的两倍,新增的行(偶数行)以0填充。
然后给指定的滤波器进行卷积(实际上是一个在每个维度都扩大为原来两倍的过滤器)去估计“丢失”像素的近似值。
PyrDown( )是一个会丢失信息的函数。
为了恢复原来更高的分辨率的图像,我们要获得由降采样操作丢失的信息,这些数据就和拉普拉斯金字塔有关系了。
*/
/*
高斯金字塔:
高斯金字塔是通过高斯平滑和亚采样获得一些列下采样图像,
也就是说第K层高斯金字塔通过平滑、亚采样就可以获得K+1层高斯图像,
高斯金字塔包含了一系列低通滤波器,
其截至频率从上一层到下一层是以因子2逐渐增加,
所以高斯金字塔可以跨越很大的频率范围。
另外,
每一层都按从下到上的次序编号,
层级 G_i+1 (表示为 G_i+1尺寸小于第i层G_i)。
对图像的向下取样:
为了获取层级为 G_i+1 的金字塔图像,我们采用如下方法:
<1>对图像G_i进行高斯内核卷积
<2>将所有偶数行和列去除
得到的图像即为G_i+1的图像,显而易见,结果图像只有原图的四分之一。
通过对输入图像G_i(原始图像)不停迭代以上步骤就会得到整个金字塔。
同时我们也可以看到,向下取样会逐渐丢失图像的信息。
以上就是对图像的向下取样操作,即缩小图像。
对图像的向上取样:
如果想放大图像,则需要通过向上取样操作得到,具体做法如下:
<1>将图像在每个方向扩大为原来的两倍,新增的行和列以0填充
<2>使用先前同样的内核(乘以4)与放大后的图像卷积,获得 “新增像素”的近似值
得到的图像即为放大后的图像,但是与原来的图像相比会比较模糊,
因为在缩放的过程中已经丢失了一些信息,
如果想在缩小和放大整个过程中减少信息的丢失,这些数据形成了拉普拉斯金字塔。
拉普拉斯金字塔:
我们可以将拉普拉斯金字塔理解为高斯金字塔的逆形式。
另外再提一点,关于图像金字塔非常重要的一个应用就是实现图像分割。
图像分割的话,
先要建立一个图像金字塔,
然后在G_i和G_i+1的像素直接依照对应的关系,
建立起”父与子“关系。
而快速初始分割可以先在金字塔高层的低分辨率图像上完成,
然后逐层对分割加以优化。
*/
/
/*
resize()函数:
resize( )为OpenCV中专职调整图像大小的函数。
此函数将源图像精确地转换为指定尺寸的目标图像。
如果源图像中设置了ROI(Region Of Interest ,感兴趣区域),
那么resize( )函数会对源图像的ROI区域进行调整图像尺寸的操作,来输出到目标图像中。
若目标图像中已经设置ROI区域,不难理解resize( )将会对源图像进行尺寸调整并填充到目标图像的ROI中。
很多时候,
我们并不用考虑第二个参数dst的初始图像尺寸和类型(即直接定义一个Mat类型,不用对其初始化),
因为其尺寸和类型可以由src,dsize,fx和fy这其他的几个参数来确定。
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 = Size(round(fx*src.cols),round(fy*src.rows))
其中,dsize,fx,fy都不能为0。
第四个参数,double类型的fx,沿水平轴的缩放系数,有默认值0,且当其等于0时,由下式进行计算:
(double)dsize.width/src.cols
第五个参数,double类型的fy,沿垂直轴的缩放系数,有默认值0,且当其等于0时,由下式进行计算:
(double)dsize.height/src.rows
第六个参数,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()函数调用示例:
1)
Mat dst=Mat::zeros(512 ,512, CV_8UC3 );//新建一张512x512尺寸的图片
Mat src=imread(“1.jpg”);
//显式指定dsize=dst.size(),那么fx和fy会其计算出来,不用额外指定。
resize(src, dst, dst.size());
2)
Mat dst;
Mat src=imread(“1.jpg”)
//指定fx和fy,让函数计算出目标图像的大小。
resize(src, dst, Size(), 0.5, 0.5);
*/
//resize
#include
#include
using namespace cv;
int main()
{
//载入原始图
Mat srcImage = imread("logo.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)来进行计算,且一直需要满足下列条件:
|dstsize.width - src.cols*2| ≤ (dstsize.width mod 2)
|dstsize.height - src.rows*2| ≤ (dstsize.height mod 2)
第四个参数,int类型的borderType,又来了,边界模式,一般我们不用去管它。
pyrUp函数执行高斯金字塔的采样操作,其实它也可以用于拉普拉斯金字塔的。
首先,它通过插入可为零的行与列,对源图像进行向上取样操作,
然后将结果与pyrDown()乘以4的内核做卷积,
*/
/*
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)来进行计算,且一直需要满足下列条件:
|dstsize.width - src.cols*2| ≤ 2
|dstsize.height - src.rows*2| ≤ 2
*/
//pyrUp
#include
#include
using namespace cv;
int main()
{
//载入原始图
Mat srcImage = imread("logo.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
#include
#include
using namespace cv;
int main()
{
Mat srcImage = imread("logo.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
#include
#include
#define WINDOW_NAME "【程序窗口】" //为窗口标题定义的宏
using namespace std;
using namespace cv;
Mat g_srcImage, g_dstImage, g_tmpImage;
static void 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"
);
//键入操作要求鼠标焦点在图片上
}
int main()
{
//改变console字体颜色
system("color 2F");
//显示帮助文字
ShowHelpText();
//载入原图
g_srcImage = imread("11.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;
}
/* OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑 */
/*
在图像处理和计算机视觉领域中,
如何从当前的图像中提取所需要的特征信息是图像识别的关键所在。
在许多应用场合中需要快速准确地检测出直线或者圆。
其中一种非常有效的解决问题的方法是霍夫(Hough)变换,
其为图像处理中从图像中识别几何形状的基本方法之一,应用很广泛,也有很多改进算法。
最基本的霍夫变换是从黑白图像中检测直线(线段)。
*/
/
/*
霍夫变换:
霍夫变换(Hough Transform)是图像处理中的一种特征提取技术,
该过程在一个参数空间中通过计算累计结果的局部最大值得到一个符合该特定形状的集合作为霍夫变换的结果。
最初的霍夫变换是设计用来检测直线和曲线,
起初的方法要求知道物体边界线的解析方程(几乎不可能),但不需要有关区域的先验知识。
后来霍夫变换扩展到任意形状物体的识别,多为圆和椭圆。
霍夫变换运用两个坐标空间之间的变换将在一个空间中具有相同形状的曲线或直线映射到另一个坐标空间的一个点上形成峰值,
从而把检测任意形状的问题转化为统计峰值问题。
霍夫变换在OpenCV中分为霍夫线变换和霍夫圆变换两种。
*/
/
/*
霍夫线变换:
霍夫线变换是一种用来寻找直线的方法。
在使用霍夫线变换之前,
首先要对图像进行边缘检测的处理,
也即霍夫线变换的直接输入只能是边缘二值图像。
OpenCV支持三种不同的霍夫线变换,
它们分别是:
标准霍夫变换(Standard Hough Transform,SHT),由HoughLines函数调用。
多尺度霍夫变换(Multi-Scale Hough Transform,MSHT),由HoughLines函数调用。
累计概率霍夫变换(Progressive Probabilistic Hough Transform ,PPHT),由HoughLinesP函数调用。
多尺度霍夫变换(MSHT)为经典霍夫变换(SHT)在多尺度下的一个变种。
累计概率霍夫变换(PPHT)算法是标准霍夫变换(SHT)算法的一个改进,
它在一定的范围内进行霍夫变换,计算单独线段的方向以及范围,
从而减少计算量,缩短计算时间。
之所以称累计概率霍夫变换(PPHT)为“概率”,是因为它并不将累加器平面内的所有可能的点累加,
而只是累加其中的一部分,
该想法是如果峰值如果足够高,只用一小部分时间去寻找它就够了。
这样猜想的话,可以实质性地减少计算时间。
霍夫线变换的原理:
对于霍夫变换, 我们采用极坐标系来表示直线。
直线的表达式可为:r = x * cosθ + y * sinθ
一般来说, 一条直线能够通过在极坐标平面寻找交于一点的曲线数量来检测。
而越多曲线交于一点也就意味着这个交点表示的直线由更多的点组成。
一般来说我们可以通过设置直线上点的阈值来定义多少条曲线交于一点我们才认为检测到了一条直线。
这就是霍夫线变换要做的。
它追踪图像中每个点对应曲线间的交点。
如果交于一点的曲线的数量超过了阈值,
那么可以认为这个交点所代表的参数对在原图像中为一条直线。
*/
/
/*
HoughLines( )函数:
此函数可以找出采用标准霍夫变换的二值图像线条。
在OpenCV中,我们可以用其来调用标准霍夫变换SHT和多尺度霍夫变换MSHT的OpenCV内建算法。
void HoughLines
(
InputArray image,
OutputArray lines,
double rho,
double theta,
int threshold,
double srn = 0,
double stn = 0
)
第一个参数,InputArray类型的image,输入图像,即源图像,需为8位的单通道二进制图像,可以将任意的源图载入进来后由函数修改成此格式后,再填在这里。
第二个参数,InputArray类型的lines,经过调用HoughLines函数后储存了霍夫线变换检测到线条的输出矢量。每一条线由具有两个元素的矢量表示,其中,是离坐标原点((0,0)(也就是图像的左上角)的距离。 是弧度线条旋转角度(0~垂直线,π/2~水平线)。
第三个参数,double类型的rho,以像素为单位的距离精度。另一种形容方式是直线搜索时的进步尺寸的单位半径。PS:Latex中/rho就表示 。
第四个参数,double类型的theta,以弧度为单位的角度精度。另一种形容方式是直线搜索时的进步尺寸的单位角度。
第五个参数,int类型的threshold,累加平面的阈值参数,即识别某部分为图中的一条直线时它在累加平面中必须达到的值。大于阈值threshold的线段才可以被检测通过并返回到结果中。
第六个参数,double类型的srn,有默认值0。对于多尺度的霍夫变换,这是第三个参数进步尺寸rho的除数距离。粗略的累加器进步尺寸直接是第三个参数rho,而精确的累加器进步尺寸为rho/srn。
第七个参数,double类型的stn,有默认值0,对于多尺度霍夫变换,srn表示第四个参数进步尺寸的单位角度theta的除数距离。且如果srn和stn同时为0,就表示使用经典的霍夫变换。否则,这两个参数应该都为正数。
PS:
可以通过调节line(dstImage, pt1, pt2, Scalar(55,100,195), 1, CV_AA);
Scalar(55,100,195)参数中G、B、R颜色值的数值,得到图中想要的线条颜色。
*/
//HoughLines
#include
#include
using namespace cv;
using namespace std;
int main()
{
//【1】载入原始图和Mat变量定义
Mat srcImage = imread("house.jpg");
Mat midImage, dstImage;//临时变量和目标图的定义
//【2】进行边缘检测和转化为灰度图
Canny(srcImage, midImage, 50, 200, 3);//进行一次canny边缘检测
cvtColor(midImage, dstImage, CV_GRAY2BGR);//转化边缘检测后的图为灰度图
//【3】进行霍夫线变换
vector<Vec2f> lines;//定义一个矢量结构lines用于存放得到的线段矢量集合
HoughLines(midImage, lines, 1, CV_PI / 180, 150, 0, 0);
//【4】依次在图中绘制出每条线段
for (size_t i = 0; i < lines.size(); i++)
{
float rho = lines[i][0], theta = lines[i][1];
Point pt1, pt2;
double a = cos(theta), b = sin(theta);
double x0 = a*rho, y0 = b*rho;
pt1.x = cvRound(x0 + 1000 * (-b));
pt1.y = cvRound(y0 + 1000 * (a));
pt2.x = cvRound(x0 - 1000 * (-b));
pt2.y = cvRound(y0 - 1000 * (a));
line(dstImage, pt1, pt2, Scalar(55, 100, 195), 1, CV_AA);
}
//【5】显示原始图
imshow("【原始图】", srcImage);
//【6】边缘检测后的图
imshow("【边缘检测后的图】", midImage);
//【7】显示效果图
imshow("【效果图】", dstImage);
waitKey(0);
return 0;
}
/*
HoughLinesP( )函数:
此函数在HoughLines的基础上末尾加了一个代表Probabilistic(概率)的P,
表明它可以采用累计概率霍夫变换(PPHT)来找出二值图像中的直线。
void HoughLinesP
(
InputArray image,
OutputArray lines,
double rho,
double theta,
int threshold,
double minLineLength = 0,
double maxLineGap = 0
)
第一个参数,InputArray类型的image,输入图像,即源图像,需为8位的单通道二进制图像,可以将任意的源图载入进来后由函数修改成此格式后,再填在这里。
第二个参数,InputArray类型的lines,经过调用HoughLinesP函数后后存储了检测到的线条的输出矢量,每一条线由具有四个元素的矢量(x_1,y_1, x_2, y_2) 表示,其中,(x_1, y_1)和(x_2, y_2) 是是每个检测到的线段的结束点。
第三个参数,double类型的rho,以像素为单位的距离精度。另一种形容方式是直线搜索时的进步尺寸的单位半径。
第四个参数,double类型的theta,以弧度为单位的角度精度。另一种形容方式是直线搜索时的进步尺寸的单位角度。
第五个参数,int类型的threshold,累加平面的阈值参数,即识别某部分为图中的一条直线时它在累加平面中必须达到的值。大于阈值threshold的线段才可以被检测通过并返回到结果中。
第六个参数,double类型的minLineLength,有默认值0,表示最低线段的长度,比这个设定参数短的线段就不能被显现出来。
第七个参数,double类型的maxLineGap,有默认值0,允许将同一行点与点之间连接起来的最大的距离。
*/
//HoughLinesP
#include
#include
using namespace cv;
using namespace std;
int main()
{
//【1】载入原始图和Mat变量定义
Mat srcImage = imread("house.jpg"); //工程目录下应该有一张名为1.jpg的素材图
Mat midImage, dstImage;//临时变量和目标图的定义
//【2】进行边缘检测和转化为灰度图
Canny(srcImage, midImage, 50, 200, 3);//进行一此canny边缘检测
cvtColor(midImage, dstImage, CV_GRAY2BGR);//转化边缘检测后的图为灰度图
//【3】进行霍夫线变换
vector<Vec4i> lines;//定义一个矢量结构lines用于存放得到的线段矢量集合
HoughLinesP(midImage, lines, 1, CV_PI / 180, 80, 50, 10);
//【4】依次在图中绘制出每条线段
for (size_t i = 0; i < lines.size(); i++)
{
Vec4i l = lines[i];
line(dstImage, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(186, 88, 255), 1, CV_AA);
}
//【5】显示原始图
imshow("【原始图】", srcImage);
//【6】边缘检测后的图
imshow("【边缘检测后的图】", midImage);
//【7】显示效果图
imshow("【效果图】", dstImage);
waitKey(0);
return 0;
}
/*
霍夫圆变换:
霍夫圆变换的基本原理和上面讲的霍夫线变化大体上是很类似的,
只是点对应的二维极径极角空间被三维的圆心点x, y还有半径r空间取代。
说“大体上类似”的原因是,如果完全用相同的方法的话,累加平面会被三维的累加容器所代替:
在这三维中,一维是x,一维是y,另外一维是圆的半径r。
这就意味着需要大量的内存而且执行效率会很低,速度会很慢。
对直线来说, 一条直线能由参数极径极角(r,θ)表示。
而对圆来说, 我们需要三个参数来表示一个圆, 也就是:C:(x,y,r);
这里的(x,y)表示圆心的位置,r 表示半径, 这样我们就能唯一的定义一个圆了。
在OpenCV中,我们一般通过一个叫做“霍夫梯度法”的方法来解决圆变换的问题。
*/
/
/*
霍夫梯度法:
霍夫梯度法的原理:
【1】首先对图像应用边缘检测,比如用canny边缘检测。
【2】然后,对边缘图像中的每一个非零点,考虑其局部梯度,即用Sobel()函数计算x和y方向的Sobel一阶导数得到梯度。
【3】利用得到的梯度,由斜率指定的直线上的每一个点都在累加器中被累加,这里的斜率是从一个指定的最小值到指定的最大值的距离。
【4】同时,标记边缘图像中每一个非0像素的位置。
【5】然后从二维累加器中这些点中选择候选的中心,这些中心都大于给定阈值并且大于其所有近邻。这些候选的中心按照累加值降序排列,以便于最支持像素的中心首先出现。
【6】接下来对每一个中心,考虑所有的非0像素。
【7】这些像素按照其与中心的距离排序。从到最大半径的最小距离算起,选择非0像素最支持的一条半径。8.如果一个中心收到边缘图像非0像素最充分的支持,并且到前期被选择的中心有足够的距离,那么它就会被保留下来。
这个实现可以使算法执行起来更高效,
或许更加重要的是,
能够帮助解决三维累加器中会产生许多噪声并且使得结果不稳定的稀疏分布问题。
霍夫梯度法的缺点:
<1>
在霍夫梯度法中,我们使用Sobel导数来计算局部梯度,
那么随之而来的假设是,其可以视作等同于一条局部切线,并这个不是一个数值稳定的做法。
在大多数情况下,这样做会得到正确的结果,但或许会在输出中产生一些噪声。
<2>
在边缘图像中的整个非0像素集被看做每个中心的候选部分。
因此,如果把累加器的阈值设置偏低,算法将要消耗比较长的时间。
第三,因为每一个中心只选择一个圆,如果有同心圆,就只能选择其中的一个。
<3>
因为中心是按照其关联的累加器值的升序排列的,
并且如果新的中心过于接近之前已经接受的中心的话,
就不会被保留下来。
且当有许多同心圆或者是近似的同心圆时,
霍夫梯度法的倾向是保留最大的一个圆。
可以说这是一种比较极端的做法,因为在这里默认Sobel导数会产生噪声,
若是对于无穷分辨率的平滑图像而言的话,这才是必须的。
*/
/
/*
HoughCircles函数:
HoughCircles函数可以利用霍夫变换算法检测出灰度图中的圆。
它和之前的HoughLines和HoughLinesP比较明显的一个区别是它不需要源图是二值的,
而HoughLines和HoughLinesP都需要源图为二值图像。
void HoughCircles
(
InputArray image,
OutputArray circles,
int method,
double dp,
double minDist,
double param1 = 100,
double param2 = 100,
int minRadius = 0,
int maxRadius=0
)
第一个参数,InputArray类型的image,输入图像,即源图像,需为8位的灰度单通道图像。
第二个参数,InputArray类型的circles,
经过调用HoughCircles函数后此参数存储了检测到的圆的输出矢量,
每个矢量由包含了3个元素的浮点矢量(x, y, radius)表示。
第三个参数,int类型的method,即使用的检测方法,
目前OpenCV中就霍夫梯度法一种可以使用,
它的标识符为CV_HOUGH_GRADIENT,在此参数处填这个标识符即可。
第四个参数,double类型的dp,
用来检测圆心的累加器图像的分辨率于输入图像之比的倒数,
且此参数允许创建一个比输入图像分辨率低的累加器。
例如,如果dp= 1时,累加器和输入图像具有相同的分辨率。
如果dp=2,累加器便有输入图像一半那么大的宽度和高度。
第五个参数,double类型的minDist,
为霍夫变换检测到的圆的圆心之间的最小距离,
即让我们的算法能明显区分的两个不同圆之间的最小距离。
这个参数如果太小的话,多个相邻的圆可能被错误地检测成了一个重合的圆。
反之,这个参数设置太大的话,某些圆就不能被检测出来了。
第六个参数,double类型的param1,有默认值100。
它是第三个参数method设置的检测方法的对应的参数。
对当前唯一的方法霍夫梯度法CV_HOUGH_GRADIENT,
它表示传递给canny边缘检测算子的高阈值,
而低阈值为高阈值的一半。
第七个参数,double类型的param2,也有默认值100。
它是第三个参数method设置的检测方法的对应的参数。
对当前唯一的方法霍夫梯度法CV_HOUGH_GRADIENT,
它表示在检测阶段圆心的累加器阈值。
它越小的话,就可以检测到更多根本不存在的圆,
而它越大的话,能通过检测的圆就更加接近完美的圆形了。
第八个参数,int类型的minRadius,有默认值0,表示圆半径的最小值。
第九个参数,int类型的maxRadius,也有默认值0,表示圆半径的最大值。
需要注意的是,使用此函数可以很容易地检测出圆的圆心,但是它可能找不到合适的圆半径。
我们可以通过第八个参数minRadius和第九个参数maxRadius指定最小和最大的圆半径,来辅助圆检测的效果。
或者,我们可以直接忽略返回半径,
因为它们都有着默认值0,
单单用HoughCircles函数检测出来的圆心,
然后用额外的一些步骤来进一步确定半径。
*/
//HoughCircles
#include
#include
using namespace cv;
using namespace std;
int main()
{
//【1】载入原始图和Mat变量定义
Mat srcImage = imread("circle.jpg");
Mat midImage, dstImage;//临时变量和目标图的定义
//【2】显示原始图
imshow("【原始图】", srcImage);
//【3】转为灰度图,进行图像平滑
cvtColor(srcImage, midImage, CV_BGR2GRAY);//转化边缘检测后的图为灰度图
GaussianBlur(midImage, midImage, Size(9, 9), 2, 2);
//【4】进行霍夫圆变换
vector<Vec3f> circles;
HoughCircles(midImage, circles, CV_HOUGH_GRADIENT, 1.5, 10, 200, 100, 0, 0);
//【5】依次在图中绘制出圆
for (size_t i = 0; i < circles.size(); i++)
{
Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
int radius = cvRound(circles[i][2]);
//绘制圆心
circle(srcImage, center, 3, Scalar(0, 255, 0), -1, 8, 0);
//绘制圆轮廓
circle(srcImage, center, radius, Scalar(155, 50, 255), 3, 8, 0);
}
//【6】显示效果图
imshow("【效果图】", srcImage);
waitKey(0);
return 0;
}
/*
漫水填充:
漫水填充法是一种用特定的颜色填充联通区域,
通过设置可连通像素的上下限以及连通方式来达到不同的填充效果的方法。
漫水填充经常被用来标记或分离图像的一部分以便对其进行进一步处理或分析,
也可以用来从输入图像获取掩码区域,
掩码会加速处理过程,或只处理掩码指定的像素点,
操作的结果总是某个连续的区域。
所谓漫水填充,
就是自动选中了和种子点相连的区域,
接着将该区域替换成指定的颜色,
这是个非常有用的功能,
经常用来标记或者分离图像的一部分进行处理或分析。
漫水填充也可以用来从输入图像获取掩码区域,掩码会加速处理过程,
或者只处理掩码指定的像素点。
以此填充算法为基础,类似photoshop的魔术棒选择工具就很容易实现了。
漫水填充(FloodFill)是查找和种子点联通的颜色相同的点,
魔术棒选择工具则是查找和种子点联通的颜色相近的点,
将和初始种子像素颜色相近的点压进栈作为新种子。
在OpenCV中,漫水填充是填充算法中最通用的方法。
且在OpenCV 2.X中,使用C++重写过的FloodFill函数有两个版本。
一个不带掩膜mask的版本,和一个带mask的版本。
这个掩膜mask,就是用于进一步控制哪些区域将被填充颜色(比如说当对同一图像进行多次填充时)。
这两个版本的FloodFill,都必须在图像中选择一个种子点,
然后把临近区域所有相似点填充上同样的颜色,
不同的是,不一定将所有的邻近像素点都染上同一颜色,
漫水填充操作的结果总是某个连续的区域。
当邻近像素点位于给定的范围(从loDiff到upDiff)内或在原始seedPoint像素值范围内时,
FloodFill函数就会为这个点涂上颜色。
*/
/
/*
floodFill函数:
在OpenCV中,漫水填充算法由floodFill函数实现,
其作用是用我们指定的颜色从种子点开始填充一个连接域。
连通性由像素值的接近程度来衡量。
OpenCV2.X有两个C++重写版本的floodFill。
1)
int floodFill
(
InputOutputArray image,
Point seedPoint,
Scalar newVal,
Rect* rect = 0,
Scalar loDiff = Scalar(),
Scalar upDiff = Scalar(),
int flags = 4
)
2)
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
#include
using namespace cv;
int main()
{
Mat src = imread("fill.png");
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函数:
SetMouseCallback函数为指定的窗口设置鼠标回调函数。
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。
*/
//实例
//-----------------------------------【程序说明】----------------------------------------------
// 程序名称::《【OpenCV入门教程之十五】水漫金山:OpenCV漫水填充算法(Floodfill)》 博文配套源码
// 开发所用IDE版本:Visual Studio 2010
// 开发所用OpenCV版本: 2.4.9
// 2014年6月3日 Created by 浅墨
// 浅墨的微博:@浅墨_毛星云 http://weibo.com/1723155442/profile?topnav=1&wvr=5&user=1
// 浅墨的知乎:http://www.zhihu.com/people/mao-xing-yun
// 浅墨的豆瓣:http://www.douban.com/people/53426472/
//----------------------------------------------------------------------------------------------
//-----------------------------------【头文件包含部分】---------------------------------------
// 描述:包含程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include
//-----------------------------------【命名空间声明部分】---------------------------------------
// 描述:包含程序所使用的命名空间
//-----------------------------------------------------------------------------------------------
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;
}