参考:
1、https://docs.opencv.org/3.2.0/
2、https://github.com/opencv/opencv/
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include
#include
using namespace std;
using namespace cv;
void on_low_r_thresh_trackbar(int, void *);
void on_high_r_thresh_trackbar(int, void *);
void on_low_g_thresh_trackbar(int, void *);
void on_high_g_thresh_trackbar(int, void *);
void on_low_b_thresh_trackbar(int, void *);
void on_high_b_thresh_trackbar(int, void *);
int low_r=30, low_g=30, low_b=30;
int high_r=100, high_g=100, high_b=100;
int main()
{
Mat frame, frame_threshold;
VideoCapture cap(0);
namedWindow("Video Capture", WINDOW_NORMAL);
namedWindow("Object Detection", WINDOW_NORMAL);
//-- Trackbars to set thresholds for RGB values
createTrackbar("Low R","Object Detection", &low_r, 255, on_low_r_thresh_trackbar);
createTrackbar("High R","Object Detection", &high_r, 255, on_high_r_thresh_trackbar);
createTrackbar("Low G","Object Detection", &low_g, 255, on_low_g_thresh_trackbar);
createTrackbar("High G","Object Detection", &high_g, 255, on_high_g_thresh_trackbar);
createTrackbar("Low B","Object Detection", &low_b, 255, on_low_b_thresh_trackbar);
createTrackbar("High B","Object Detection", &high_b, 255, on_high_b_thresh_trackbar);
while((char)waitKey(1)!='q'){
cap>>frame;
if(frame.empty())
break;
//-- Detect the object based on RGB Range Values
inRange(frame,Scalar(low_b,low_g,low_r), Scalar(high_b,high_g,high_r),frame_threshold);
//-- Show the frames
imshow("Video Capture",frame);
imshow("Object Detection",frame_threshold);
}
return 0;
}
void on_low_r_thresh_trackbar(int, void *)
{
low_r = min(high_r-1, low_r);
setTrackbarPos("Low R","Object Detection", low_r);
}
void on_high_r_thresh_trackbar(int, void *)
{
high_r = max(high_r, low_r+1);
setTrackbarPos("High R", "Object Detection", high_r);
}
void on_low_g_thresh_trackbar(int, void *)
{
low_g = min(high_g-1, low_g);
setTrackbarPos("Low G","Object Detection", low_g);
}
void on_high_g_thresh_trackbar(int, void *)
{
high_g = max(high_g, low_g+1);
setTrackbarPos("High G", "Object Detection", high_g);
}
void on_low_b_thresh_trackbar(int, void *)
{
low_b= min(high_b-1, low_b);
setTrackbarPos("Low B","Object Detection", low_b);
}
void on_high_b_thresh_trackbar(int, void *)
{
high_b = max(high_b, low_b+1);
setTrackbarPos("High B", "Object Detection", high_b);
}
1、我们来看看程序的一般结构:
创建两个矩阵元素来存储帧
Mat frame, frame_threshold;
从默认捕获设备捕获视频流。
VideoCapture cap(0);
namedWindow("Video Capture", WINDOW_NORMAL);
namedWindow("Object Detection", WINDOW_NORMAL);
//-- Trackbars to set thresholds for RGB values
createTrackbar("Low R","Object Detection", &low_r, 255, on_low_r_thresh_trackbar);
createTrackbar("High R","Object Detection", &high_r, 255, on_high_r_thresh_trackbar);
createTrackbar("Low G","Object Detection", &low_g, 255, on_low_g_thresh_trackbar);
createTrackbar("High G","Object Detection", &high_g, 255, on_high_g_thresh_trackbar);
createTrackbar("Low B","Object Detection", &low_b, 255, on_low_b_thresh_trackbar);
createTrackbar("High B","Object Detection", &high_b, 255, on_high_b_thresh_trackbar);
cap>>frame;
if(frame.empty())
break;
//-- Detect the object based on RGB Range Values
inRange(frame,Scalar(low_b,low_g,low_r), Scalar(high_b,high_g,high_r),frame_threshold);
//-- Show the frames
imshow("Video Capture",frame);
imshow("Object Detection",frame_threshold);
void on_low_r_thresh_trackbar(int, void *)
{
low_r = min(high_r-1, low_r);
setTrackbarPos("Low R","Object Detection", low_r);
}
void on_high_r_thresh_trackbar(int, void *)
{
high_r = max(high_r, low_r+1);
setTrackbarPos("High R", "Object Detection", high_r);
}
使用OpenCV函数cv :: filter2D创建您自己的线性过滤器。
从一般意义上讲,卷积是图像的每个部分与运算符(内核)之间的操作。
内核本质上是一个数值系数的固定大小的数组,以及该数组中的锚点,该数组通常位于中心。
假设您想知道图像中特定位置的结果值。 卷积的值按以下方式计算:
用等式的形式表示上述过程,我们可以得到:
幸运的是,OpenCV为您提供了函数cv :: filter2D,所以您不必编写所有这些操作。
1、这个程序做什么?
程序将执行大小为3,5,7,9和11的内核的过滤操作。
2、教程代码的显示如下。 您也可以从这里下载
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
using namespace cv;
int main ( int, char** argv )
{
Mat src, dst;
Mat kernel;
Point anchor;
double delta;
int ddepth;
int kernel_size;
const char* window_name = "filter2D Demo";
src = imread( argv[1], IMREAD_COLOR ); // Load an image
if( src.empty() )
{ return -1; }
anchor = Point( -1, -1 );
delta = 0;
ddepth = -1;
int ind = 0;
for(;;)
{
char c = (char)waitKey(500);
if( 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++;
}
return 0;
}
1、加载图像
src = imread( argv[1], IMREAD_COLOR ); // Load an image
if( src.empty() )
{ return -1; }
2、初始化线性滤波器的参数
anchor = Point( -1, -1 );
delta = 0;
ddepth = -1;
3、执行更新内核大小的无限循环,并将线性滤波器应用于输入图像。 我们来更详细地分析一下:
4、首先我们定义我们的过滤器将要使用的内核。 这里是:
kernel_size = 3 + 2*( ind%5 );
kernel = Mat::ones( kernel_size, kernel_size, CV_32F )/ (float)(kernel_size*kernel_size);
第一行是将kernel_size更新为范围内的奇数值:[3,11]。 第二行实际上是通过将它的值设置为一个用1填充的矩阵并通过将其除以元素数来规范它来建立内核。
5、设置内核之后,我们可以使用函数cv :: filter2D生成过滤器:
filter2D(src, dst, ddepth , kernel, anchor, delta, BORDER_DEFAULT );
参数表示:
6、我们的程序将实现一个while循环,每500 ms,我们的过滤器的内核大小将在指定的范围内更新。
使用OpenCV函数cv :: copyMakeBorder来设置边框(额外的填充图像)。
1、这个程序做什么?
2、教程代码的显示如下。 您也可以从这里下载
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
using namespace cv;
Mat src, dst;
int top, bottom, left, right;
int borderType;
const char* window_name = "copyMakeBorder Demo";
RNG rng(12345);
int main( int, char** argv )
{
src = imread( argv[1], IMREAD_COLOR ); // Load an image
if( src.empty() )
{
printf(" No data entered, please enter the path to an image file \n");
return -1;
}
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, 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 );
for(;;)
{
char c = (char)waitKey(500);
if( c == 27 )
{ break; }
else if( c == 'c' )
{ borderType = BORDER_CONSTANT; }
else if( c == 'r' )
{ borderType = BORDER_REPLICATE; }
Scalar value( 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 );
}
return 0;
}
1、首先我们声明我们要使用的变量:
Mat src, dst;
int top, bottom, left, right;
int borderType;
const char* window_name = "copyMakeBorder Demo";
RNG rng(12345);
特别值得注意的是随机数发生器的变量rng。 我们用它来生成随机的边框颜色,我们很快就会看到。
2、像往常一样,我们加载我们的源图像src:
src = imread( argv[1], IMREAD_COLOR ); // Load an image
if( src.empty() )
{
printf(" No data entered, please enter the path to an image file \n");
return -1;
}
3、在介绍如何使用该程序之后,我们创建一个窗口:
namedWindow( window_name, WINDOW_AUTOSIZE );
4、现在我们初始化定义边界大小的参数(顶部,底部,左侧和右侧)。 我们给他们一个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);
5、该程序运行在一个for循环。 如果用户按下“c”或“r”,borderType变量分别取BORDER_CONSTANT或BORDER_REPLICATE的值:
char c = (char)waitKey(500);
if( c == 27 )
{ break; }
else if( c == 'c' )
{ borderType = BORDER_CONSTANT; }
else if( c == 'r' )
{ borderType = BORDER_REPLICATE; }
6、在每次迭代中(0.5秒后),变量值被更新…
Scalar value( rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255) );
与由RNG变量rng生成的随机值进行比较。 该值是在[0,255]范围内随机选取的数字,
7、最后,我们调用函数cv :: copyMakeBorder来应用相应的填充:
copyMakeBorder( src, dst, top, bottom, left, right, borderType, value );
参数是:
8、我们在之前创建的图像中显示我们的输出图像
imshow( window_name, dst );
1、在最后两篇教程中,我们看到了卷积的应用例子。 其中最重要的一个卷积是计算图像中的导数(或对它们的近似)。
2、为什么图像中导数的微积分可能很重要? 让我们想象我们想要检测图像中存在的边缘。 例如:
你可以很容易地注意到,在一个边缘,像素的强度以一种显著的方式变化。 表达变化的好方法是使用导数工具。 梯度的高度变化表示图像的主要变化。
3、为了图形化,我们假设我们有一维图像。 在下图中,边缘由强度的“跳跃”表示:
4、如果我们采用一阶导数(实际上,这里显示为最大值),则可以更容易地看到边缘“跳跃”
5、所以,从上面的解释中,我们可以推断出一种检测图像中边缘的方法可以通过定位梯度高于其邻域(或推广,高于阈值)的像素位置来执行。
6、更详细的解释请参考Bradski和Kaehler学习OpenCV
假设要操作的图像是 I :
1、我们计算两个导数:
2、在图像的每个点上,我们通过结合上面的两个结果来计算该点上的渐变的近似值:
尽管有时会使用以下更简单的公式:
注:
当内核大小为3时,上面显示的Sobel内核可能会产生明显的不准确性(毕竟,Sobel只是导数的近似值)。 OpenCV通过使用cv :: Scharr函数来解决大小为3的内核的这种不准确性。 这与标准的Sobel功能一样快,但更准确。 它实现了以下内核:
您可以在OpenCV参考(cv :: Scharr)中查看该函数的更多信息。 另外,在下面的示例代码中,您会注意到在cv :: Sobel函数的代码之上还有cv :: Scharr函数注释的代码。 取消注释(并明显评论Sobel的东西)应该给你一个这个功能如何工作的想法。
1、这个程序做什么?
2、教程代码的显示如下。 您也可以从这里下载
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
using namespace cv;
int main( int, char** argv )
{
Mat src, src_gray;
Mat grad;
const char* window_name = "Sobel Demo - Simple Edge Detector";
int scale = 1;
int delta = 0;
int ddepth = CV_16S;
src = imread( argv[1], IMREAD_COLOR ); // Load an image
if( src.empty() )
{ return -1; }
GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );
cvtColor( src, src_gray, COLOR_BGR2GRAY );
Mat grad_x, grad_y;
Mat abs_grad_x, abs_grad_y;
//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 );
//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_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 );
waitKey(0);
return 0;
}
1、首先我们声明我们要使用的变量:
Mat src, src_gray;
Mat grad;
const char* window_name = "Sobel Demo - Simple Edge Detector";
int scale = 1;
int delta = 0;
int ddepth = CV_16S;
2、像往常一样,我们加载我们的源图像src:
src = imread( argv[1], IMREAD_COLOR ); // Load an image
if( src.empty() )
{ return -1; }
3、首先,我们将cv :: GaussianBlur应用于图像,以减少噪声(内核大小= 3)
GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );
4、现在我们将我们过滤的图像转换成灰度图:
cvtColor( src, src_gray, COLOR_BGR2GRAY );
5、其次,我们计算x和y方向上的“导数”。 为此,我们使用函数cv :: Sobel,如下所示:
Mat grad_x, grad_y;
Mat abs_grad_x, abs_grad_y;
//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 );
//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 );
该函数采用以下参数:
请注意,要计算x方向上的梯度,我们使用: xorder = 1和 yorder = 0。 我们做类似的y方向。
6、我们将部分结果转换回CV_8U:
convertScaleAbs( grad_x, abs_grad_x );
convertScaleAbs( grad_y, abs_grad_y );
7、最后,我们尝试通过添加两个方向梯度来近似梯度(请注意,这根本不是一个精确的计算,但它对我们的目的是有利的)。
addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad );
8、最后,我们展示我们的结果:
imshow( window_name, grad );
waitKey(0);
使用OpenCV函数cv :: Laplacian来实现拉普拉斯算子的离散模拟。
1、在之前的教程中,我们学习了如何使用Sobel算子。 这是基于这样的事实,即在边缘区域,像素强度显示出“跳跃”或强度变化很大。 获得强度的一阶导数,我们观察到边缘的特征是最大的,如图中所示:
2、而且…如果我们采用二阶导数会发生什么?
你可以观察到二阶导数是零! 所以,我们也可以使用这个标准来尝试检测图像中的边缘。 但是,请注意,零不仅会出现在边缘(它们实际上可能出现在其他无意义的位置)。 这可以通过在需要的地方应用过滤来解决。
1、从上面的解释中,我们推导出二阶导数可以用来检测边缘。 由于图像是“* 2D *”,因此我们需要在两个维度上都采用导数。 在这里,拉普拉斯算子来得方便。
2、拉普拉斯算子定义如下:
3、拉普拉斯算子在OpenCV中由函数cv :: Laplacian实现。 实际上,由于拉普拉斯算子使用图像的梯度,所以它在内部调用Sobel算子来执行它的计算。
1、这个程序做什么?
2、教程代码的显示如下。 您也可以从这里下载
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
using namespace cv;
int main( int, char** argv )
{
Mat src, src_gray, dst;
int kernel_size = 3;
int scale = 1;
int delta = 0;
int ddepth = CV_16S;
const char* window_name = "Laplace Demo";
src = imread( argv[1], IMREAD_COLOR ); // Load an image
if( src.empty() )
{ return -1; }
GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );
cvtColor( src, src_gray, COLOR_BGR2GRAY ); // Convert the image to grayscale
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);
return 0;
}
1、创建一些需要的变量:
Mat src, src_gray, dst;
int kernel_size = 3;
int scale = 1;
int delta = 0;
int ddepth = CV_16S;
const char* window_name = "Laplace Demo";
2、加载源图像:
src = imread( argv[1], IMREAD_COLOR ); // Load an image
if( src.empty() )
{ return -1; }
3、应用高斯模糊来减少噪音:
GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );
4、使用cv :: cvtColor将图像转换为灰度
cvtColor( src, src_gray, COLOR_BGR2GRAY ); // Convert the image to grayscale
5、将拉普拉斯算子应用于灰度图像:
Laplacian( src_gray, dst, ddepth, kernel_size, scale, delta, BORDER_DEFAULT );
参数是:
6、将拉普拉斯算子的输出转换为CV_8U图像:
convertScaleAbs( dst, abs_dst );
7、在窗口中显示结果:
imshow( window_name, abs_dst );
waitKey(0);
使用OpenCV函数cv :: Canny来实现Canny边缘检测器。
Canny边缘检测器是由John F. Canny于1986年开发的。也被许多人称为最佳检测器,Canny算法旨在满足三个主要标准:
1、滤除任何噪音。 高斯滤波器用于此目的。 下面显示了一个大小为5的高斯内核的例子:
2、找到图像的强度梯度。 为此,我们遵循一个类似于Sobel的程序:
方向被四舍五入为四个可能的角度之一(即0,45,90或135)
3、非最大抑制应用。 这将删除不被视为边缘一部分的像素。 因此,只有细线(候选边缘)将保留。
4、迟滞:最后一步。 Canny确实使用了两个阈值(上限和下限):
Canny建议上限:下限比例 在2:1和3:1之间。
5、欲了解更多详情,您可以随时咨询您最喜爱的计算机视觉书籍。
1、这个程序做什么?
2、教程代码的显示如下。 您也可以从这里下载
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
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;
const char* window_name = "Edge Map";
static void CannyThreshold(int, void*)
{
blur( src_gray, detected_edges, Size(3,3) );
Canny( detected_edges, detected_edges, lowThreshold, lowThreshold*ratio, kernel_size );
dst = Scalar::all(0);
src.copyTo( dst, detected_edges);
imshow( window_name, dst );
}
int main( int, char** argv )
{
src = imread( argv[1], IMREAD_COLOR ); // Load an image
if( src.empty() )
{ return -1; }
dst.create( src.size(), src.type() );
cvtColor( src, src_gray, COLOR_BGR2GRAY );
namedWindow( window_name, WINDOW_AUTOSIZE );
createTrackbar( "Min Threshold:", window_name, &lowThreshold, max_lowThreshold, CannyThreshold );
CannyThreshold(0, 0);
waitKey(0);
return 0;
}
1、创建一些需要的变量:
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;
const char* window_name = "Edge Map";
请注意以下几点:
2、加载源图像:
src = imread( argv[1], IMREAD_COLOR ); // Load an image
if( src.empty() )
{ return -1; }
3、创建一个与src相同类型和大小的矩阵(即dst)
dst.create( src.size(), src.type() );
4、将图像转换为灰度(使用函数cv :: cvtColor:
cvtColor( src, src_gray, COLOR_BGR2GRAY );
5、创建一个窗口来显示结果
namedWindow( window_name, WINDOW_AUTOSIZE );
6、为用户创建一个跟踪栏,为我们的Canny探测器输入较低的阈值:
createTrackbar( "Min Threshold:", window_name, &lowThreshold, max_lowThreshold, CannyThreshold );
注意以下几点:
7、让我们一步一步检查CannyThreshold函数:
blur( src_gray, detected_edges, Size(3,3) );
Canny( detected_edges, detected_edges, lowThreshold, lowThreshold*ratio, kernel_size );
参数是:
8、我们用零填充一个dst图像(意思是图像是完全黑色的)。
dst = Scalar::all(0);
9、最后,我们将使用函数cv :: Mat :: copyTo来仅映射被识别为边缘的图像区域(在黑色背景上)。 cv :: Mat :: copy将src图像复制到dst。 但是,它只会复制具有非零值的位置的像素。 由于Canny检测器的输出是黑色背景上的边缘轮廓,因此得到的dst在所有区域中将是黑色的,但检测到的边缘是黑色的。
src.copyTo( dst, detected_edges);
10、我们显示我们的结果:
imshow( window_name, dst );
使用OpenCV函数cv :: HoughLines和cv :: HoughLinesP来检测图像中的行。
1、如您所知,图像空间中的一条线可以用两个变量表示。 例如:
对于霍夫变换,我们将在极坐标系中表示线。 因此,一个线性方程可以写成:
式中: r = xcosθ+ysinθ
①、一般而言,对于每个点 (x0,y0) ,我们可以定义通过该点的线族:
这意味着每对 (rθ,θ) 代表通过 (x0,y0) 的每条线。
②、如果对于给定的 (x0,y0) 我们绘制经过它的线的族,我们得到一个正弦曲线。 例如,对于 x0=8 和 y0=6 ,我们得到下面的图(在 θ−r 平面内):
我们只考虑 r>0 和 0<θ<2π 的点。
③、我们可以对图像中的所有点进行相同的操作。 如果两个不同点的曲线在 θ−r 平面上相交,就意味着两个点属于同一条直线。 例如,按照上面的例子,再画两点: x1=4 , y1=9 , x2=12 , y2=3 ,得到:
这三个图相交于一个点(0.925,9.6),这些坐标是参数(θ,r)或(x0,y0),(x1,y1)和(x2,y2)所在的线。
④、上面所有的东西是什么意思? 这意味着一般情况下,可以通过查找曲线之间的交点数来检测线。越多的曲线相交意味着由该交点表示的线具有更多的点。 一般来说,我们可以定义检测线所需的最小交点数阈值。
⑤、这就是霍夫曲线变换所做的。 它跟踪图像中每个点的曲线之间的交集。 如果交点的数量超过某个阈值,则将其声明为交点的参数 (θ,rθ) 的一条直线。
OpenCV实现了两种Hough Line变换:
1、 标准霍夫曲线变换
2、 概率霍夫曲线变换
1、这个程序做什么?
2、我们将解释的示例代码可以从这里下载。 可以在这里找到一个稍微更有趣的版本(显示Hough标准和带有改变阈值的trackbars的概率)。
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include
using namespace cv;
using namespace std;
static void help()
{
cout << "\nThis program demonstrates line finding with the Hough transform.\n"
"Usage:\n"
"./houghlines , Default is ../data/pic1.png\n" << endl;
}
int main(int argc, char** argv)
{
cv::CommandLineParser parser(argc, argv,
"{help h||}{@image|../data/pic1.png|}"
);
if (parser.has("help"))
{
help();
return 0;
}
string filename = parser.get<string>("@image");
if (filename.empty())
{
help();
cout << "no image_name provided" << endl;
return -1;
}
Mat src = imread(filename, 0);
if(src.empty())
{
help();
cout << "can not open " << filename << endl;
return -1;
}
Mat dst, cdst;
Canny(src, dst, 50, 200, 3);
cvtColor(dst, cdst, COLOR_GRAY2BGR);
#if 0
vector lines;
HoughLines(dst, lines, 1, CV_PI/180, 100, 0, 0 );
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( cdst, pt1, pt2, Scalar(0,0,255), 3, CV_AA);
}
#else
vector lines;
HoughLinesP(dst, lines, 1, CV_PI/180, 50, 50, 10 );
for( size_t i = 0; i < lines.size(); i++ )
{
Vec4i l = lines[i];
line( cdst, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0,0,255), 3, LINE_AA);
}
#endif
imshow("source", src);
imshow("detected lines", cdst);
waitKey();
return 0;
}
1、加载图像
Mat src = imread(filename, 0);
if(src.empty())
{
help();
cout << "can not open " << filename << endl;
return -1;
}
2、通过使用Canny检测器来检测图像的边缘
Canny(src, dst, 50, 200, 3);
现在我们将应用霍夫线变换。 我们将解释如何使用可用于此目的的OpenCV功能:
3、标准霍夫曲线变换
a、首先,你应用转换:
vector lines;
HoughLines(dst, lines, 1, CV_PI/180, 100, 0, 0 );
有以下参数:
b、然后通过画线来显示结果。
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( cdst, pt1, pt2, Scalar(0,0,255), 3, LINE_AA);
}
4、概率霍夫线变换
a、首先你应用转换:
vector lines;
HoughLinesP(dst, lines, 1, CV_PI/180, 50, 50, 10 );
以下参数:
b、然后通过画线来显示结果。
for( size_t i = 0; i < lines.size(); i++ )
{
Vec4i l = lines[i];
line( cdst, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0,0,255), 3, LINE_AA);
}
5、显示原始图像和检测线:
imshow("source", src);
imshow("detected lines", cdst);
6、等到用户退出程序
waitKey();
使用OpenCV函数cv :: HoughCircles来检测图像中的圆圈。
其中 (xcenter,ycenter) 定义中心位置(绿色点),r是半径,这使得我们可以完全定义一个圆,如下所示:
1、这个程序做什么?
2、我们将解释的示例代码可以从这里下载。 稍微更漂亮的版本(显示改变阈值的轨迹栏)可以在这里找到。
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include
using namespace cv;
using namespace std;
static void help()
{
cout << "\nThis program demonstrates circle finding with the Hough transform.\n"
"Usage:\n"
"./houghcircles , Default is ../data/board.jpg\n" << endl;
}
int main(int argc, char** argv)
{
cv::CommandLineParser parser(argc, argv,
"{help h ||}{@image|../data/board.jpg|}"
);
if (parser.has("help"))
{
help();
return 0;
}
string filename = parser.get<string>("@image");
Mat img = imread(filename, IMREAD_COLOR);
if(img.empty())
{
help();
cout << "can not open " << filename << endl;
return -1;
}
Mat gray;
cvtColor(img, gray, COLOR_BGR2GRAY);
medianBlur(gray, gray, 5);
vector circles;
HoughCircles(gray, circles, HOUGH_GRADIENT, 1,
gray.rows/16, // change this value to detect circles with different distances to each other
100, 30, 1, 30 // change the last two parameters
// (min_radius & max_radius) to detect larger circles
);
for( size_t i = 0; i < circles.size(); i++ )
{
Vec3i c = circles[i];
circle( img, Point(c[0], c[1]), c[2], Scalar(0,0,255), 3, LINE_AA);
circle( img, Point(c[0], c[1]), 2, Scalar(0,255,0), 3, LINE_AA);
}
imshow("detected circles", img);
waitKey();
return 0;
}
1、加载图像
string filename = parser.get<string>("@image");
Mat img = imread(filename, IMREAD_COLOR);
if(img.empty())
{
help();
cout << "can not open " << filename << endl;
return -1;
}
2、转成灰度
Mat gray;
cvtColor(img, gray, COLOR_BGR2GRAY);
3、应用中值滤波来减少噪音并避免错误的圆圈检测:
medianBlur(gray, gray, 5);
4、继续应用Hough Circle Transform:
vector circles;
HoughCircles(gray, circles, HOUGH_GRADIENT, 1,
gray.rows/16, // change this value to detect circles with different distances to each other
100, 30, 1, 30 // change the last two parameters
// (min_radius & max_radius) to detect larger circles
);
以下参数:
5、绘制检测到的圈子:
for( size_t i = 0; i < circles.size(); i++ )
{
Vec3i c = circles[i];
circle( img, Point(c[0], c[1]), c[2], Scalar(0,0,255), 3, LINE_AA);
circle( img, Point(c[0], c[1]), 2, Scalar(0,255,0), 3, LINE_AA);
}
你可以看到,我们将绘制圆(红色)和中心(S)与一个小绿点
6、显示检测到的圆并等待用户退出程序:
imshow("detected circles", img);
waitKey();
使用OpenCV函数cv :: remap来实现简单的重新映射例程。
其中 g() 是重映射后的图像, f() 是源图像, h(x,y) 是对 (x,y) 进行操作的映射函数。
会发生什么? 很容易看到图像将在x方向上翻转。 例如,考虑输入图像:
观察红圈如何改变x的位置(考虑x的水平方向):
1、这个程序做什么?
2、教程代码的显示如下。 您也可以从这里下载
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include
using namespace cv;
Mat src, dst;
Mat map_x, map_y;
const char* remap_window = "Remap demo";
int ind = 0;
void update_map( void );
int main( int, char** argv )
{
src = imread( argv[1], IMREAD_COLOR );
dst.create( src.size(), src.type() );
map_x.create( src.size(), CV_32FC1 );
map_y.create( src.size(), CV_32FC1 );
namedWindow( remap_window, WINDOW_AUTOSIZE );
for(;;)
{
char c = (char)waitKey( 1000 );
if( c == 27 )
{ break; }
update_map();
remap( src, dst, map_x, map_y, INTER_LINEAR, BORDER_CONSTANT, Scalar(0, 0, 0) );
// Display results
imshow( remap_window, dst );
}
return 0;
}
void update_map( void )
{
ind = ind%4;
for( int j = 0; j < src.rows; j++ )
{ for( int i = 0; i < src.cols; i++ )
{
switch( ind )
{
case 0:
if( i > src.cols*0.25 && i < src.cols*0.75 && j > src.rows*0.25 && j < src.rows*0.75 )
{
map_x.at<float>(j,i) = 2*( i - src.cols*0.25f ) + 0.5f ;
map_y.at<float>(j,i) = 2*( j - src.rows*0.25f ) + 0.5f ;
}
else
{ map_x.at<float>(j,i) = 0 ;
map_y.at<float>(j,i) = 0 ;
}
break;
case 1:
map_x.at<float>(j,i) = (float)i ;
map_y.at<float>(j,i) = (float)(src.rows - j) ;
break;
case 2:
map_x.at<float>(j,i) = (float)(src.cols - i) ;
map_y.at<float>(j,i) = (float)j ;
break;
case 3:
map_x.at<float>(j,i) = (float)(src.cols - i) ;
map_y.at<float>(j,i) = (float)(src.rows - j) ;
break;
} // end of switch
}
}
ind++;
}
1、创建一些我们将使用的变量:
Mat src, dst;
Mat map_x, map_y;
char* remap_window = "Remap demo";
int ind = 0;
2、加载图像
src = imread( argv[1], 1 );
3、创建目标图像和两个映射矩阵(对于x和y)
dst.create( src.size(), src.type() );
map_x.create( src.size(), CV_32FC1 );
map_y.create( src.size(), CV_32FC1 );
4、创建一个窗口来显示结果
namedWindow( remap_window, WINDOW_AUTOSIZE );
5、建立一个循环。 每1000毫秒我们更新我们的映射矩阵(mat_x和mat_y)并将它们应用到我们的源图像:
while( true )
{
char c = (char)waitKey( 1000 );
if( c == 27 )
{ break; }
update_map();
remap( src, dst, map_x, map_y, INTER_LINEAR, BORDER_CONSTANT, Scalar(0,0, 0) );
imshow( remap_window, dst );
}
应用重映射的函数是cv :: remap。 我们给出以下参数:
我们如何更新我们的映射矩阵mat_x和mat_y? 继续阅读:
6、更新映射矩阵:我们将执行4个不同的映射:
a、将图片缩小一半,并将其显示在中间:
对于所有对(i,j),使得: src.cols4<i<3⋅src.cols4 和 src.rows4<j<3⋅src.rows4
b、翻转图像: h(i,j)=(i,src.rows−j)
c、从左到右反映图像: h(i,j)=(src.cols−i,j)
d、b和c的组合: h(i,j)=(src.cols−i,src.rows−j)
这在以下片段中表示。 这里,map_x表示h(i,j)的第一个坐标,map_y表示第二个坐标。
for( int j = 0; j < src.rows; j++ )
{ for( int i = 0; i < src.cols; i++ )
{
switch( ind )
{
case 0:
if( i > src.cols*0.25 && i < src.cols*0.75 && j > src.rows*0.25 && j < src.rows*0.75 )
{
map_x.at<float>(j,i) = 2*( i - src.cols*0.25 ) + 0.5 ;
map_y.at<float>(j,i) = 2*( j - src.rows*0.25 ) + 0.5 ;
}
else
{ map_x.at<float>(j,i) = 0 ;
map_y.at<float>(j,i) = 0 ;
}
break;
case 1:
map_x.at<float>(j,i) = i ;
map_y.at<float>(j,i) = src.rows - j ;
break;
case 2:
map_x.at<float>(j,i) = src.cols - i ;
map_y.at<float>(j,i) = j ;
break;
case 3:
map_x.at<float>(j,i) = src.cols - i ;
map_y.at<float>(j,i) = src.rows - j ;
break;
} // end of switch
}
}
ind++;
}
1、可以用矩阵乘法(线性变换)和矢量加法(平移)的形式表示任何变换。
2、从上面我们可以用仿射变换来表示:
您可以看到,实质上,仿射变换表示两个图像之间的关系。
3、表示仿射变换的常用方法是使用2×3矩阵。
考虑到我们想要变换2D矢量 X=[xy] 通过使用 A 和B,我们可以等价地做到: T=A⋅[xy]+B 和 T=M⋅[x,y,1]T
1、很好的问题。 我们提到仿射变换基本上是两个图像之间的关系。 关于这种关系的信息大致可以通过两种方式得出:
2、让我们稍微解释一下( b )。 由于M涉及02图像,我们可以分析两幅图像中三点相关的最简单情况。 看下面的图:
点1,点2和点3(在图像1中形成一个三角形)映射到图像2,仍然形成一个三角形,但是现在它们已经改变了。 如果我们找到这3个点的仿射变换(可以随意选择它们),那么我们可以将这个找到的关系应用到图像中的整个像素上。
1、这个程序做什么?
2、教程代码的显示如下。 您也可以从这里下载
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include
using namespace cv;
using namespace std;
const char* source_window = "Source image";
const char* warp_window = "Warp";
const char* warp_rotate_window = "Warp + Rotate";
int main( int, char** argv )
{
Point2f srcTri[3];
Point2f dstTri[3];
Mat rot_mat( 2, 3, CV_32FC1 );
Mat warp_mat( 2, 3, CV_32FC1 );
Mat src, warp_dst, warp_rotate_dst;
src = imread( argv[1], IMREAD_COLOR );
warp_dst = Mat::zeros( src.rows, src.cols, src.type() );
srcTri[0] = Point2f( 0,0 );
srcTri[1] = Point2f( src.cols - 1.f, 0 );
srcTri[2] = Point2f( 0, src.rows - 1.f );
dstTri[0] = Point2f( src.cols*0.0f, src.rows*0.33f );
dstTri[1] = Point2f( src.cols*0.85f, src.rows*0.25f );
dstTri[2] = Point2f( src.cols*0.15f, src.rows*0.7f );
warp_mat = getAffineTransform( srcTri, dstTri );
warpAffine( src, warp_dst, warp_mat, warp_dst.size() );
Point center = Point( warp_dst.cols/2, warp_dst.rows/2 );
double angle = -50.0;
double scale = 0.6;
rot_mat = getRotationMatrix2D( center, angle, scale );
warpAffine( warp_dst, warp_rotate_dst, rot_mat, warp_dst.size() );
namedWindow( source_window, WINDOW_AUTOSIZE );
imshow( source_window, src );
namedWindow( warp_window, WINDOW_AUTOSIZE );
imshow( warp_window, warp_dst );
namedWindow( warp_rotate_window, WINDOW_AUTOSIZE );
imshow( warp_rotate_window, warp_rotate_dst );
waitKey(0);
return 0;
}
1、声明我们将使用的一些变量,比如矩阵来存储我们的结果,以及2个点的数组来存储定义我们的仿射变换的2D点。
Point2f srcTri[3];
Point2f dstTri[3];
Mat rot_mat( 2, 3, CV_32FC1 );
Mat warp_mat( 2, 3, CV_32FC1 );
Mat src, warp_dst, warp_rotate_dst;
2、加载图像
src = imread( argv[1], 1 );
3、将目标图像初始化为与源具有相同的大小和类型:
warp_dst = Mat::zeros( src.rows, src.cols, src.type() );
4、仿射变换:正如我们在上面解释的那样,我们需要两组3点来导出仿射变换关系。 看一看:
srcTri[0] = Point2f( 0,0 );
srcTri[1] = Point2f( src.cols - 1, 0 );
srcTri[2] = Point2f( 0, src.rows - 1 );
dstTri[0] = Point2f( src.cols*0.0, src.rows*0.33 );
dstTri[1] = Point2f( src.cols*0.85, src.rows*0.25 );
dstTri[2] = Point2f( src.cols*0.15, src.rows*0.7 );
你可能想要画出点来更好地了解它们如何改变。 它们的位置与示例图(在“理论”部分)中描述的位置大致相同。 您可能会注意到3点定义的三角形的大小和方向会改变。
5、用两组点来武装,我们使用OpenCV函数cv :: getAffineTransform来计算仿射变换:
warp_mat = getAffineTransform( srcTri, dstTri );
6、我们将刚刚发现的仿射变换应用于src图像
warpAffine( src, warp_dst, warp_mat, warp_dst.size() );
有以下参数:
我们刚刚得到了我们的第一个转化的形象 我们将显示在一个位。 在此之前,我们也想旋转它…
7、旋转:要旋转图像,我们需要知道两件事:
我们用下面的代码定义这些参数:
Point center = Point( warp_dst.cols/2, warp_dst.rows/2 );
double angle = -50.0;
double scale = 0.6;
8、我们使用OpenCV函数cv :: getRotationMatrix2D生成旋转矩阵,该函数返回一个2×3矩阵(本例中为rot_mat)
rot_mat = getRotationMatrix2D( center, angle, scale );
9、我们现在将找到的旋转应用到之前的变换的输出。
warpAffine( warp_dst, warp_rotate_dst, rot_mat, warp_dst.size() );
10、最后,我们在两个窗口中加上原始图像来显示我们的结果,
namedWindow( source_window, WINDOW_AUTOSIZE );
imshow( source_window, src );
namedWindow( warp_window, WINDOW_AUTOSIZE );
imshow( warp_window, warp_dst );
namedWindow( warp_rotate_window, WINDOW_AUTOSIZE );
imshow( warp_rotate_window, warp_rotate_dst );
11、我们只需要等到用户退出程序
waitKey(0);
为了将其用作重映射函数,必须使 H′(i) 归一化,使得最大值为255(或图像强度的最大值)。 从上面的例子来看,累积函数是:
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include
using namespace cv;
using namespace std;
int main( int, char** argv )
{
Mat src, dst;
const char* source_window = "Source image";
const char* equalized_window = "Equalized Image";
src = imread( argv[1], IMREAD_COLOR );
if( src.empty() )
{ cout<<"Usage: ./EqualizeHist_Demo " <return -1;
}
cvtColor( src, src, COLOR_BGR2GRAY );
equalizeHist( src, dst );
namedWindow( source_window, WINDOW_AUTOSIZE );
namedWindow( equalized_window, WINDOW_AUTOSIZE );
imshow( source_window, src );
imshow( equalized_window, dst );
waitKey(0);
return 0;
}
1、声明源图像和目标图像以及窗口名称:
Mat src, dst;
char* source_window = "Source image";
char* equalized_window = "Equalized Image";
2、加载图像
src = imread( argv[1], 1 );
if( !src.data )
{ cout<<"Usage: ./Histogram_Demo " <return -1;}
3、转成灰度图
cvtColor( src, src, COLOR_BGR2GRAY );
4、使用函数cv :: equalizeHist应用直方图均衡:
equalizeHist( src, dst );
因为它可以很容易地看到,唯一的参数是原始图像和输出(均衡)图像。
5、显示两个图像(原始和均衡):
namedWindow( source_window, WINDOW_AUTOSIZE );
namedWindow( equalized_window, WINDOW_AUTOSIZE );
imshow( source_window, src );
imshow( equalized_window, dst );
6、等到用户退出程序
waitKey(0);
return 0;
如果我们想有组织地计算这些数据会发生什么? 既然我们知道这种情况下信息值的范围是256个值,我们可以将我们的范围分割成子部分(称为bins),如下所示:
并且我们可以保持在每个二进制范围内的像素数。 把这个应用到上面的例子中,我们得到下面的图像(轴x代表箱和轴y每个像素的数量)。
为了简单起见,OpenCV实现了cv :: calcHist函数,它计算一组数组(通常是图像或图像平面)的直方图。 它可以运行多达32个维度。 我们将在下面的代码中看到它!
#include "opencv2/highgui.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include
using namespace std;
using namespace cv;
int main(int argc, char** argv)
{
Mat src, dst;
String imageName( "../data/lena.jpg" ); // by default
if (argc > 1)
{
imageName = argv[1];
}
src = imread( imageName, IMREAD_COLOR );
if( src.empty() )
{ return -1; }
vector bgr_planes;
split( src, bgr_planes );
int histSize = 256;
float range[] = { 0, 256 } ;
const float* histRange = { range };
bool uniform = true; bool accumulate = false;
Mat b_hist, g_hist, r_hist;
calcHist( &bgr_planes[0], 1, 0, Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate );
calcHist( &bgr_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange, uniform, accumulate );
calcHist( &bgr_planes[2], 1, 0, Mat(), r_hist, 1, &histSize, &histRange, uniform, accumulate );
// Draw the histograms for B, G and R
int hist_w = 512; int hist_h = 400;
int bin_w = cvRound( (double) hist_w/histSize );
Mat histImage( hist_h, hist_w, CV_8UC3, Scalar( 0,0,0) );
normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
for( int i = 1; i < histSize; i++ )
{
line( histImage, Point( bin_w*(i-1), hist_h - cvRound(b_hist.at<float>(i-1)) ) ,
Point( bin_w*(i), hist_h - cvRound(b_hist.at<float>(i)) ),
Scalar( 255, 0, 0), 2, 8, 0 );
line( histImage, Point( bin_w*(i-1), hist_h - cvRound(g_hist.at<float>(i-1)) ) ,
Point( bin_w*(i), hist_h - cvRound(g_hist.at<float>(i)) ),
Scalar( 0, 255, 0), 2, 8, 0 );
line( histImage, Point( bin_w*(i-1), hist_h - cvRound(r_hist.at<float>(i-1)) ) ,
Point( bin_w*(i), hist_h - cvRound(r_hist.at<float>(i)) ),
Scalar( 0, 0, 255), 2, 8, 0 );
}
namedWindow("calcHist Demo", WINDOW_AUTOSIZE );
imshow("calcHist Demo", histImage );
waitKey(0);
return 0;
}
1、创建必要的矩阵:
Mat src, dst;
2、加载原图像
src = imread( argv[1], 1 );
if( !src.data )
{ return -1; }
3、在三个R,G和B平面中分离源图像。 为此,我们使用OpenCV函数cv :: split:
vector bgr_planes;
split( src, bgr_planes );
我们的输入是要分割的图像(这种情况下有三个通道),输出是Mat的矢量)
4、现在我们准备开始配置每个平面的直方图。 由于我们正在使用B,G和R平面,所以我们知道我们的值将在[0,255]
int histSize = 256; //from 0 to 255
float range[] = { 0, 256 } ; //the upper boundary is exclusive
const float* histRange = { range };
bool uniform = true; bool accumulate = false;
Mat b_hist, g_hist, r_hist;
calcHist( &bgr_planes[0], 1, 0, Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate );
calcHist( &bgr_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange, uniform, accumulate );
calcHist( &bgr_planes[2], 1, 0, Mat(), r_hist, 1, &histSize, &histRange, uniform, accumulate );
参数是:
5、创建一个图像来显示直方图:
// Draw the histograms for R, G and B
int hist_w = 512; int hist_h = 400;
int bin_w = cvRound( (double) hist_w/histSize );
Mat histImage( hist_h, hist_w, CV_8UC3, Scalar( 0,0,0) );
6、请注意,在绘制之前,我们首先cv::normalize直方图,使其值落在输入参数指定的范围内:
normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
这个函数接收这些参数:
7、最后,观察到访问bin(在这种情况下在这个1D柱状图):
for( int i = 1; i < histSize; i++ )
{
line( histImage, Point( bin_w*(i-1), hist_h - cvRound(b_hist.at(i-1) ) ) ,
Point( bin_w*(i), hist_h - cvRound(b_hist.at(i) ) ),
Scalar( 255, 0, 0), 2, 8, 0 );
line( histImage, Point( bin_w*(i-1), hist_h - cvRound(g_hist.at(i-1) ) ) ,
Point( bin_w*(i), hist_h - cvRound(g_hist.at(i) ) ),
Scalar( 0, 255, 0), 2, 8, 0 );
line( histImage, Point( bin_w*(i-1), hist_h - cvRound(r_hist.at(i-1) ) ) ,
Point( bin_w*(i), hist_h - cvRound(r_hist.at(i) ) ),
Scalar( 0, 0, 255), 2, 8, 0 );
}
我们使用表达式:
b_hist.at<float>(i)
i 在哪里指出维度。 如果这是一个二维直方图,我们会使用类似于:
b_hist.at( i, j )
8、最后我们显示我们的直方图并等待用户退出:
namedWindow("calcHist Demo", WINDOW_AUTOSIZE );
imshow("calcHist Demo", histImage );
waitKey(0);
return 0;
1、相关性(CV_COMP_CORREL)
这里的:
N是直方图bins的总数。
2、Chi-Square ( CV_COMP_CHISQR )
3、相交(method= CV_COMP_INTERSECT)
4、Bhattacharyya距离(CV_COMP_BHATTACHARYYA)
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include
using namespace std;
using namespace cv;
int main( int argc, char** argv )
{
Mat src_base, hsv_base;
Mat src_test1, hsv_test1;
Mat src_test2, hsv_test2;
Mat hsv_half_down;
if( argc < 4 )
{
printf("** Error. Usage: ./compareHist_Demo \n" );
return -1;
}
src_base = imread( argv[1], IMREAD_COLOR );
src_test1 = imread( argv[2], IMREAD_COLOR );
src_test2 = imread( argv[3], IMREAD_COLOR );
if(src_base.empty() || src_test1.empty() || src_test2.empty())
{
cout << "Can't read one of the images" << endl;
return -1;
}
cvtColor( src_base, hsv_base, COLOR_BGR2HSV );
cvtColor( src_test1, hsv_test1, COLOR_BGR2HSV );
cvtColor( src_test2, hsv_test2, COLOR_BGR2HSV );
hsv_half_down = hsv_base( Range( hsv_base.rows/2, hsv_base.rows - 1 ), Range( 0, hsv_base.cols - 1 ) );
int h_bins = 50; int s_bins = 60;
int histSize[] = { h_bins, s_bins };
// hue varies from 0 to 179, saturation from 0 to 255
float h_ranges[] = { 0, 180 };
float s_ranges[] = { 0, 256 };
const float* ranges[] = { h_ranges, s_ranges };
// Use the o-th and 1-st channels
int channels[] = { 0, 1 };
MatND hist_base;
MatND hist_half_down;
MatND hist_test1;
MatND hist_test2;
calcHist( &hsv_base, 1, channels, Mat(), hist_base, 2, histSize, ranges, true, false );
normalize( hist_base, hist_base, 0, 1, NORM_MINMAX, -1, Mat() );
calcHist( &hsv_half_down, 1, channels, Mat(), hist_half_down, 2, histSize, ranges, true, false );
normalize( hist_half_down, hist_half_down, 0, 1, NORM_MINMAX, -1, Mat() );
calcHist( &hsv_test1, 1, channels, Mat(), hist_test1, 2, histSize, ranges, true, false );
normalize( hist_test1, hist_test1, 0, 1, NORM_MINMAX, -1, Mat() );
calcHist( &hsv_test2, 1, channels, Mat(), hist_test2, 2, histSize, ranges, true, false );
normalize( hist_test2, hist_test2, 0, 1, NORM_MINMAX, -1, Mat() );
for( int i = 0; i < 4; i++ )
{
int compare_method = i;
double base_base = compareHist( hist_base, hist_base, compare_method );
double base_half = compareHist( hist_base, hist_half_down, compare_method );
double base_test1 = compareHist( hist_base, hist_test1, compare_method );
double base_test2 = compareHist( hist_base, hist_test2, compare_method );
printf( " Method [%d] Perfect, Base-Half, Base-Test(1), Base-Test(2) : %f, %f, %f, %f \n", i, base_base, base_half , base_test1, base_test2 );
}
printf( "Done \n" );
return 0;
}
1、声明变量,如矩阵来存储基础图像和其他两个图像进行比较(BGR和HSV)
Mat src_base, hsv_base;
Mat src_test1, hsv_test1;
Mat src_test2, hsv_test2;
Mat hsv_half_down;
2、加载基本图像(src_base)和另外两个测试图像:
if( argc < 4 )
{ printf("** Error. Usage: ./compareHist_Demo \n" );
return -1;
}
src_base = imread( argv[1], 1 );
src_test1 = imread( argv[2], 1 );
src_test2 = imread( argv[3], 1 );
3、将它们转换为HSV格式:
cvtColor( src_base, hsv_base, COLOR_BGR2HSV );
cvtColor( src_test1, hsv_test1, COLOR_BGR2HSV );
cvtColor( src_test2, hsv_test2, COLOR_BGR2HSV );
4、另外,创建基本图像的一半(以HSV格式)的图像:
hsv_half_down = hsv_base( Range( hsv_base.rows/2, hsv_base.rows - 1 ), Range( 0, hsv_base.cols - 1 ) );
5、初始化参数以计算直方图(bins,范围和通道H和S)。
int h_bins = 50; int s_bins = 60;
int histSize[] = { h_bins, s_bins };
float h_ranges[] = { 0, 180 };
float s_ranges[] = { 0, 256 };
const float* ranges[] = { h_ranges, s_ranges };
int channels[] = { 0, 1 };
6、创建MatND对象来存储直方图:
MatND hist_base;
MatND hist_half_down;
MatND hist_test1;
MatND hist_test2;
7、计算基础图像,2个测试图像和半下基本图像的直方图:
calcHist( &hsv_base, 1, channels, Mat(), hist_base, 2, histSize, ranges, true, false );
normalize( hist_base, hist_base, 0, 1, NORM_MINMAX, -1, Mat() );
calcHist( &hsv_half_down, 1, channels, Mat(), hist_half_down, 2, histSize, ranges, true, false );
normalize( hist_half_down, hist_half_down, 0, 1, NORM_MINMAX, -1, Mat() );
calcHist( &hsv_test1, 1, channels, Mat(), hist_test1, 2, histSize, ranges, true, false );
normalize( hist_test1, hist_test1, 0, 1, NORM_MINMAX, -1, Mat() );
calcHist( &hsv_test2, 1, channels, Mat(), hist_test2, 2, histSize, ranges, true, false );
normalize( hist_test2, hist_test2, 0, 1, NORM_MINMAX, -1, Mat() );
8、依次应用基本图像(Hist_base)的直方图和其他直方图之间的4种比较方法:
for( int i = 0; i < 4; i++ )
{ int compare_method = i;
double base_base = compareHist( hist_base, hist_base, compare_method );
double base_half = compareHist( hist_base, hist_half_down, compare_method );
double base_test1 = compareHist( hist_base, hist_test1, compare_method );
double base_test2 = compareHist( hist_base, hist_test2, compare_method );
printf( " Method [%d] Perfect, Base-Half, Base-Test(1), Base-Test(2) : %f, %f, %f, %f \n", i, base_base, base_half , base_test1, base_test2 );
}