在这里,您将了解关于库的基本构建块。必须阅读并了解如何在像素级上操作图像。
Mat-基本的图像容器
您将了解如何在内存中存储映像,以及如何将其内容打印到控制台。
1.Mat不需要手动分配它的内存,也并在不需要释放
2.通过让两个Mat对象的矩阵指针指向相同的地址,来避免数据复制导致计算速度降低。赋值操作符和复制构造函数只复制指向矩阵的头,而不是数据本身。
Mat A,C; //只创建标题部分
A = imread(argv[1], IMREAD_COLOR); //这里我们将知道使用的方法(分配矩阵)
Mat B (A); //使用复制构造函数
C = A; //赋值运算符
Mat D (A, Rect(10,10,100, 100)); //使用矩形
Mat D =A(Range::all(), Range(1,3));//使用行和列边界
3.可以使用cv::Mat::clone()和cv::Mat::copyTo()函数复制图像的底层矩阵
Mat F = A.clone();
Mat G;
A.copyTo(G);
4.可以使用Mat的<<运算符来显示二维矩阵
Mat M(2,2, CV_8UC3, Scalar(0,0,255));
cout << "M = " << endl << " " << M << endl << endl;
CV_8UC3:CV_[8位][无符号的][类型前缀]C[通道号码]
5.使用C/ c++数组并通过构造函数进行初始化
int sz[3] = {2,2,2};
Mat L(3,sz, CV_8UC(1), Scalar::all(0));
M.create(4,4, CV_8UC(2));
cout << "M = "<< endl << " " << M << endl << endl;
如何扫描图像,查找表和时间测量
您将了解如何使用OpenCV扫描图像(遍历每个图像像素)
我们将寻求以下问题的答案:
如何遍历图像的每一个像素?
如何存储OpenCV矩阵值?
如何衡量我们算法的性能?
什么是查询表,为什么使用它们?
通道的顺序是相反的:BGR而不是RGB
使用cv::Mat::isContinuous()函数来询问矩阵是否连续的方式存储;
矩阵掩码运算
您将了解如何使用邻居访问扫描图像,并使用cv::filter2D函数对图像应用内核过滤器。
操作的图片
从文件中读取/写入图像,访问像素,原始操作,可视化图像。
1.文件的读写
Mat img = imread(filename);
Mat img = imread(filename, IMREAD_GRAYSCALE);
文件的格式由它的内容(前几个字节)决定,保存一个图像到一个文件。文件的格式由其扩展名决定。使用cv::imdecode和cv::imencode读取/写入图像从/到内存,而不是一个文件。
imwrite(filename, img);
2.为了得到像素强度值,你必须知道图像的类型和通道的数量。下面是一个单通道灰度图像(类型8UC1)和像素坐标x和y的例子:
Scalar intensity = img.at<uchar>(y, x);
Scalar intensity = img.at<uchar>(Point(x, y));
3.一个BGR颜色顺序的3通道图像(imread返回的默认格式):
Vec3b intensity = img.at<Vec3b>(y, x);
uchar blue = intensity.val[0];
uchar green = intensity.val[1];
uchar red = intensity.val[2];
4.以Mat的形式取一个二维或三维点数组,矩阵只包含一列,每一行对应一个点,对应的矩阵类型为32FC2或32FC3 vector<Point2f> points;
Mat pointsMat = Mat(points);
5.使用相同的方法可以访问矩阵中的一个点:
Point2f point = pointsMat.at<Point2f>(i, 0);
6.得到了一个3列的32FC1矩阵,而不是一个1列的32FC3矩阵
std::vector<Point3f> points;
Mat pointsMat = Mat(points).reshape(1);
7.复制
Mat img = imread("image.jpg");
Mat img1 = img.clone();
8.例如,下面是我们如何从现有的灰度图像img中创建一个黑色图像
img = Scalar(0);
9.选择感兴趣的地区:
Rect r(10, 10, 100, 100);
Mat smallImg = img(r);
10.从彩色到灰度的转换:
Mat img = imread("image.jpg");
Mat grey;
cvtColor(img, grey, COLOR_BGR2GRAY);
11.图像类型由8UC1改为32FC1:
src.convertTo(dst, CV_32F);
12.图像可视化
Mat img = imread("image.jpg");
namedWindow("image", WINDOW_AUTOSIZE);
imshow("image", img);
waitKey();
Mat img = imread("image.jpg");
Mat grey;
cvtColor(img, grey, COLOR_BGR2GRAY);
Mat sobelx;
Sobel(grey, sobelx, CV_32F, 1, 0);
double minVal, maxVal;
minMaxLoc(sobelx, &minVal, &maxVal); //找出最小和最大强度
Mat draw;
sobelx.convertTo(draw, CV_8U, 255.0/(maxVal - minVal), -minVal * 255.0/(maxVal - minVal));
namedWindow("image", WINDOW_AUTOSIZE);
imshow("image", draw);
waitKey();
添加(混合)两个图像
将学习如何混合两个图像!
在本教程中,您将学习:
什么是线性混合,为什么它有用?
如何使用addWeighted()添加两个图像
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
// we're NOT "using namespace std;" here, to avoid collisions between the beta variable and std::beta in c++17
using std::cin;
using std::cout;
using std::endl;
int main( void )
{
double alpha = 0.5; double beta; double input;
Mat src1, src2, dst;
cout << " Simple Linear Blender " << endl;
cout << "-----------------------" << endl;
cout << "* Enter alpha [0.0-1.0]: ";
cin >> input;
// We use the alpha provided by the user if it is between 0 and 1
if( input >= 0 && input <= 1 )
{ alpha = input; }
src1 = imread( samples::findFile("LinuxLogo.jpg") );
src2 = imread( samples::findFile("WindowsLogo.jpg") );
if( src1.empty() ) { cout << "Error loading src1" << endl; return EXIT_FAILURE; }
if( src2.empty() ) { cout << "Error loading src2" << endl; return EXIT_FAILURE; }
beta = ( 1.0 - alpha );
addWeighted( src1, alpha, src2, beta, 0.0, dst);
imshow( "Linear Blend", dst );
waitKey(0);
return 0;
}
使用imread读取图片,addWeighted进行混合叠加,src1和src2图片的尺寸和类型一致,alpha为叠加前景的透明度,调用addWeighted实现叠加
改变图像的对比度和亮度
将学习如何改变我们的形象外观!
在本教程中,您将学习如何:
获取像素值
用零初始化一个矩阵
了解cv::saturate_cast的作用,及它为什么有用
获取一些关于像素变换的很酷的信息
提高图像亮度的实例
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
// we're NOT "using namespace std;" here, to avoid collisions between the beta variable and std::beta in c++17
using std::cin;
using std::cout;
using std::endl;
using namespace cv;
int main( int argc, char** argv )
{
CommandLineParser parser( argc, argv, "{@input | lena.jpg | input image}" );
Mat image = imread( samples::findFile( parser.get<String>( "@input" ) ) );
if( image.empty() )
{
cout << "Could not open or find the image!\n" << endl;
cout << "Usage: " << argv[0] << " <Input image>" << endl;
return -1;
}
Mat new_image = Mat::zeros( image.size(), image.type() );
double alpha = 1.0; /*< Simple contrast control */
int beta = 0; /*< Simple brightness control */
cout << " Basic Linear Transforms " << endl;
cout << "-------------------------" << endl;
cout << "* Enter the alpha value [1.0-3.0]: "; cin >> alpha;
cout << "* Enter the beta value [0-100]: "; cin >> beta;
for( int y = 0; y < image.rows; y++ ) {
for( int x = 0; x < image.cols; x++ ) {
for( int c = 0; c < image.channels(); c++ ) {
new_image.at<Vec3b>(y,x)[c] =
saturate_cast<uchar>( alpha*image.at<Vec3b>(y,x)[c] + beta );
}
}
}
imshow("Original Image", image);
imshow("New Image", new_image);
waitKey();
return 0;
}
imread读取原图片
通过 Mat::zeros( image.size(), image.type() )创建和原图片尺寸和类型一致的新图片设置对比度和亮度
通过new_image.at<Vec3b>(y,x)[c]设置每一个像素点的值;
离散傅里叶变换
你会看到如何以及为什么在OpenCV中使用离散傅里叶变换。
我们将寻求以下问题的答案:
什么是傅里叶变换,为什么要用它?
在OpenCV中怎么做?
使用诸如:copyMakeBorder()、merge()、dft()、getOptimalDFTSize()、log()和normalize()等函数。
#include "opencv2/core.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
static void help(char ** argv)
{
cout << endl
<< "This program demonstrated the use of the discrete Fourier transform (DFT). " << endl
<< "The dft of an image is taken and it's power spectrum is displayed." << endl << endl
<< "Usage:" << endl
<< argv[0] << " [image_name -- default lena.jpg]" << endl << endl;
}
int main(int argc, char ** argv)
{
help(argv);
const char* filename = argc >=2 ? argv[1] : "lena.jpg";
Mat I = imread( samples::findFile( filename ), IMREAD_GRAYSCALE);
if( I.empty()){
cout << "Error opening image" << endl;
return EXIT_FAILURE;
}
//DFT的性能取决于图像的大小,对于2、3和5的倍数的图像大小,它往往是最快的。因此,为了获得最大的性能,通常将边界值填充到图像中,得到具有特征的大小。
Mat padded;
//获取最佳大小
int m = getOptimalDFTSize( I.rows );
int n = getOptimalDFTSize( I.cols );
//扩展图像的边界(附加的像素被初始化为零)
copyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, BORDER_CONSTANT, Scalar::all(0));
//让复杂的和真实的价值都有一席之地,傅里叶变换的结果是复数的。这意味着对于每个图像值,结果是两个图像值(每个组件一个)。此外,它的频域范围要比它的空间对应物大得多。因此,我们通常至少以浮点格式存储它们。因此,我们将把输入图像转换为这种类型,然后用另一个通道展开它,以保存复数值:
Mat planes[] = {Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F)};
Mat complexI;
merge(planes, 2, complexI); // 在展开的平面上加上另一个零
//进行离散傅里叶变换
dft(complexI, complexI);
// 计算大小和切换到对数尺度
// => log(1 + sqrt(Re(DFT(I))^2 + Im(DFT(I))^2))
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];
magI += Scalar::all(1); // switch to logarithmic scale
log(magI, magI);
// 如果频谱的行数或列数为奇数,则对其进行裁剪
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);
normalize(magI, magI, 0, 1, NORM_MINMAX); // Transform the matrix with float values into a
// viewable image form (float between values 0 and 1).
imshow("Input Image" , I ); // Show the result
imshow("spectrum magnitude", magI);
waitKey();
return EXIT_SUCCESS;
}
一个应用的想法是确定图像中呈现的几何方向。例如,让我们看看一个文本是否水平?看一些文本,你会注意到文本线也有水平线字母也有垂直线。在傅里叶变换的情况下,也可以看到文本片段的这两个主要成分。让我们使用这个水平图像和这个旋转的文本图像。
您可以看到,频域最有影响力的分量(亮度图像上最亮的点)跟随图像上对象的几何旋转。由此,我们可以计算偏移量,并执行图像旋转来纠正最终错过的对准。
使用XML和YAML进行文件输入和输出
您将看到如何使用OpenCV的cv::FileStorage数据结构将数据读写为XML或YAML文件格式。
你会找到以下问题的答案:
如何打印和读取文本条目到文件和OpenCV使用YAML或XML文件?
如何做同样的OpenCV数据结构?
如何为你的数据结构做到这一点?
使用OpenCV数据结构,如cv::FileStorage, cv::FileNode或cv::FileNodeIterator。
如何使用OpenCV的parallel_for_并行化你的代码
您将看到如何使用OpenCV parallel_for_轻松地并行化代码。