OpenCV2.4.10之samples_cpp_tutorial-code_learn-ImgProc(图像处理)
OpenCV2.4.10之samples_cpp_tutorial-code_learn-----ImgProc(图像处理)
当我们alpha分别取0,0.5,1的时候,运行结果如下图所示:




运行截图如下:

可以看出at成员函数为一个内联的模板函数。另外Vec3b为一个类模板特例化的后的类型定义,代表一个三个元素的向量或者数组:
此外saturate_cast为一个重载的函数模板,用于像素值的溢出保护。可参考点击打开链接:
3.Morphology_1.cpp(图像的膨胀与腐蚀)

首先介绍函数createTrackbar,其函数声明:C++: int createTrackbar(const string& trackbarname, const string& winname, int* value, int count, TrackbarCallback onChange=0, void*userdata=0)


本系列学习笔记参考自OpenCV2.4.10之opencv\sources\samples\cpp\tutorial_code和http://www.opencv.org.cn/opencvdoc/2.3.2/html/genindex.html
本博文将继续学习OpenCV2.4.10中tutorial-code下的ImgProc,还有对于涉及到的知识才进行解释,如果没有涉及到相关知识,不进行系列性的介绍。顾名思义该文件夹介绍的图像处理相关的Demo,该文件夹下包括以下7个例子:
1.AddingImages.cpp(图像线性叠加)
该Demo的源码及相关的中文如下所示:
#include "stdafx.h" //预编译头文件 /** * @图像简单线性叠加 ( 叠加公式:dst = alpha*src1 + beta*src2,其中src1和src2为读取的需要叠加的图像,alpha和 beta分别为src1和src2两张图像的权重,且alpha+ beta=1,dst为叠加合成后的图像) */ #include "opencv2/highgui/highgui.hpp" #include <iostream> using namespace cv; int main( void ) { double alpha = 0.5; double beta; double input; Mat src1, src2, dst; /// 询问并输入第一张图像的权重 std::cout<<" Simple Linear Blender "<<std::endl; std::cout<<"-----------------------"<<std::endl; std::cout<<"* Enter alpha [0-1]: "; std::cin>>input; // 权重alpha必须为[0,1],否则错误 if( alpha >= 0 && alpha <= 1 ) { alpha = input; } // 读取两张需要进行叠加的图像 (注意,需要叠加的图像是相同大小像素点,同时类型也相同) // imread第二个参数默认为1,读取为3通道彩色图像 src1 = imread("D:\\opencv\\sources\\samples\\cpp\\tutorial_code\\images\\LinuxLogo.jpg"); src2 = imread("D:\\opencv\\sources\\samples\\cpp\\tutorial_code\\images\\WindowsLogo.jpg"); //对读取结果判断 if( !src1.data ) { std::cout<< "Error loading src1"<<std::endl; return -1; } if( !src2.data ) { std::cout<< "Error loading src2"<<std::endl; return -1; } /// 创建窗口 namedWindow("Linear Blend", 1); //计算第二张图像的权重 beta = ( 1.0 - alpha ); //线性叠加函数 addWeighted( src1, alpha, src2, beta, 0.0, dst); //显示图像 imshow( "Linear Blend", dst ); waitKey(0); return 0; }
不难看出,根据叠加公式dst = alpha*src1 + beta*src2(alpha+beta=1);当alpha=0时,beta=1,dst
=src2(显示WindowsLogo.jpg),当alpha=1时,beta=0,dst =src1(显示LinuxLogo.jpg)。当alpha=0.5时,beta=0.5,dst
=0.5*src1+0.5*src2(显示叠加效果图片)。(gramma=0,前提)
下面我们着重看看addWeighted,addWeighted的函数声明为:C++: void addWeighted(InputArray src1,
double alpha, InputArray src2,
double beta, double gamma,
OutputArray dst, int dtype=-1)
其中src1为第一张图像数据,src为第二章图像数据,alpha和beta分别为两张图像的叠加权重,gamma为常数项。dst为合成后的图像,dtype为合成后的图像的位深,默认值为-1。(关于位深:可以理解为每个像素值所表示所需的位数,比如8位,16位等,位数越高表示的颜色越多,当然位数越高存储空间和计算量都会大增。)
实际上addWeighted内部实现原理公式为:
2.BasicLinearTransforms.cpp(图像基本的线性变换)
源码及详细注释如下:
#include "stdafx.h" //预编译头文件 /** * 改变图像的亮度和对比度 */ #include "opencv2/highgui/highgui.hpp" #include <iostream> using namespace cv; double alpha; /*图像对比度*/ int beta; /*图像亮度*/ int main( int, char** argv ) { /// 读取图像 Mat image = imread("D:\\opencv\\lena.png"); Mat new_image = Mat::zeros( image.size(), image.type() ); /// 初始化对比度和亮度,其中对比度在[1.0-3.0],亮度在[0-100] std::cout<<" Basic Linear Transforms "<<std::endl; std::cout<<"-------------------------"<<std::endl; std::cout<<"* Enter the alpha value [1.0-3.0]: ";std::cin>>alpha; std::cout<<"* Enter the beta value [0-100]: "; std::cin>>beta; ///新的图像公式为 new_image(i,j) = alpha*image(i,j) + beta //我们可以使用简单的成员函数进行对比度和亮度的改变如: /// image.convertTo(new_image, -1, alpha, beta); /// 但是这里想为读者展示如果在像素级别进行操作 :) for( int y = 0; y < image.rows; y++ ) { for( int x = 0; x < image.cols; x++ ) { for( int c = 0; c < 3; c++ ) { new_image.at<Vec3b>(y,x)[c] = saturate_cast<uchar>( alpha*( image.at<Vec3b>(y,x)[c] ) + beta ); } } } /// 创建窗口 namedWindow("Original Image", 1); namedWindow("New Image", 1); /// 显示图像 imshow("Original Image", image); imshow("New Image", new_image); /// Wait until user press some key waitKey(); return 0; }
运行截图如下:
这里需要对如下三重循环进行解释:
for( int y = 0; y < image.rows; y++ ) { for( int x = 0; x < image.cols; x++ ) { for( int c = 0; c < 3; c++ ) { new_image.at<Vec3b>(y,x)[c] = saturate_cast<uchar>( alpha*( image.at<Vec3b>(y,x)[c] ) + beta ); } } }
图像是由无数个像素点组成的,这些像素点是由一行一行进行存储的,这样外层两个for循环就不难理解了,其作用是遍历整张图像的像素点。第三层循环的意思是对每个像素点的三个通道值进行遍历,因为我们读入的是三通道彩色图像,每个像素点的颜色值由红,滤,蓝三原色合成。所以要改变三通道图像的一个像素值就需要对该像素的三个通道的值都进行改变。
另外要解释一下new_image.at<Vec3b>(y,x)[c],new_image为Mat类型,at为mat类型的成员函数,其函数原型如下:
template<typename _Tp> inline _Tp& Mat::at(int i0, int i1)
可以看出at成员函数为一个内联的模板函数。另外Vec3b为一个类模板特例化的后的类型定义,代表一个三个元素的向量或者数组:
typedef Vec<uchar, 3> Vec3b;
template<typename _Tp, int cn> class Vec : public Matx<_Tp, cn, 1>所以new_image.at<Vec3b>(y,x)返回的将是一个Vec3b类型的向量,这个向量中有三个值(代表三个通道颜色),所以要遍历这三个值需要用数组的方式进行索引new_image.at<Vec3b>(y,x)[c]。
此外saturate_cast为一个重载的函数模板,用于像素值的溢出保护。可参考点击打开链接:
3.Morphology_1.cpp(图像的膨胀与腐蚀)
#include "stdafx.h" //预编译头文件 /** * @图像膨胀与腐蚀Demo */ #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" #include <stdlib.h> #include <stdio.h> using namespace cv; ///全局变量 Mat src, erosion_dst, dilation_dst; int erosion_elem = 0; int erosion_size = 0; int dilation_elem = 0; int dilation_size = 0; int const max_elem = 2; int const max_kernel_size = 21; /** 膨胀腐蚀函数声明 */ void Erosion( int, void* ); void Dilation( int, void* ); /** 主函数 */ int main( int, char** argv ) { ///加载图片 src = imread("D:\\opencv\\lena.png"); if( !src.data ) { return -1; } /// 创建窗口 namedWindow( "Erosion Demo", WINDOW_AUTOSIZE ); namedWindow( "Dilation Demo", WINDOW_AUTOSIZE ); moveWindow( "Dilation Demo", src.cols, 0 ); /// 创建腐蚀的滑动条 createTrackbar( "Element:\n 0: Rect \n 1: Cross \n 2: Ellipse", "Erosion Demo", &erosion_elem, max_elem, Erosion ); createTrackbar( "Kernel size:\n 2n +1", "Erosion Demo", &erosion_size, max_kernel_size, Erosion ); /// 创建膨胀的滑动条 createTrackbar( "Element:\n 0: Rect \n 1: Cross \n 2: Ellipse", "Dilation Demo", &dilation_elem, max_elem, Dilation ); createTrackbar( "Kernel size:\n 2n +1", "Dilation Demo", &dilation_size, max_kernel_size, Dilation ); /// 开始 Erosion( 0, 0 ); Dilation( 0, 0 ); waitKey(0); return 0; } /** * @腐蚀函数 */ void Erosion( int, void* ) { int erosion_type = 0; if( erosion_elem == 0 ){ erosion_type = MORPH_RECT; } else if( erosion_elem == 1 ){ erosion_type = MORPH_CROSS; } else if( erosion_elem == 2) { erosion_type = MORPH_ELLIPSE; } Mat element = getStructuringElement( erosion_type, Size( 2*erosion_size + 1, 2*erosion_size+1 ), Point( erosion_size, erosion_size ) ); /// 进行腐蚀操作 erode( src, erosion_dst, element ); imshow( "Erosion Demo", erosion_dst ); } /** * @膨胀函数 */ void Dilation( int, void* ) { int dilation_type = 0; if( dilation_elem == 0 ){ dilation_type = MORPH_RECT; } else if( dilation_elem == 1 ){ dilation_type = MORPH_CROSS; } else if( dilation_elem == 2) { dilation_type = MORPH_ELLIPSE; } Mat element = getStructuringElement( dilation_type, Size( 2*dilation_size + 1, 2*dilation_size+1 ), Point( dilation_size, dilation_size ) ); /// 进行膨胀操作 dilate( src, dilation_dst, element ); imshow( "Dilation Demo", dilation_dst ); }
运行结果如下:
首先介绍函数createTrackbar,其函数声明:C++: int createTrackbar(const string& trackbarname, const string& winname, int* value, int count, TrackbarCallback onChange=0, void*userdata=0)
trackbarname为滑动条名称,winname为滑动条所在窗口的窗口名称,value为一个整型指针,指向滑块位置,count为滑块位置最大位置,最小值默认为0. onChange位一个回调函数指针,滑块位置一旦改变将会调用该回调函数,回调函数的函数原型必须为void Foo(int,void*),其中Foo为自定义的回调函数名。userdata为用户数据传递给回调函数,使用该值可不比使用全局变量进行值传递。
接下来介绍getStructuringElement,函数原型为:C++: Mat getStructuringElement(int shape,
Size ksize, Point anchor=Point(-1,-1))该函数的功能是生产一个特定形状和大小的图像元素进行图像形态学操作。shape有三个值分别为:
MORPH_RECT(矩形元素),MORPH_ELLIPSE(圆形元素)等等。ksize为矩形元素大小,anchor仅仅当shape为MORPH_CROSS时才有效。
最后erode函数和dilate函数,两个函数声明分别为:C++: void erode(InputArray src,
OutputArray dst, InputArray element,
Point anchor=Point(-1,-1), int iterations=1,
intborderType=BORDER_CONSTANT, const Scalar& borderValue=morphologyDefaultBorderValue() )¶
C++: void dilate(InputArray src,
OutputArray dst, InputArray element,
Point anchor=Point(-1,-1), int iterations=1,
intborderType=BORDER_CONSTANT, const Scalar& borderValue=morphologyDefaultBorderValue() )
主要参数:src为源图像,dst为形态学操作后的结果图像,element为图像形态学元素单元。anchor默认下为中心位置,iteration为操作的次数
接下来介绍下erode(腐蚀)和dilate(膨胀)的简单原理。
erode:
根据公式可以看出腐蚀便是找出src图像像素点邻域内的最小值来代替该点的像素值。
dilate:
根据公式可以看出膨胀便是找出src图像像素点邻域内的最大值来代替该点的像素值。
4.Morphology_2.cpp(高级的图像形态学操作)
源码及注释如下:
#include "stdafx.h" //预编译头文件 /** 高级的形态学变换 */ #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" #include <stdlib.h> #include <stdio.h> using namespace cv; /// 全局变量 Mat src, dst; int morph_elem = 0; int morph_size = 0; int morph_operator = 0; int const max_operator = 4; int const max_elem = 2; int const max_kernel_size = 21; const char* window_name = "Morphology Transformations Demo"; /** 自定义形态学操作函数声明 */ void Morphology_Operations( int, void* ); /** * @主函数 */ int main( int, char** argv ) { /// 加载图片 src = imread("D:\\opencv\\lena.png"); if( !src.data ) { return -1; } /// 创建窗口 namedWindow( window_name, WINDOW_AUTOSIZE ); /// 创建选择形态学操作类型的滑动条 createTrackbar("Operator:\n 0: Opening - 1: Closing \n 2: Gradient - 3: Top Hat \n 4: Black Hat", window_name, &morph_operator, max_operator, Morphology_Operations ); /// 创建形态学元素类型选择的滑动条 createTrackbar( "Element:\n 0: Rect - 1: Cross - 2: Ellipse", window_name, &morph_elem, max_elem, Morphology_Operations ); /// 创建形态学元素大小选择的滑动条 createTrackbar( "Kernel size:\n 2n +1", window_name, &morph_size, max_kernel_size, Morphology_Operations ); /// 开始 Morphology_Operations( 0, 0 ); waitKey(0); return 0; } /** * @形态学操作函数定义 */ void Morphology_Operations( int, void* ) { // Since MORPH_X : 2,3,4,5 and 6 int operation = morph_operator + 2; //创建形态学操作元素 Mat element = getStructuringElement( morph_elem, Size( 2*morph_size + 1, 2*morph_size+1 ), Point( morph_size, morph_size ) ); /// 形态学操作 morphologyEx( src, dst, operation, element ); imshow( window_name, dst ); }运行截图:
这里主要解释函数morphologyEx:C++: void morphologyEx(InputArray src,
OutputArray dst, int op,
InputArray element, Point anchor=Point(-1,-1),
int iterations=1, intborderType=BORDER_CONSTANT,
const Scalar& borderValue=morphologyDefaultBorderValue() )
这里的参数很多与之前的erode和dilate类似,这里我们主要介绍参数op,op代表要进行的形态学操作,主要包括以下几种:

