为什么ximgproc.hppp里面没有函数实现

OpenCV3.0 Examples学习笔记(13)-kmeans.cpp-kmeans函数实现对图像位置进行聚类
这个系列的目的是通过对OpenCV示例,进一步了解OpenCV函数的使用,不涉及具体原理。
Example运行截图
Example分析
Example代码
本文记录了对OpenCV示例kmeans.cpp的分析。
资料地址:
这个示例主要演示了如何使用kmeans对图像位置进行聚类。
示例涉及到kmeans函数使用。
K-means算法是最为经典的基于划分的聚类方法,是十大经典数据挖掘算法之一。K-means算法的基本思想是:以空间中k个点为中心进行聚类,对最靠近他们的对象归类。通过迭代的方法,逐次更新各聚类中心的值,直至得到最好的聚类结果。&
假设要把样本集分为c个类别,算法描述如下:
(1)适当选择c个类的初始中心;
(2)在第k次迭代中,对任意一个样本,求其到c个中心的距离,将该样本归到距离最短的中心所在的类;
(3)利用均值等方法更新该类的中心值;
(4)对于所有的c个聚类中心,如果利用(2)(3)的迭代法更新后,值保持不变,则迭代结束,否则继续迭代。
该算法的最大优势在于简洁和快速。算法的关键在于初始中心的选择和距离公式。 & &
kmeans在OpenCV中声明
CV_EXPORTS_W&double&kmeans(&InputArray&data,&int&K,&InputOutputArray&bestLabels,
&&&&&&&&&&&&&&&&&&&&&&&&&&&&TermCriteria&criteria,&int&attempts,
&&&&&&&&&&&&&&&&&&&&&&&&&&&&int&flags,&OutputArray&centers&=&noArray()&);
参数说明:
data:用于聚类的数据,是N维的数组类型(Mat型),必须浮点型;
K:表示需要聚类的类别数;
bestLabels:聚类后的标签数组,Mat型;
criteria:迭代收敛准则(MAX_ITER最大迭代次数,EPS最高精度);
attempts:表示尝试的次数,防止陷入局部最优;
flags:表示聚类中心的选取方式(KMEANS_RANDOM_CENTERS&随机选取,KMEANS_P
& & & & & & P_CENTERS使用Arthur提供的算法,KMEANS_USE_INITIAL_LABELS使用初始标签
& & & & & & );
:表示聚类后的类别中心。
注意,聚类指对data的值进行聚类,而非位置!如果需要对位置进行聚类,可以考虑将x,y坐标存入CV_32FC2的Mat
Example运行截图
鼠标点击次数
注意,如前文提到聚类是针对Mat的值进行的,而非位置,但是这个例子看似将图像的位置进行分类,
但是其实质是将随机位置存入一个N行2列(2列当然是x,y坐标)的矩阵中,完成聚类后,再根据结果在原图中绘制。
Example分析
1.声明类簇的最大数量
const&int&MAX_CLUSTERS&=&5;
2.声明绘制类簇中点的颜色,用于区分不同的类簇
Scalar&colorTab[]&=
&&&&Scalar(0,&0,&255),
&&&&Scalar(0,255,0),
&&&&Scalar(255,100,100),
&&&&Scalar(255,0,255),
&&&&Scalar(0,255,255)
3.创建预览图
Mat&img(500,&500,&CV_8UC3);
4.声明随机数类
RNG&rng(12345);
(1)&OpenCV中RNG类。它可以压缩一个64位的i整数并可以得到scalar和array的随机数。
目前的版本支持均匀分布随机数和Gaussian分布随机数。随机数的产生采用的是Multiply-With-Carry算法和Ziggurat算法。
其构造函数的初始化可以传入一个64位的整型参数作为随机数产生器的初值。
next可以取出下一个随机数,
uniform函数可以返回指定范围的随机数,
gaussian函数返回一个高斯随机数,
fill函数用随机数填充矩阵。
(2)OpenCV中还有一些随机数相关的函数,
比如randu可以产生一个均匀分布的随机数或者矩阵,
randn可以产生一个正态分布的随机数,
randShuffle可以随机打乱矩阵元素
5.使用循环反复观察K-means结果
& & char&key&=&(char)waitKey();
& & if(&key&==&27&||&key&==&'q'&||&key&==&'Q'&)&//&'ESC'
&&&&&&&&&&&&
(1)这样的方式常用于批量样本测试,或使用随机量反复观察
6.具体分析for循环中代码
6.1通过随机数获取类簇的数量(数量在2到5之间)
int&k,&clusterCount&=&rng.uniform(2,&MAX_CLUSTERS+1);
(1)cv::RNG 的uniform取随机值区间为[a,b),前闭后开,因此类似此处取2至5应该使用参数[2,6)
6.2通过随机数获取样本数量(数量在1到1000之间)
int&i,&sampleCount&=&rng.uniform(1,&1001);
6.3创建测试聚类样本矩阵,行数为样本数量,列数为1
Mat&points(sampleCount,&1,&CV_32FC2),&
(1)聚类算法是对Mat的值进行聚类,因此使用行/列矩阵就可以,很多初学者以为是对图像的位置进行聚类,那是错的。
如果希望对图像的位置进行聚类,只能将其位置信息作为值重新装入一个Mat,再进行聚类。
(2)因为是将位置存入Mat进行聚类,包含x,y信息,所以此处当然选用CV_32FC2。
6.4根据样本数量和预设类簇数量进行检测,如果样本数量小于类簇数量,则修改类簇数量
clusterCount&=&MIN(clusterCount,&sampleCount);
6.5中心点(为了确保采用K-Means算法能够找到预期的聚类,因此随机点是根据中心值波动的)
6.6根据类簇数量绘制随机样本(如前文提到为了确保采用K-Means算法能够找到预期的聚类,因此随机点是根据中心值波动的,并且有多少预设类簇就有多少中心点)
/*&generate&random&sample&from&multigaussian&distribution&*/
for(&k&=&0;&k&&&clusterC&k++&)
& & &Point&
& & &center.x&=&rng.uniform(0,&img.cols);
& & &center.y&=&rng.uniform(0,&img.rows);
& & &Mat&pointChunk&=&points.rowRange(k*sampleCount/clusterCount,
& & & & & & & & & & & & & & & & & & &k&==&clusterCount&-&1&?&sampleCount&:
& & & & & & & & & & & & & & & & & & &(k+1)*sampleCount/clusterCount);
& & &rng.fill(pointChunk,&RNG::NORMAL,&Scalar(center.x,&center.y),&Scalar(img.cols*0.05,&img.rows*0.05));
此处流程:
(1)遍历每一个类簇,k
(2)在图像中随机选取一个中心点center,其坐标随机
(3)根据当前类簇对测试样本获取其制定区域(行)pointChunk&,算法为
& & & a.将这个样本平均分为clusterCount分区,起始值为第k个分区起始位置;
& & & b.判断 k 为最后一个分区(总之为了取得平均分区的区域)
& & & & & b.1.如果是,末尾位置为最后一行;
& & & & & b.2.如果不是,末尾位置为当前分区的终止位置。
(4)对pointChunk
进行随机填充(根据中心值波动,并且波动值很小)
(1)Mat::rowRange,取example中特定范围的行列构成矩阵
(2)RNG::fill,对Mat的值进行范围内随机填充(随机是指值随机,而不是位置随机,Mat中的每一个元素都会有一个随机值)
6.7随机打乱矩阵元素
randShuffle(points,&1,&&rng);
6.8调用kmeans
kmeans(points,&clusterCount,&labels,
&&&&&&&&&&&&TermCriteria(&TermCriteria::EPS+TermCriteria::COUNT,&10,&1.0),
& & & & & & 3,&KMEANS_PP_CENTERS,&centers);
6.9预览图背景为黑色
img&=&Scalar::all(0);
6.10根据points中存储位置,和labels中存储的分类,对结果采用不同颜色进行绘制区分
for(&i&=&0;&i&&&sampleC&i++&)
& & int&clusterIdx&=&labels.at&int&(i);
& & Point&ipt&=&points.at&Point2f&(i);
& & circle(&img,&ipt,&2,&colorTab[clusterIdx],&FILLED,&LINE_AA&);
6.11显示预览图
imshow(&clusters&,&img);
Example代码
#include &opencv2/highgui.hpp&
#include &opencv2/core.hpp&
#include &opencv2/imgproc.hpp&
#include &iostream&
// static void help()
cout && &\nThis program demonstrates kmeans clustering.\n&
&It generates an image with random points, then assigns a random number of cluster\n&
&centers and uses kmeans to move those cluster centers to their representitive location\n&
&./kmeans\n& &&
int main( int /*argc*/, char** /*argv*/ )
const int MAX_CLUSTERS = 5;
Scalar colorTab[] =
Scalar(0, 0, 255),
Scalar(0,255,0),
Scalar(255,100,100),
Scalar(255,0,255),
Scalar(0,255,255)
Mat img(500, 500, CV_8UC3);
RNG rng(12345);
int k, clusterCount = rng.uniform(2, MAX_CLUSTERS+1);
int i, sampleCount = rng.uniform(1, 1001);
Mat points(sampleCount, 1, CV_32FC2),
clusterCount = MIN(clusterCount, sampleCount);
/* generate random sample from multigaussian distribution */
for( k = 0; k & clusterC k++ )
center.x = rng.uniform(0, img.cols);
center.y = rng.uniform(0, img.rows);
Mat pointChunk = points.rowRange(k*sampleCount/clusterCount,
k == clusterCount - 1 ? sampleCount :
(k+1)*sampleCount/clusterCount);
rng.fill(pointChunk, RNG::NORMAL, Scalar(center.x, center.y), Scalar(img.cols*0.05, img.rows*0.05));
randShuffle(points, 1, &rng);
kmeans(points, clusterCount, labels,
TermCriteria( TermCriteria::EPS+TermCriteria::COUNT, 10, 1.0),
3, KMEANS_PP_CENTERS, centers);
img = Scalar::all(0);
for( i = 0; i & sampleC i++ )
int clusterIdx = labels.at&int&(i);
Point ipt = points.at&Point2f&(i);
circle( img, ipt, 2, colorTab[clusterIdx], FILLED, LINE_AA );
imshow(&clusters&, img);
char key = (char)waitKey();
if( key == 27 || key == 'q' || key == 'Q' ) // 'ESC'
看过本文的人也看了:
我要留言技术领域:
取消收藏确定要取消收藏吗?
删除图谱提示你保存在该图谱下的知识内容也会被删除,建议你先将内容移到其他图谱中。你确定要删除知识图谱及其内容吗?
删除节点提示无法删除该知识节点,因该节点下仍保存有相关知识内容!
删除节点提示你确定要删除该知识节点吗?OpenCV(91)
实现自己的线性滤波器
本篇教程中,我们将学到:
用OpenCV函数&&创建自己的线性滤波器。
以下解释节选自Bradski and Kaehler所著&Learning OpenCV&。
高度概括地说,卷积是在每一个图像块与某个算子(核)之间进行的运算。
核是什么?
核说白了就是一个固定大小的数值数组。该数组带有一个&锚点&,一般位于数组中央。
如何用核实现卷积?
假如你想得到图像的某个特定位置的卷积值,可用下列方法计算:
将核的锚点放在该特定位置的像素上,同时,核内的其他值与该像素邻域的各像素重合;将核内各值与相应像素值相乘,并将乘积相加;将所得结果放到与锚点对应的像素上;对图像所有像素重复上述过程。
用公式表示上述过程如下:
幸运的是,我们不必自己去实现这些运算,OpenCV为我们提供了函数&&。
下面这段程序做了些什么?
载入一幅图像
对图像执行&归一化块滤波器&。举例来说,如果该滤波器核的大小为&&,则它会像下面这样:
程序将执行核的大小分别为3、5、7、9、11的滤波器运算。
该滤波器每一种核的输出将在屏幕上显示500毫秒
本教程代码所示如下。你也可以从&&下载。
#include &opencv2/imgproc/imgproc.hpp&
#include &opencv2/highgui/highgui.hpp&
#include &stdlib.h&
#include &stdio.h&
using namespace cv;
/** @函数main */
int main ( int argc, char** argv )
/// 声明变量
Mat src, dst;
Mat kernel;
Point anchor;
double delta;
int ddepth;
int kernel_size;
char* window_name = &filter2D Demo&;
/// 载入图像
src = imread( argv[1] );
if( !src.data )
{ return -1; }
/// 创建窗口
namedWindow( window_name, CV_WINDOW_AUTOSIZE );
/// 初始化滤波器参数
anchor = Point( -1, -1 );
delta = 0;
ddepth = -1;
/// 循环 - 每隔0.5秒,用一个不同的核来对图像进行滤波
int ind = 0;
while( true )
c = waitKey(500);
/// 按'ESC'可退出程序
if( (char)c == 27 )
{ break; }
/// 更新归一化块滤波器的核大小
kernel_size = 3 + 2*( ind%5 );
kernel = Mat::ones( kernel_size, kernel_size, CV_32F )/ (float)(kernel_size*kernel_size);
/// 使用滤波器
filter2D(src, dst, ddepth , kernel, anchor, delta, BORDER_DEFAULT );
imshow( window_name, dst );
ind++;
载入一幅图像
src = imread( argv[1] );
if( !src.data )
{ return -1; }
创建窗口以显示结果
namedWindow( window_name, CV_WINDOW_AUTOSIZE );
初始化线性滤波器的参数
anchor = Point( -1, -1 );
delta = 0;
ddepth = -1;
执行无限循环。在循环中,我们更新了核的大小,并将线性滤波器用在输入图像上。下面,我们详细分析一下该循环:
首先,我们定义滤波器要用到的核。像下面这样:
kernel_size = 3 + 2*( ind%5 );
kernel = Mat::ones( kernel_size, kernel_size, CV_32F )/ (float)(kernel_size*kernel_size);
第一行代码将&核的大小&设置为&&范围内的奇数。第二行代码把1填充进矩阵,并执行归一化——除以矩阵元素数——以构造出所用的核。
将核设置好之后,使用函数&&就可以生成滤波器:
filter2D(src, dst, ddepth , kernel, anchor, delta, BORDER_DEFAULT );
其中各参数含义如下:
src: 源图像dst: 目标图像ddepth:&dst&的深度。若为负值(如&&),则表示其深度与源图像相等。kernel: 用来遍历图像的核anchor: 核的锚点的相对位置,其中心点默认为&(-1, -1)&。delta: 在卷积过程中,该值会加到每个像素上。默认情况下,这个值为&&。BORDER_DEFAULT: 这里我们保持其默认值,更多细节将在其他教程中详解
#. 我们在程序里写了个&while&循环。每隔500毫秒,滤波器的核将在我们所指定的范围内更新。 结果 ========
编译好上述代码之后,输入图像路径的参数,我们就可以执行这个程序。其输出结果是一个窗口,其中显示了由归一化滤波器模糊之后的图像。每过0.5秒,滤波器核的大小会有所变化,如你在下面几张图像中所见:
给图像添加边界
本文档尝试解答如下问题:
如何使用OpenCV函数&&设置边界(添加额外的边界)。
以下内容来自于Bradski和Kaehler的大作&Learning OpenCV&。
前一节我们学习了图像的卷积操作。一个很自然的问题是如何处理卷积边缘。当卷积点在图像边界时会发生什么,如何处理这个问题?
大多数用到卷积操作的OpenCV函数都是将给定图像拷贝到另一个轻微变大的图像中,然后自动填充图像边界(通过下面示例代码中的各种方式)。这样卷积操作就可以在边界像素安全执行了(填充边界在操作完成后会自动删除)。
本文档将会探讨填充图像边界的两种方法:
BORDER_CONSTANT: 使用常数填充边界 (i.e. 黑色或者&)BORDER_REPLICATE: 复制原图中最临近的行或者列。
源码部分给出更加详细的解释。
本程序做什么?
由用户决定使用哪种填充方式。有两个选项:
常数边界: 所有新增边界像素使用一个常数,程序每0.5秒会产生一个随机数更新该常数值。复制边界: 复制原图像的边界像素。
用户可以选择按 ‘c’ 键 (常数边界) 或者 ‘r’ 键 (复制边界)
当用户按 ‘ESC’ 键,程序退出。
下面是本教程的源码, 你也可以从&&下载
#include &opencv2/imgproc/imgproc.hpp&
#include &opencv2/highgui/highgui.hpp&
#include &stdlib.h&
#include &stdio.h&
using namespace cv;
/// 全局变量
Mat src, dst;
int top, bottom, left, right;
int borderType;
Scalar value;
char* window_name = &copyMakeBorder Demo&;
RNG rng(12345);
/** @函数 main
int main( int argc, char** argv )
/// 装载图像
src = imread( argv[1] );
if( !src.data )
{ return -1;
printf(& No data entered, please enter the path to an image file \n&);
/// 使用说明
printf( &\n \t copyMakeBorder Demo: \n& );
printf( &\t -------------------- \n& );
printf( & ** Press 'c' to set the border to a random constant value \n&);
printf( & ** Press 'r' to set the border to be replicated \n&);
printf( & ** Press 'ESC' to exit the program \n&);
/// 创建显示窗口
namedWindow( window_name, CV_WINDOW_AUTOSIZE );
/// 初始化输入参数
top = (int) (0.05*src.rows); bottom = (int) (0.05*src.rows);
left = (int) (0.05*src.cols); right = (int) (0.05*src.cols);
dst = src;
imshow( window_name, dst );
while( true )
c = waitKey(500);
if( (char)c == 27 )
{ break; }
else if( (char)c == 'c' )
{ borderType = BORDER_CONSTANT; }
else if( (char)c == 'r' )
{ borderType = BORDER_REPLICATE; }
value = Scalar( rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255) );
copyMakeBorder( src, dst, top, bottom, left, right, borderType, value );
imshow( window_name, dst );
首先申明程序中用到的变量:
Mat src, dst;
int top, bottom, left, right;
int borderType;
Scalar value;
char* window_name = &copyMakeBorder Demo&;
RNG rng(12345);
尤其要注意变量&rng&,这是一个随机数生成器, 用来产生随机边界色彩。
装载原图像&src:
src = imread( argv[1] );
if( !src.data )
{ return -1;
printf(& No data entered, please enter the path to an image file \n&);
在简要说明了程序的使用方法后,创建一个显示窗口:
namedWindow( window_name, CV_WINDOW_AUTOSIZE );
初始化边界宽度参数(top,&bottom,&left&和&right)。我们将它们设定为图像&src&大小的5%。
top = (int) (0.05*src.rows); bottom = (int) (0.05*src.rows);
left = (int) (0.05*src.cols); right = (int) (0.05*src.cols);
程序进入&while&循环。 如果用户按’c’键或者 ‘r’键, 变量&borderType&分别取值&BORDER_CONSTANT&或BORDER_REPLICATE&:
while( true )
c = waitKey(500);
if( (char)c == 27 )
{ break; }
else if( (char)c == 'c' )
{ borderType = BORDER_CONSTANT; }
else if( (char)c == 'r' )
{ borderType = BORDER_REPLICATE; }
每个循环 (周期 0.5 秒), 变量&value&自动更新...
value = Scalar( rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255) );
为一个由&RNG&类型变量&rng&产生的随机数。 随机数的范围在&&之间。
最后调用函数&&填充边界像素:
copyMakeBorder( src, dst, top, bottom, left, right, borderType, value );
src: 原图像dst: 目标图像top,&bottom,&left,&right: 各边界的宽度,此处定义为原图像尺寸的5%。borderType: 边界类型,此处可以选择常数边界或者复制边界。value: 如果&borderType&类型是&BORDER_CONSTANT, 该值用来填充边界像素。
显示输出图像
imshow( window_name, dst );
在编译上面的代码之后, 我们可以运行结果,将图片路径输入。 结果应该为:
程序启动时边界类型为 BORDER_CONSTANT (0), 因此,一开始边界颜色任意变换。如果用户按 ‘r’ 键, 边界将会变成原图像边缘的拷贝。如果用户按 ‘c’ 键, 边界再次变为任意颜色。如果用户按 ‘ESC’ 键,程序退出。
下面显示了几张截图演示了边界颜色如何改变,以及在边界类型为&BORDER_REPLICATE&时的情形:
Sobel 导数
本文档尝试解答如下问题:
如何使用OpenCV函数&&对图像求导。如何使用OpenCV函数&&更准确地计算&&核的导数。
以下内容来自于Bradski和Kaehler的大作:&Learning OpenCV&.
上面两节我们已经学习了卷积操作。一个最重要的卷积运算就是导数的计算(或者近似计算).
为什么对图像进行求导是重要的呢? 假设我们需要检测图像中的&边缘&,如下图:
你可以看到在&边缘&,相素值显著的&改变&了。表示这一&改变&的一个方法是使用&导数&。 梯度值的大变预示着图像中内容的显著变化。
用更加形象的图像来解释,假设我们有一张一维图形。下图中灰度值的”跃升”表示边缘的存在:
使用一阶微分求导我们可以更加清晰的看到边缘”跃升”的存在(这里显示为高峰值)
从上例中我们可以推论检测边缘可以通过定位梯度值大于邻域的相素的方法找到(或者推广到大于一个阀值).
更加详细的解释,请参考Bradski 和 Kaehler的&Learning OpenCV&。
Sobel 算子是一个离散微分算子 (discrete differentiation operator)。 它用来计算图像灰度函数的近似梯度。Sobel 算子结合了高斯平滑和微分求导。
假设被作用图像为&:
在两个方向求导:
水平变化: 将&&与一个奇数大小的内核&&进行卷积。比如,当内核大小为3时,&&的计算结果为:
垂直变化: 将:math:I&与一个奇数大小的内核&&进行卷积。比如,当内核大小为3时,&&的计算结果为:
在图像的每一点,结合以上两个结果求出近似&梯度:
有时也用下面更简单公式代替:
当内核大小为&&时, 以上Sobel内核可能产生比较明显的误差(毕竟,Sobel算子只是求取了导数的近似值)。
为解决这一问题,OpenCV提供了&&函数,但该函数仅作用于大小为3的内核。该函数的运算与Sobel函数一样快,但结果却更加精确,其内核为:
关于(&&)的更多信息请参考OpenCV文档。在下面的示例代码中,你会发现在&&函数调用的上面有被注释掉的&&函数调用。
反注释Scharr调用 (当然也要相应的注释掉Sobel调用),看看该函数是如何工作的。
本程序做什么?
使用&Sobel算子&产生的输出图像上,检测到的亮起的&边缘&相素散布在更暗的背景中。
下面是本教程的源码,你也可以从&&下载
#include &opencv2/imgproc/imgproc.hpp&
#include &opencv2/highgui/highgui.hpp&
#include &stdlib.h&
#include &stdio.h&
using namespace cv;
/** @function main */
int main( int argc, char** argv )
Mat src, src_gray;
char* window_name = &Sobel Demo - Simple Edge Detector&;
int scale = 1;
int delta = 0;
int ddepth = CV_16S;
/// 装载图像
src = imread( argv[1] );
if( !src.data )
{ return -1; }
GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );
/// 转换为灰度图
cvtColor( src, src_gray, CV_RGB2GRAY );
/// 创建显示窗口
namedWindow( window_name, CV_WINDOW_AUTOSIZE );
/// 创建 grad_x 和 grad_y 矩阵
Mat grad_x, grad_y;
Mat abs_grad_x, abs_grad_y;
/// 求 X方向梯度
//Scharr( src_gray, grad_x, ddepth, 1, 0, scale, delta, BORDER_DEFAULT );
Sobel( src_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT );
convertScaleAbs( grad_x, abs_grad_x );
/// 求Y方向梯度
//Scharr( src_gray, grad_y, ddepth, 0, 1, scale, delta, BORDER_DEFAULT );
Sobel( src_gray, grad_y, ddepth, 0, 1, 3, scale, delta, BORDER_DEFAULT );
convertScaleAbs( grad_y, abs_grad_y );
/// 合并梯度(近似)
addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad );
imshow( window_name, grad );
waitKey(0);
首先申明变量:
Mat src, src_gray;
char* window_name = &Sobel Demo - Simple Edge Detector&;
int scale = 1;
int delta = 0;
int ddepth = CV_16S;
装载原图像&src:
src = imread( argv[1] );
if( !src.data )
{ return -1; }
第一步对原图像使用&&降噪
( 内核大小 = 3 )
GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );
将降噪后的图像转换为灰度图:
cvtColor( src, src_gray, CV_RGB2GRAY );
第二步,在&x&和&y&方向分别”求导“。 为此,我们使用函数&&:
Mat grad_x, grad_y;
Mat abs_grad_x, abs_grad_y;
/// 求 X方向梯度
Sobel( src_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT );
/// 求 Y方向梯度
Sobel( src_gray, grad_y, ddepth, 0, 1, 3, scale, delta, BORDER_DEFAULT );
该函数接受了以下参数:
src_gray: 在本例中为输入图像,元素类型&CV_8Ugrad_x/grad_y: 输出图像.ddepth: 输出图像的深度,设定为&CV_16S&避免外溢。x_order:&x&方向求导的阶数。y_order:&y&方向求导的阶数。scale,&delta&和&BORDER_DEFAULT: 使用默认值
注意为了在&x&方向求导我们使用:&&,&.
采用同样方法在&y&方向求导。
将中间结果转换到&CV_8U:
convertScaleAbs( grad_x, abs_grad_x );
convertScaleAbs( grad_y, abs_grad_y );
将两个方向的梯度相加来求取近似&梯度&(注意这里没有准确的计算,但是对我们来讲已经足够了)。
addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad );
最后,显示结果:
imshow( window_name, grad );
这里是将Sobel算子作用于&lena.jpg&的结果:
Laplace 算子
本文档尝试解答如下问题:
如何使用OpenCV函数&&实现&Laplacian
算子&的离散模拟。
前一节我们学习了&Sobel 算子&,其基础来自于一个事实,即在边缘部分,像素值出现”跳跃“或者较大的变化。如果在此边缘部分求取一阶导数,你会看到极值的出现。正如下图所示:
如果在边缘部分求二阶导数会出现什么情况?
你会发现在一阶导数的极值位置,二阶导数为0。所以我们也可以用这个特点来作为检测图像边缘的方法。 但是, 二阶导数的0值不仅仅出现在边缘(它们也可能出现在无意义的位置),但是我们可以过滤掉这些点。
Laplacian 算子
从以上分析中,我们推论二阶导数可以用来&检测边缘&。 因为图像是 “2维”, 我们需要在两个方向求导。使用Laplacian算子将会使求导过程变得简单。Laplacian 算子&的定义:
OpenCV函数&&实现了Laplacian算子。
实际上,由于 Laplacian使用了图像梯度,它内部调用了&Sobel&算子。
本程序做什么?
装载图像使用高斯平滑消除噪声, 将图像转换到灰度空间。使用Laplacian算子作用于灰度图像,并保存输出图像。输出结果。
下面是本教程的源码,你也可以从&&下载。
#include &opencv2/imgproc/imgproc.hpp&
#include &opencv2/highgui/highgui.hpp&
#include &stdlib.h&
#include &stdio.h&
using namespace cv;
/** @函数 main */
int main( int argc, char** argv )
Mat src, src_gray, dst;
int kernel_size = 3;
int scale = 1;
int delta = 0;
int ddepth = CV_16S;
char* window_name = &Laplace Demo&;
/// 装载图像
src = imread( argv[1] );
if( !src.data )
{ return -1; }
/// 使用高斯滤波消除噪声
GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );
/// 转换为灰度图
cvtColor( src, src_gray, CV_RGB2GRAY );
/// 创建显示窗口
namedWindow( window_name, CV_WINDOW_AUTOSIZE );
/// 使用Laplace函数
Mat abs_dst;
Laplacian( src_gray, dst, ddepth, kernel_size, scale, delta, BORDER_DEFAULT );
convertScaleAbs( dst, abs_dst );
/// 显示结果
imshow( window_name, abs_dst );
waitKey(0);
首先申明变量:
Mat src, src_gray, dst;
int kernel_size = 3;
int scale = 1;
int delta = 0;
int ddepth = CV_16S;
char* window_name = &Laplace Demo&;
装载原图像:
src = imread( argv[1] );
if( !src.data )
{ return -1; }
高斯平滑降噪:
GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );
使用&&转换为灰度图
cvtColor( src, src_gray, CV_RGB2GRAY );
#.对灰度图使用Laplacian算子:
Laplacian( src_gray, dst, ddepth, kernel_size, scale, delta, BORDER_DEFAULT );
函数接受了以下参数:
src_gray: 输入图像。dst: 输出图像ddepth: 输出图像的深度。 因为输入图像的深度是&CV_8U&,这里我们必须定义&ddepth&=&CV_16S&以避免外溢。kernel_size: 内部调用的 Sobel算子的内核大小,此例中设置为3。scale,&delta&和&BORDER_DEFAULT: 使用默认值。
将输出图像的深度转化为&CV_8U&:
convertScaleAbs( dst, abs_dst );
imshow( window_name, abs_dst );
#.在编译上面的代码之后, 我们可以运行结果,将图片路径输入,如下图:
我们得到下图所示的结果。 注意观察树木和牛的轮廓基本上很好的反映出来(除了像素值比较接近的地方, 比如奶牛的头部)。 此外,注意树木(右方)后面的房子屋顶被明显的加强显示出来,这是由于局部对比度比较强的原因。
Canny 边缘检测
本文档尝试解答如下问题:
使用OpenCV函数&&检测边缘.
Canny 边缘检测算法&是 John F. Canny 于 1986年开发出来的一个多级边缘检测算法,也被很多人认为是边缘检测的&最优算法, 最优边缘检测的三个主要评价标准是:
低错误率:&标识出尽可能多的实际边缘,同时尽可能的减少噪声产生的误报。高定位性:&标识出的边缘要与图像中的实际边缘尽可能接近。最小响应:&图像中的边缘只能标识一次。
消除噪声。 使用高斯平滑滤波器卷积降噪。 下面显示了一个&&的高斯内核示例:
计算梯度幅值和方向。 此处,按照Sobel滤波器的步骤:
运用一对卷积阵列 (分别作用于&&和&&方向):
使用下列公式计算梯度幅值和方向:
梯度方向近似到四个可能角度之一(一般 0, 45, 90, 135)
非极大值&抑制。 这一步排除非边缘像素, 仅仅保留了一些细线条(候选边缘)。
滞后阈值: 最后一步,Canny 使用了滞后阈值,滞后阈值需要两个阈值(高阈值和低阈值):
如果某一像素位置的幅值超过&高&阈值, 该像素被保留为边缘像素。如果某一像素位置的幅值小于&低&阈值, 该像素被排除。如果某一像素位置的幅值在两个阈值之间,该像素仅仅在连接到一个高于&高&阈值的像素时被保留。
Canny 推荐的&高:低&阈值比在 2:1 到3:1之间。
想要了解更多细节,你可以参考任何你喜欢的计算机视觉书籍。
本程序做什么?
要求使用者输入一个数字,设置&Canny Edge Detector&的低阈值 (通过trackbar)使用&Canny 边缘检测&产生一个&mask&(白线代表边缘,黑色代表背景)。使用&mask&作为掩码显示原图像。
本教程的源码如下,你也可以从&&下载
#include &opencv2/imgproc/imgproc.hpp&
#include &opencv2/highgui/highgui.hpp&
#include &stdlib.h&
#include &stdio.h&
using namespace cv;
/// 全局变量
Mat src, src_gray;
Mat dst, detected_edges;
int edgeThresh = 1;
int lowThreshold;
int const max_lowThreshold = 100;
int ratio = 3;
int kernel_size = 3;
char* window_name = &Edge Map&;
* @函数 CannyThreshold
* @简介: trackbar 交互回调 - Canny阈值输入比例1:3
void CannyThreshold(int, void*)
/// 使用 3x3内核降噪
blur( src_gray, detected_edges, Size(3,3) );
/// 运行Canny算子
Canny( detected_edges, detected_edges, lowThreshold, lowThreshold*ratio, kernel_size );
/// 使用 Canny算子输出边缘作为掩码显示原图像
dst = Scalar::all(0);
src.copyTo( dst, detected_edges);
imshow( window_name, dst );
/** @函数 main */
int main( int argc, char** argv )
/// 装载图像
src = imread( argv[1] );
if( !src.data )
{ return -1; }
/// 创建与src同类型和大小的矩阵(dst)
dst.create( src.size(), src.type() );
/// 原图像转换为灰度图像
cvtColor( src, src_gray, CV_BGR2GRAY );
/// 创建显示窗口
namedWindow( window_name, CV_WINDOW_AUTOSIZE );
/// 创建trackbar
createTrackbar( &Min Threshold:&, window_name, &lowThreshold, max_lowThreshold, CannyThreshold );
/// 显示图像
CannyThreshold(0, 0);
/// 等待用户反应
waitKey(0);
创建程序中要用到的变量:
Mat src, src_
Mat dst, detected_
int edgeThresh = 1;
int const max_lowThreshold = 100;
int ratio = 3;
int kernel_size = 3;
char* window_name = &Edge Map&;
a. 我们首先设定高:低阈值比为 3:1 (通过变量 *ratio* )
b. 设定内核尺寸为 :math:`3` (Canny函数内部调用Sobel操作)
c. 将低阈值的上限设定为 :math:`100`.
装载原图像:
/// 装载图像
src = imread( argv[1] );
if( !src.data )
{ return -1; }
创建与&src&同类型和大小的矩阵(dst)
dst.create( src.size(), src.type() );
将输入图像转换到灰度空间 (使用函数&):
cvtColor( src, src_gray, CV_BGR2GRAY );
创建显示窗口
namedWindow( window_name, CV_WINDOW_AUTOSIZE );
创建trackbar,来获取用户交互输入的低阈值:
createTrackbar( &Min Threshold:&, window_name, &lowThreshold, max_lowThreshold, CannyThreshold );
通过trackbar控制的变量为&lowThreshold&,上限为&max_lowThreshold&(我们已经设定为100)每次用户通过trackbar产生变动,回调函数&CannyThreshold&被调用.
让我们一步一步的来观察&CannyThreshold&函数:
首先, 使用 3x3的内核平滑图像:
blur( src_gray, detected_edges, Size(3,3) );
其次,运用&&寻找边缘:
Canny( detected_edges, detected_edges, lowThreshold, lowThreshold*ratio, kernel_size );
detected_edges: 原灰度图像detected_edges: 输出图像 (支持原地计算,可为输入图像)lowThreshold: 用户通过 trackbar设定的值。highThreshold: 设定为低阈值的3倍 (根据Canny算法的推荐)kernel_size: 设定为 3 (Sobel内核大小,内部使用)
填充&dst&图像,填充值为0 (图像全黑).
dst = Scalar::all(0);
最后, 使用函数&&标识被检测到的边缘部分
(背景为黑色).
src.copyTo( dst, detected_edges);
&将&src&图像拷贝到&dst&.
但是,仅仅拷贝掩码不为0的像素。既然Canny边缘检测的输出是镶嵌在黑色背景中的边缘像素,因此其结果&dst&图像除了被检测的边缘像素,其余部分都为黑色。
imshow( window_name, dst );
在编译上面的代码之后, 我们可以运行结果,将图片路径输入,如下图:
滑动标尺, 尝试不同的阈值,我们得到如下结果:
仔细观察边缘像素是如何叠加在黑色背景之上的。
from: http://www./opencvdoc/2.3.2/html/doc/tutorials/imgproc/table_of_content_imgproc/table_of_content_imgproc.html#table-of-content-imgproc
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:3532830次
积分:43929
积分:43929
排名:第77名
原创:253篇
转载:278篇
译文:253篇
评论:265条
(25)(3)(20)(108)(169)(174)(212)(222)(17)(127)(155)(179)(317)(382)(479)(217)(348)(477)(286)(113)(8)(1)(72)(11)(10)(21)(11)
----------coding----------
----------MLCV----------
----------IT笔试面试----------
----------大数据云计算----------
----------Math----------

我要回帖

更多关于 strstr函数实现 的文章

 

随机推荐