这里需要对如下三重循环进行解释:
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, int
borderType
=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代表要进行的形态学操作,主要包括以下几种:
- 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为阈值类型。
阈值类型及原理如下图所示:
6
.Pyramids.cpp(图像金字塔)
源码及相关注释:
#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(图像处理)就介绍到此。