图像监视是微软VisualStudio的插件,它允许您在调试应用程序时在内存映像中可视化。这有助于跟踪错误,或者简单地理解给定代码的操作。
可以在vs里面的工具-->扩展和更新
里面获取Image Watch
然后再视图-->其他窗口
调用。
#include
#include
#include
#include
#include
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
string imageName("1.jpg"); // by default
if (argc > 1)
{
imageName = argv[1];
}
Mat image;
image = imread(imageName.c_str(), IMREAD_COLOR); // Read the file
if (image.empty()) // Check for invalid input
{
cout << "Could not open or find the image" << std::endl;
return -1;
}
namedWindow("Display window", WINDOW_AUTOSIZE); // Create a window for display.
imshow("Display window", image); // Show our image inside it.
waitKey(0); // Wait for a keystroke in the window
return 0;
}
现在我们调用CV::imRead
函数,它加载由第一个参数指定的图像名称(ARGV〔1〕)
。第二个参数指定了我们想要的图像的格式。
IimeRead不变(<0)按原样加载图像(包括alpha通道,如果存在)
IimeRead灰度(0)将图像加载为强度图像。
IdRead颜色(> 0)以RGB格式加载图像
image = imread(imageName.c_str(), IMREAD_COLOR); // Read the file
在检查图像数据被正确加载之后,我们想要显示我们的图像,所以我们使用CV::NAMEDLE
窗口函数创建OpenCV窗口。一旦创建了OpenCV,它们就会自动管理。为此,您需要指定它的名称以及它应该如何从大小的角度来处理它包含的图像的变化。
如果不使用QT后端,则只支持WistWOWAutoSead。在这种情况下,窗口大小将占用它所显示的图像的大小。不允许调整大小!
Qt上的Windows WORKWORD,您可以使用它来允许窗口调整大小。图像将根据当前窗口大小调整自身大小。通过使用该运算符,还需要指定是否希望图像保持其纵横比(WOWDOWKEEPRATIO)或不(WOWDOWFRIERATIO)。
namedWindow( "Display window", WINDOW_AUTOSIZE ); // Create a window for display.
最后,使用新的图像更新OpenCV窗口的内容,使用imshow
函数。指定要更新的OpenCV窗口名称和在此操作期间要使用的图像:
imshow( "Display window", image ); // Show our image inside it.
因为我们希望我们的窗口显示出来,直到用户按下一个键(否则程序将结束得太快),我们使用CV::WaWiKEY函数,它的唯一参数是它等待用户输入的时间(毫秒测量)。零意味着永远等待。
waitKey(0); // Wait for a keystroke in the window
#include
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
char* imageName = argv[1];
Mat image;
image = imread(imageName, 1);
if (argc != 2 || !image.data)
{
printf(" No image data \n ");
return -1;
}
Mat gray_image;
cvtColor(image, gray_image, COLOR_BGR2GRAY);
imwrite("Gray_Image.jpg", gray_image);
namedWindow(imageName, WINDOW_AUTOSIZE);
namedWindow("Gray image", WINDOW_AUTOSIZE);
imshow(imageName, image);
imshow("Gray image", gray_image);
waitKey(0);
return 0;
}
cvtColor( image, gray_image, COLOR_BGR2GRAY );
参数:
源图像(图像)
目的地图像(GrayIX图像),在其中我们将保存转换后的图像。
指示将执行什么类型的转换的附加参数。在这种情况下,我们使用CurryBGR2GRY(因为在彩色图像的情况下具有BGR默认信道顺序)。
imwrite( “…/…/images/Gray_Image.jpg”, gray_image );
参数:
写入的图片名字
写入的Mat
MAT基本上是一个具有两个数据部分的类:矩阵标头(包含诸如矩阵的大小、用于存储的方法、地址是存储的矩阵等)和指向包含像素值的矩阵的指针(取决于选择用于存储的方法。矩阵头的大小是恒定的,但是矩阵本身的大小可以从图像到图像变化,并且通常是按数量级大的。
OpenCV是一个图像处理库。它包含了大量的图像处理功能集合。为了解决计算上的挑战,大多数情况下,您将最终使用库的多个函数。因此,将图像传递给函数是一种常见的做法。我们不应该忘记,我们正在谈论图像处理算法,这往往是相当沉重的计算。我们想做的最后一件事是通过制作不必要的大图像拷贝来进一步降低程序的速度。
为了解决这个问题,OpenCV使用一个参考计数系统。其思想是每个Mat对象都有自己的标头,但是矩阵可以通过它们的矩阵指针指向相同的地址而在它们的两个实例之间共享。此外,复制运算符只将标题和指针复制到大矩阵,而不是数据本身。
Mat A, C; // creates just the header parts
A = imread(argv[1], IMREAD_COLOR); // here we'll know the method used (allocate matrix)
Mat B(A); // Use the copy constructor
C = A; // Assignment operator
最后,所有这些对象指向相同的单个数据矩阵。然而,它们的标题是不同的,使用它们中的任何一个进行修改也会影响所有其他的标题。在实际应用中,不同的对象只对同一基础数据提供不同的访问方法。然而,它们的标题部分是不同的。真正有趣的部分是,您可以创建仅引用完整数据的一个子段的标题。例如,为了在图像中创建感兴趣区域(ROI),您只需创建具有新边界的新标题:
Mat D (A, Rect(10, 10, 100, 100) ); // using a rectangle
Mat E = A(Range::all(), Range(1,3)); // using row and column boundaries
现在你可能会问,矩阵本身是否可能属于多个物体,当它不再需要时,它负责清理它。简短的回答是:最后一个使用它的对象。这是通过使用参考计数机制来处理的。每当有人复制垫对象的头时,矩阵的计数器就会增加。每当清空报头时,计数器就会减少。当计数器达到零时,矩阵也被释放。有时,您也希望复制矩阵本身,因此OpenCV提供CV::MAT::clone()
和CV::MAT::CopyTo()
函数。
Mat F = A.clone();
Mat G;
A.copyTo(G);
现在修改F或G不会影响垫头所指向的矩阵。你需要记住的是:
OpenCV函数的输出图像分配是自动的(除非另有说明)。
你不需要考虑内存管理与opencvs C++接口。
赋值运算符和复制构造函数只复制报头。
可以使用CV::Mat:clone()
和CV::MAT::CopyTo()
函数复制图像的底层矩阵。
CV_[The number of bits per item][Signed or Unsigned][Type Prefix]C[The channel number]
CVY8UC3意味着我们使用8位长的无符号字符类型,每个像素都有三个来形成三个通道。这是预定义的多达四个频道号码。CV::标量是四元短向量。指定这个值,并且可以用自定义值初始化所有矩阵点。如果需要更多,可以用上宏创建类型,在括号中设置通道编号,如下所示。
迭代器方法被认为是一种更安全的方法,因为它从用户那里接管这些任务。所有你需要做的是询问图像矩阵的开始和结束,然后只需增加开始迭代器,直到达到结束。若要获取迭代器所指向的值,请使用*运算符(在它之前加上它)。
Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)
{
// accept only char type matrices
CV_Assert(I.depth() != sizeof(uchar));
const int channels = I.channels();
switch(channels)
{
case 1:
{
MatIterator_ it, end;
for( it = I.begin(), end = I.end(); it != end; ++it)
*it = table[*it];
break;
}
case 3:
{
MatIterator_ it, end;
for( it = I.begin(), end = I.end(); it != end; ++it)
{
(*it)[0] = table[(*it)[0]];
(*it)[1] = table[(*it)[1]];
(*it)[2] = table[(*it)[2]];
}
}
}
return I;
}
在彩色图像的情况下,每列有三个UCHAR项。这可能被认为是一个短向量uChar项目。要访问第n个子列,我们使用简单的运算符[]访问。记住OpenCV迭代器经过列并自动跳转到下一行是很重要的。因此,在彩色图像的情况下,如果使用简单的UCHAR迭代器,则只能访问蓝色通道值。
矩阵上的掩码运算是相当简单的。想法是根据掩模矩阵(也称为内核)重新计算图像中的每个像素值。该掩模保持将调整相邻像素(和当前像素)对新像素值有多大影响的值。从数学的角度,我们用我们的指定值做加权平均。
void Sharpen(const Mat& myImage, Mat& Result)
{
CV_Assert(myImage.depth() == CV_8U); // accept only uchar images
Result.create(myImage.size(), myImage.type());
const int nChannels = myImage.channels();
for(int j = 1; j < myImage.rows - 1; ++j)
{
const uchar* previous = myImage.ptr(j - 1);
const uchar* current = myImage.ptr(j );
const uchar* next = myImage.ptr(j + 1);
uchar* output = Result.ptr(j);
for(int i = nChannels; i < nChannels * (myImage.cols - 1); ++i)
{
*output++ = saturate_cast(5 * current[i]
-current[i - nChannels] - current[i + nChannels] - previous[i] - next[i]);
}
}
Result.row(0).setTo(Scalar(0));
Result.row(Result.rows - 1).setTo(Scalar(0));
Result.col(0).setTo(Scalar(0));
Result.col(Result.cols - 1).setTo(Scalar(0));
}
合成两张图片
#include
#include
using namespace cv;
int main(int argc, char** argv)
{
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;
if (input >= 0.0 && input <= 1.0)
{
alpha = input;
}
src1 = imread("1.jpg");
src2 = imread("2.jpg");
if (!src1.data) { printf("Error loading src1 \n"); return -1; }
if (!src2.data) { printf("Error loading src2 \n"); return -1; }
namedWindow("Linear Blend", 1);
beta = (1.0 - alpha);
addWeighted(src1, alpha, src2, beta, 0.0, dst);
imshow("Linear Blend", dst);
imwrite("3.jpg", dst);
waitKey(0);
return 0;
}
addWeighted( src1, alpha, src2, beta, 0.0, dst);
图片:src1,src2
比例:alpha,beta,和为1
新图片:dst
一般的图像处理算子是一个获取一个或多个输入图像并产生输出图像的函数。
图像变换可以看作:
点算子(像素变换)
邻域(基于区域)算子
在这种图像处理变换中,每个输出像素的值仅取决于相应的输入像素值。
这样的例子包括亮度和对比度调整以及颜色校正和变换。
两个常用的点过程是乘法和加法:
g(x)=αf(x)+β
参数α>0和β通常称为增益和偏置参数,有时这些参数分别用来控制对比度和亮度。
可以将f(x)作为源图像像素和g(x)作为输出图像像素。然后,我们更方便地将表达式写成:
g(i,j)=αf(i,j)+β
#include
#include
using namespace cv;
double alpha; /*< Simple contrast control */
int beta; /*< Simple brightness control */
int main(int argc, char** argv)
{
Mat image = imread(argv[1]);
Mat new_image = Mat::zeros(image.size(), image.type());
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;
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(y, x)[c] =
saturate_cast(alpha*(image.at(y, x)[c]) + beta);
}
}
}
namedWindow("Original Image", 1);
namedWindow("New Image", 1);
imshow("Original Image", image);
imshow("New Image", new_image);
imwrite("2.jpg", new_image);
waitKey();
return 0;
}
1.我们首先创建参数来保存用户输入的α和β:
double alpha;
int beta;
2.我们使用cv::imread
加载图像并将其保存在一个MAT对象中:
Mat image = imread( argv[1] );
3.我们使用CV::iMead加载图像并将其保存在一个Mat对象中:现在,因为我们将对该图像进行一些转换,所以我们需要一个新的Mat对象来存储它。此外,我们希望这具有以下特征:
初始像素值等于零
与原始图像相同大小和类型
Mat new_image = Mat::zeros( image.size(), image.type() );
4.现在,为了执行操作G(i,j)=αf(i,j)+β
,我们将访问图像中的每个像素。由于我们正在使用BGR图像,我们将有三个像素(B,G和R)的值,所以我们也将分别访问它们。
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(y,x)[c] =
saturate_cast( alpha*( image.at(y,x)[c] ) + beta );
}
}
}
注意以下事项:
为了访问图像中的每个像素,我们使用这个语法:图像。在
,其中Y是行,X是列,C是R,G或B(0, 1或2)。
因为运算 αp(i,j)+β
可以给出超出范围的值,或者不是整数(如果α是浮点数),我们使用cv::SudialType
强制转换来确保值是有效的。
5.最后,我们创建窗口和显示图像,通常的方式。
namedWindow("Original Image", 1);
namedWindow("New Image", 1);
imshow("Original Image", image);
imshow("New Image", new_image);
imwrite("2.jpg", new_image);
waitKey();
它代表一个2D点,由它的图像坐标x和y指定。我们可以将它定义为:
Point pt;
pt.x = 10;
pt.y = 8;
//Point pt = Point(10, 8);
表示一个4元向量。在OpenCV中,类型标量被广泛用于传递像素值;
在本教程中,我们将广泛使用它来表示BGR颜色值(3个参数)。如果不使用最后一个参数,则不必定义最后一个参数。
让我们来看看一个例子,如果我们要求一个颜色参数,我们给出:
Scalar( a, b, c )
我们将定义一个BGR颜色,例如:蓝色= A,绿色= B和红色= C。
#include
#include
#include
#define w 400
using namespace cv;
/// Function headers
void MyEllipse( Mat img, double angle );
void MyFilledCircle( Mat img, Point center );
void MyPolygon( Mat img );
void MyLine( Mat img, Point start, Point end );
/**
* @function main
* @brief Main function
*/
int main( void ){
//![create_images]
/// Windows names
char atom_window[] = "Drawing 1: Atom";
char rook_window[] = "Drawing 2: Rook";
/// Create black empty images
Mat atom_image = Mat::zeros( w, w, CV_8UC3 );
Mat rook_image = Mat::zeros( w, w, CV_8UC3 );
//![create_images]
/// 1. Draw a simple atom:
/// -----------------------
//![draw_atom]
/// 1.a. Creating ellipses
MyEllipse( atom_image, 90 );
MyEllipse( atom_image, 0 );
MyEllipse( atom_image, 45 );
MyEllipse( atom_image, -45 );
/// 1.b. Creating circles
MyFilledCircle( atom_image, Point( w/2, w/2) );
//![draw_atom]
/// 2. Draw a rook
/// ------------------
//![draw_rook]
/// 2.a. Create a convex polygon
MyPolygon( rook_image );
//![rectangle]
/// 2.b. Creating rectangles
rectangle( rook_image,
Point( 0, 7*w/8 ),
Point( w, w),
Scalar( 0, 255, 255 ),
FILLED,
LINE_8 );
//![rectangle]
/// 2.c. Create a few lines
MyLine( rook_image, Point( 0, 15*w/16 ), Point( w, 15*w/16 ) );
MyLine( rook_image, Point( w/4, 7*w/8 ), Point( w/4, w ) );
MyLine( rook_image, Point( w/2, 7*w/8 ), Point( w/2, w ) );
MyLine( rook_image, Point( 3*w/4, 7*w/8 ), Point( 3*w/4, w ) );
//![draw_rook]
/// 3. Display your stuff!
imshow( atom_window, atom_image );
moveWindow( atom_window, 0, 200 );
imshow( rook_window, rook_image );
moveWindow( rook_window, w, 200 );
waitKey( 0 );
return(0);
}
/// Function Declaration
/**
* @function MyEllipse
* @brief Draw a fixed-size ellipse with different angles
*/
//![my_ellipse]
void MyEllipse( Mat img, double angle )
{
int thickness = 2;
int lineType = 8;
ellipse( img,
Point( w/2, w/2 ),
Size( w/4, w/16 ),
angle,
0,
360,
Scalar( 255, 0, 0 ),
thickness,
lineType );
}
//![my_ellipse]
/**
* @function MyFilledCircle
* @brief Draw a fixed-size filled circle
*/
//![my_filled_circle]
void MyFilledCircle( Mat img, Point center )
{
circle( img,
center,
w/32,
Scalar( 0, 0, 255 ),
FILLED,
LINE_8 );
}
//![my_filled_circle]
/**
* @function MyPolygon
* @brief Draw a simple concave polygon (rook)
*/
//![my_polygon]
void MyPolygon( Mat img )
{
int lineType = LINE_8;
/** Create some points */
Point rook_points[1][20];
rook_points[0][0] = Point( w/4, 7*w/8 );
rook_points[0][1] = Point( 3*w/4, 7*w/8 );
rook_points[0][2] = Point( 3*w/4, 13*w/16 );
rook_points[0][3] = Point( 11*w/16, 13*w/16 );
rook_points[0][4] = Point( 19*w/32, 3*w/8 );
rook_points[0][5] = Point( 3*w/4, 3*w/8 );
rook_points[0][6] = Point( 3*w/4, w/8 );
rook_points[0][7] = Point( 26*w/40, w/8 );
rook_points[0][8] = Point( 26*w/40, w/4 );
rook_points[0][9] = Point( 22*w/40, w/4 );
rook_points[0][10] = Point( 22*w/40, w/8 );
rook_points[0][11] = Point( 18*w/40, w/8 );
rook_points[0][12] = Point( 18*w/40, w/4 );
rook_points[0][13] = Point( 14*w/40, w/4 );
rook_points[0][14] = Point( 14*w/40, w/8 );
rook_points[0][15] = Point( w/4, w/8 );
rook_points[0][16] = Point( w/4, 3*w/8 );
rook_points[0][17] = Point( 13*w/32, 3*w/8 );
rook_points[0][18] = Point( 5*w/16, 13*w/16 );
rook_points[0][19] = Point( w/4, 13*w/16 );
const Point* ppt[1] = { rook_points[0] };
int npt[] = { 20 };
fillPoly( img,
ppt,
npt,
1,
Scalar( 255, 255, 255 ),
lineType );
}
//![my_polygon]
/**
* @function MyLine
* @brief Draw a simple line
*/
//![my_line]
void MyLine( Mat img, Point start, Point end )
{
int thickness = 2;
int lineType = LINE_8;
line( img,
start,
end,
Scalar( 0, 0, 0 ),
thickness,
lineType );
}
1.由于我们计划画两个例子(原子和一个木棒),我们必须创建两个图像和两个窗口来显示它们。
char atom_window[] = "Drawing 1: Atom";
char rook_window[] = "Drawing 2: Rook";
Mat atom_image = Mat::zeros( w, w, CV_8UC3 );
Mat rook_image = Mat::zeros( w, w, CV_8UC3 );
2.我们创建了绘制不同几何形状的函数。例如,为了画原子,我们使用了MyEngEs和MyFilledCircle:
MyEllipse( atom_image, 90 );
MyEllipse( atom_image, 0 );
MyEllipse( atom_image, 45 );
MyEllipse( atom_image, -45 );
MyFilledCircle( atom_image, Point( w/2.0, w/2.0) );
3.为了绘制木屋,我们采用了MyLoin
、 rectangle
和MyPosion
:
MyPolygon( rook_image );
rectangle( rook_image,
Point( 0, 7*w/8.0 ),
Point( w, w),
Scalar( 0, 255, 255 ),
-1,
8 );
MyLine( rook_image, Point( 0, 15*w/16 ), Point( w, 15*w/16 ) );
MyLine( rook_image, Point( w/4, 7*w/8 ), Point( w/4, w ) );
MyLine( rook_image, Point( w/2, 7*w/8 ), Point( w/2, w ) );
MyLine( rook_image, Point( 3*w/4, 7*w/8 ), Point( 3*w/4, w ) );
4.让我们检查一下这些函数的内部内容:
MyLine
void MyLine( Mat img, Point start, Point end )
{
int thickness = 2;
int lineType = 8;
line( img, start, end,
Scalar( 0, 0, 0 ),
thickness,
lineType );
}
正如我们所看到的,MyLayle只调用函数cv::Load
,它执行以下操作:
从点到点画一条线
该线显示在图像IMG中。
线条颜色由标量(0, 0, 0)定义,这是布莱克的RGB值。
线的厚度设置为厚度(在这种情况下为2)。
该线是8连通的(LyeType=8)。
MyEllipse
void MyEllipse( Mat img, double angle )
{
int thickness = 2;
int lineType = 8;
ellipse( img,
Point( w/2.0, w/2.0 ),
Size( w/4.0, w/16.0 ),
angle,
0,
360,
Scalar( 255, 0, 0 ),
thickness,
lineType );
}
从上面的代码中,我们可以看到函数cv::fillPoly
绘制椭圆,使得:
椭圆在图像IMG中显示。
椭圆中心位于点**(w/2,w/2),并被封装在大小为的框中(w/4,w/16)**。
椭圆旋转角度
椭圆在0度和360度之间延伸一个弧。
图形的颜色将是标量(255, 0, 0),这意味着蓝色在RGB值。
椭圆的厚度为2。
MyFilledCircle
void MyFilledCircle( Mat img, Point center )
{
int thickness = -1;
int lineType = 8;
circle( img,
center,
w/32.0,
Scalar( 0, 0, 255 ),
thickness,
lineType );
}
类似于椭圆函数,我们可以观察到圆圈作为参数接收:
将显示圆的图像(IMG)
以圆心为中心的圆的中心
圆的半径:W/32
圆的颜色:标量(0, 0, 255),表示BGR中的红色。
由于厚度=1,将画圆填充。
MyPolygon
void MyPolygon( Mat img )
{
int lineType = 8;
/* Create some points */
Point rook_points[1][20];
rook_points[0][0] = Point( w/4.0, 7*w/8.0 );
rook_points[0][1] = Point( 3*w/4.0, 7*w/8.0 );
rook_points[0][2] = Point( 3*w/4.0, 13*w/16.0 );
rook_points[0][3] = Point( 11*w/16.0, 13*w/16.0 );
rook_points[0][4] = Point( 19*w/32.0, 3*w/8.0 );
rook_points[0][5] = Point( 3*w/4.0, 3*w/8.0 );
rook_points[0][6] = Point( 3*w/4.0, w/8.0 );
rook_points[0][7] = Point( 26*w/40.0, w/8.0 );
rook_points[0][8] = Point( 26*w/40.0, w/4.0 );
rook_points[0][9] = Point( 22*w/40.0, w/4.0 );
rook_points[0][10] = Point( 22*w/40.0, w/8.0 );
rook_points[0][11] = Point( 18*w/40.0, w/8.0 );
rook_points[0][12] = Point( 18*w/40.0, w/4.0 );
rook_points[0][13] = Point( 14*w/40.0, w/4.0 );
rook_points[0][14] = Point( 14*w/40.0, w/8.0 );
rook_points[0][15] = Point( w/4.0, w/8.0 );
rook_points[0][16] = Point( w/4.0, 3*w/8.0 );
rook_points[0][17] = Point( 13*w/32.0, 3*w/8.0 );
rook_points[0][18] = Point( 5*w/16.0, 13*w/16.0 );
rook_points[0][19] = Point( w/4.0, 13*w/16.0) ;
const Point* ppt[1] = { rook_points[0] };
int npt[] = { 20 };
fillPoly( img,
ppt,
npt,
1,
Scalar( 255, 255, 255 ),
lineType );
}
为了画一个填充多边形,我们使用函数cv:fIyPull。我们注意到:
多边形将绘制在IMG上。
多边形的顶点是PPT中的点集。
要绘制的顶点的总数是NPT
绘制的多边形数仅为1个。
多边形的颜色由标量(255, 255, 255)定义,这是白色的BGR值。
rectangle
rectangle( rook_image,
Point( 0, 7*w/8.0 ),
Point( w, w),
Scalar( 0, 255, 255 ),
-1, 8 );
最后,我们有了cv::rectangle
函数(我们没有为这个家伙创建一个特殊的函数)。我们注意到:
矩形将绘制在Rookx图像上。
矩形的两个相对顶点由**点(0, 7×w/8)**和点(w,w)**定义。
矩形的颜色由标量(0, 255, 255)给出,这是黄色的BGR值。
由于厚度值由** -1 **给出,矩形将被填充。
//插入文字
//参数为:承载的图片,插入的文字,文字的位置(文本框左下角),字体,大小,颜色
string words= "good luck";
putText( picture, words, Point( picture.rows/2,picture.cols/4),CV_FONT_HERSHEY_COMPLEX, 1, Scalar(255, 0, 0) );
imshow("1.jpg",picture);
//参数为:承载的图像、圆心、半径、颜色、粗细、线型、最后一个0
circle(picture,center,r,Scalar(0,0,0));
椭圆
//参数为:承载的图像、圆心、长短轴、径向夹角(水平面到长轴的夹角)、起始角度(长轴到起始边沿的夹角)、结束角度(长轴到结束点的夹角)、倾斜的矩形(可选项)、颜色、粗细、线性、偏移
ellipse(picture,center,Size( 250, 100 ),0,30,240,Scalar(0,0,0));
line:
//画线
Point a = Point (600,600);
//参数为:承载的图像、起始点、结束点、颜色、粗细、线型
line(picture,a,center,Scalar(255,0,0));
imshow("1.jpg",picture);
矩形:
//画矩形
//参数为:承载的图像、顶点、对角点、颜色(这里是蓝色)、粗细、大小
rectangle(picture,a,center,Scalar(255,0,0));
imshow("底板",picture);
傅立叶变换将图像分解成其正弦和余弦分量。换句话说,它将将图像从其空间域转换到其频域。其思想是任何函数都可以用无穷窦和余弦函数之和精确地近似。
变换的结果是复数。通过真实图像和复数图像或通过幅度和相位图像来显示这一点是可能的。然而,在整个图像处理算法中,只有幅度图像是有趣的,因为这包含了关于图像几何结构所需的所有信息。然而,如果你打算对这些表单中的图像做一些修改,然后你需要重新变换它,你就需要保存这两个。
在这个示例中,我将展示如何计算和显示傅立叶变换的幅度图像。在数字图像的情况下是离散的。这意味着它们可以从给定的域值中获取一个值。例如,在基本灰度值中,图像值通常介于0和255之间。因此,傅立叶变换也需要是离散的类型,导致离散傅立叶变换(DFT)。每当你需要从几何的角度来确定图像的结构时,你就需要使用它。这里是要遵循的步骤(在灰度输入图像I的情况下):
1.Expand the image to an optimal size将图像扩展到最佳大小。
DFT的性能取决于图像的大小。它往往是最快的图像大小是倍数的二,三和五。因此,为了达到最大的性能,通常将一个边界值映射到图像以获得具有这些特征的大小是一个好主意。CV::GoDestaldDftsie()
返回这个最佳大小,我们可以使用CV::CopyMaBurdReand()
函数来扩展图像的边框:
Mat padded; //expand input image to optimal size
int m = getOptimalDFTSize( I.rows );
int n = getOptimalDFTSize( I.cols ); // on the border add zero pixels
copyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, BORDER_CONSTANT, Scalar::all(0));
2.Make place for both the complex and the real values为复杂的和真实的价值创造位置。
傅立叶变换的结果是复杂的。这意味着对于每个图像值,结果是两个图像值(每个分量一个)。此外,频域范围远大于其空间对应部分。因此,我们通常至少以浮点格式存储这些文件。因此,我们将将输入图像转换为这种类型,并将其扩展到另一个通道以保存复数值:
Mat planes[] = {Mat_(padded), Mat::zeros(padded.size(), CV_32F)};
Mat complexI;
merge(planes, 2, complexI); // Add to the expanded another plane with zeros
3.Make the Discrete Fourier Transform进行离散傅立叶变换。
有可能进行就地计算(与输出相同的输入):
4.Transform the real and complex values to magnitude将实数和复值变换为幅值。
split(complexI, planes); // planes[0] = Re(DFT(I), planes[1] = Im(DFT(I))
magnitude(planes[0], planes[1], planes[0]);// planes[0] = magnitude
Mat magI = planes[0];
5.Switch to a logarithmic scale切换到对数刻度。
结果表明,傅立叶系数的动态范围太大,不能在屏幕上显示。我们有一些小的和一些高度变化的值,我们不能像这样观察到。因此,高值将全部变为白色点,而小值则变为黑色。为了使用灰度值来可视化,我们可以将我们的线性尺度转换成对数尺度
翻译成OpenCV代码:
magI += Scalar::all(1); // switch to logarithmic scale
log(magI, magI);
6.Crop and rearrange
magI = magI(Rect(0, 0, magI.cols & -2, magI.rows & -2));
int cx = magI.cols/2;
int cy = magI.rows/2;
Mat q0(magI, Rect(0, 0, cx, cy)); // Top-Left - Create a ROI per quadrant
Mat q1(magI, Rect(cx, 0, cx, cy)); // Top-Right
Mat q2(magI, Rect(0, cy, cx, cy)); // Bottom-Left
Mat q3(magI, Rect(cx, cy, cx, cy)); // Bottom-Right
Mat tmp; // swap quadrants (Top-Left with Bottom-Right)
q0.copyTo(tmp);
q3.copyTo(q0);
tmp.copyTo(q3);
q1.copyTo(tmp); // swap quadrant (Top-Right with Bottom-Left)
q2.copyTo(q1);
tmp.copyTo(q2);
请记住,在第一步,我们扩大了图像?是时候抛弃新引进的价值观了。为了可视化目的,我们也可以重新排列结果的象限,以便原点(0,0)对应于图像中心。
7.Normalize.归一化。
这是为了可视化目的再次进行的。我们现在有了大小,但是这仍然是我们的图像显示范围为0至1。我们使用CV::标准化()函数将我们的值标准化到这个范围
normalize(magI, magI, 0, 1, NORM_MINMAX); // Transform the matrix with float values into a
// viewable image form (float between values 0 and 1).
cv::FileStorage(const string& source, int flags, const string& encoding=string());
参数:
**source –**存储或读取数据的文件名(字符串),其扩展名(.xml 或 .yml/.yaml)决定文件格式。
flags – 操作模式,包括:
- FileStorage::READ 打开文件进行读操作
- FileStorage::WRITE 打开文件进行写操作
- FileStorage::APPEND打开文件进行附加操作
- FileStorage::MEMORY 从source读数据,或向内部缓存写入数据(由FileStorage::release返回)
encoding – 文件编码方式。目前不支持UTF-16 XML 编码,应使用 8-bit 编码
演示写入数值、矩阵、多个变量、当前时间和关闭文件:
// 1.create our writter
cv::FileStorage fs("test.yml", FileStorage::WRITE);
// 2.Save an int
int imageWidth= 5;
int imageHeight= 10;
fs << "imageWidth" << imageWidth;
fs << "imageHeight" << imageHeight;
// 3.Write a Mat
cv::Mat m1= Mat::eye(3,3, CV_8U);
cv::Mat m2= Mat::ones(3,3, CV_8U);
cv::Mat resultMat= (m1+1).mul(m1+2);
fs << "resultMat" << resultMat;
// 4.Write multi-variables
cv::Mat cameraMatrix = (Mat_(3,3) << 1000, 0, 320, 0, 1000, 240, 0, 0, 1);
cv::Mat distCoeffs = (Mat_(5,1) << 0.1, 0.01, -0.001, 0, 0);
fs << "cameraMatrix" << cameraMatrix << "distCoeffs" << distCoeffs;
// 5.Save local time
time_t rawtime; time(&rawtime); //#include
fs << "calibrationDate" << asctime(localtime(&rawtime));
// 6.close the file opened
fs.release();