运行截图如下:



6.Pyramids.cpp(图像金字塔)
- MORPH_OPEN - an opening operation
- MORPH_CLOSE - a closing operation
- MORPH_GRADIENT - a morphological gradient
- MORPH_TOPHAT - “top hat”
- MORPH_BLACKHAT - “black hat”
具体的实现如下图:
比如Opening operation是对src先腐蚀后膨胀的结果,而闭操作是对src先膨胀后腐蚀的结果。其他的操作读者可以自行去实验。
5.Smoothing.cpp(图像平滑(模糊))
源码及注释如下:
#include "stdafx.h" //预编译头文件 /** 图像的模糊 */ #include <iostream> #include <vector> #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" #include "opencv2/features2d/features2d.hpp" using namespace std; using namespace cv; /// 全局变量 int DELAY_CAPTION = 1500; int DELAY_BLUR = 100; int MAX_KERNEL_LENGTH = 31; Mat src; Mat dst; char window_name[] = "Smoothing Demo"; /// 函数声明 int display_caption( const char* caption ); int display_dst( int delay ); /** * 主函数 */ int main( void ) { namedWindow( window_name, WINDOW_AUTOSIZE ); /// 加载图像 src = imread( "D:\\opencv\\lena.png", 1 ); if( display_caption( "Original Image" ) != 0 ) { return 0; } dst = src.clone(); if( display_dst( DELAY_CAPTION ) != 0 ) { return 0; } /// 均匀线性模糊 if( display_caption( "Homogeneous Blur" ) != 0 ) { return 0; } for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 ) { blur( src, dst, Size( i, i ), Point(-1,-1) ); if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } } /// 高斯模糊 if( display_caption( "Gaussian Blur" ) != 0 ) { return 0; } for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 ) { GaussianBlur( src, dst, Size( i, i ), 0, 0 ); if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } } /// 中值模糊 if( display_caption( "Median Blur" ) != 0 ) { return 0; } for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 ) { medianBlur ( src, dst, i ); if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } } /// 双边模糊 if( display_caption( "Bilateral Blur" ) != 0 ) { return 0; } for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 ) { bilateralFilter ( src, dst, i, i*2, i/2 ); if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } } display_caption( "End: Press a key!" ); waitKey(0); return 0; } /** * @显示标题 */ int display_caption( const char* caption ) { dst = Mat::zeros( src.size(), src.type() ); putText( dst, caption, Point( src.cols/4, src.rows/2), FONT_HERSHEY_COMPLEX, 1, Scalar(255, 255, 255) ); imshow( window_name, dst ); int c = waitKey( DELAY_CAPTION ); if( c >= 0 ) { return -1; } return 0; } /** * @显示图像 */ int display_dst( int delay ) { imshow( window_name, dst ); int c = waitKey ( delay ); if( c >= 0 ) { return -1; } return 0; }
运行截图如下:
函数blur原型为:C++: void blur(InputArray src,
OutputArray dst, Size ksize,
Point anchor=Point(-1,-1), int borderType=BORDER_DEFAULT )
这里主要介绍ksize,ksize代表平滑核元素的大小。该元素为一个矩形像素。其平滑公式为:
关于GaussianBlur,medianBlur和bilateralFilter请读者自行查看:点击打开链接
函数putText原型为:C++: void putText(Mat& img,
const string& text, Point org,
int fontFace, double fontScale,
Scalar color, int thickness=1,
int lineType=8, bool bottomLeftOrigin=false )用于在dst图像中生成字符串,参数说明请读者自行参考文档
5.Threshold.cpp(图像阈值)
Demo源码及注释如下:
#include "stdafx.h" //预编译头文件 /** * 简单的示例代码显示了如何使用不同的阈值 */ #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" #include <stdlib.h> #include <stdio.h> using namespace cv; /// 全局变量 int threshold_value = 0; int threshold_type = 3; int const max_value = 255; int const max_type = 4; int const max_BINARY_value = 255; Mat src, src_gray, dst; const char* window_name = "Threshold Demo"; const char* trackbar_type = "Type: \n 0: Binary \n 1: Binary Inverted \n 2: Truncate \n 3: To Zero \n 4: To Zero Inverted"; const char* trackbar_value = "Value"; /// 函数声明 void Threshold_Demo( int, void* ); /** * 主函数 */ int main( int, char** argv ) { /// 加载图片 src = imread("D:\\opencv\\lena.png", 1 ); ///将图像转换为灰度图 cvtColor( src, src_gray, COLOR_RGB2GRAY ); /// 创建窗口 namedWindow( window_name, WINDOW_AUTOSIZE ); /// 创建阈值类型选择的滑动块 createTrackbar( trackbar_type, window_name, &threshold_type, max_type, Threshold_Demo ); createTrackbar( trackbar_value, window_name, &threshold_value, max_value, Threshold_Demo ); /// 调用阈值函数 Threshold_Demo( 0, 0 ); /// Wait until user finishes program for(;;) { int c; c = waitKey( 20 ); if( (char)c == 27 ) { break; } } } /** *阈值函数定义 */ void Threshold_Demo( int, void* ) { /* 0: Binary 1: Binary Inverted 2: Threshold Truncated 3: Threshold to Zero 4: Threshold to Zero Inverted */ threshold( src_gray, dst, threshold_value, max_BINARY_value,threshold_type ); imshow( window_name, dst ); }运行截图:
函数threshold原型为:C++: double threshold(InputArray src,
OutputArray dst, double thresh,
double maxVal, int thresholdType)¶第一个参数为源图像,第二个参数dst为进行阈值转换后的图像,第三个参数thresh为阈值大小,第五个参数thresholdType为阈值类型。
阈值类型及原理如下图所示:
源码及相关注释:
#include "stdafx.h" //预编译头文件 /** 图像金字塔 */ #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" #include <math.h> #include <stdlib.h> #include <stdio.h> using namespace cv; /// 全局变量 Mat src, dst, tmp; const char* window_name = "Pyramids Demo"; /** 主函数 */ int main( void ) { printf( "\n Zoom In-Out demo \n " ); printf( "------------------ \n" ); printf( " * [u] -> Zoom in \n" ); printf( " * [d] -> Zoom out \n" ); printf( " * [ESC] -> Close program \n \n" ); /// Test image - Make sure it s divisible by 2^{n} src = imread( "D:\\opencv\\chicky_512.png" ); if( !src.data ) { printf(" No data! -- Exiting the program \n"); return -1; } tmp = src; dst = tmp; /// 创建窗口 namedWindow( window_name, WINDOW_AUTOSIZE ); imshow( window_name, dst ); /// Loop for(;;) { int c; c = waitKey(1000); if( (char)c == 27 ) { break; } if( (char)c == 'u' ) { pyrUp( tmp, dst, Size( tmp.cols*2, tmp.rows*2 ) ); printf( "** Zoom In: Image x 2 \n" ); } else if( (char)c == 'd' ) { pyrDown( tmp, dst, Size( tmp.cols/2, tmp.rows/2 ) ); printf( "** Zoom Out: Image / 2 \n" ); } imshow( window_name, dst ); tmp = dst; } return 0; }运行截图如下:
pyrUp函数原型:C++: void pyrUp(InputArray src,
OutputArray dst, const Size& dstsize=Size())
pyrDown函数原型:C++: void pyrDown(InputArray src,
OutputArray dst, const Size& dstsize=Size())
OK,ImgProc(图像处理)就介绍到此。