/*
* 3calibration.cpp -- Calibrate 3 cameras in a horizontal line together.
*/
#include "opencv2/calib3d.hpp" //相机校准和3D重建
#include "opencv2/imgproc.hpp"//图像处理模块
#include "opencv2/imgcodecs.hpp" //该类常用于媒体资源文件的读写,如imread()与imwrite(),在没有指明特定情况下,Mat对象的加载模式为CV_8UC3
#include "opencv2/highgui.hpp" //OpenCV高级GUI和媒体(highgui模块)
#include "opencv2/core/utility.hpp" //
#include <stdio.h>
#include <string.h>
#include <time.h>
using namespace cv;
using namespace std;
enum { DETECTION = 0, CAPTURING = 1, CALIBRATED = 2 };
static void help(char** argv)
{
printf( "\n这是一个相机校准示例,用于校准3个水平放置的相机.\n"
"Usage: %s\n"
" -w=<board_width> # the number of inner corners per one of board dimension每一个板维度的内角数 \n"
" -h=<board_height> # the number of inner corners per another board dimension另一个维度板的内角数\n"
" [-s=<squareSize>] # square size in some user-defined units (1 by default)标定板网格尺寸大小(默认为 1)\n"
" [-o=<out_camera_params>] # the output filename for intrinsic [and extrinsic] parameters相机内参或者外参的输出文件名\n"
" [-zt] # assume zero tangential distortion假设零切向畸变\n"
" [-a=<aspectRatio>] # fix aspect ratio (fx/fy)修复纵横比 (fx/fy)\n"
" [-p] # fix the principal point at the center将主点固定在中心\n"
" [input_data] # input data - text file with a list of the images of the board输入数据 - 带有网格板图像列表的文本文件\n"
"\n", argv[0] );
}
//计算网格板理论角点 网格板行列数,网格尺寸,角点列表
static void calcChessboardCorners(Size boardSize, float squareSize, vector<Point3f>& corners)
{
corners.resize(0);//角点清空
for( int i = 0; i < boardSize.height; i++ )
for( int j = 0; j < boardSize.width; j++ )
corners.push_back(Point3f(float(j*squareSize),
float(i*squareSize), 0));//理论角点坐标
}
//运行校准 3个水平放置的相机
static bool run3Calibration(vector<vector<Point2f> > imagePoints1,//图像1角点列表
vector<vector<Point2f> > imagePoints2,//图像2角点列表
vector<vector<Point2f> > imagePoints3,//图像3角点列表
Size imageSize, Size boardSize, //图像尺寸,网格板尺寸
float squareSize, float aspectRatio, //网格尺寸,比例
int flags, //标志位
Mat& cameraMatrix1, Mat& distCoeffs1,//相机1矩阵,畸变系数1
Mat& cameraMatrix2, Mat& distCoeffs2,//相机2矩阵,畸变系数2
Mat& cameraMatrix3, Mat& distCoeffs3,//相机3矩阵,畸变系数3
Mat& R12, Mat& T12, Mat& R13, Mat& T13) //相机1与2的RT 相机1与3的RT
{
int c, i;
// step 1:单独校准每个相机 calibrate each camera individually
vector<vector<Point3f> > objpt(1);//理论角点坐标 二维数组
vector<vector<Point2f> > imgpt;// 识别的角点像素坐标 二维数组
calcChessboardCorners(boardSize, squareSize, objpt[0]);//计算理论角点坐标
vector<Mat> rvecs, tvecs;//R T 矢量
for( c = 1; c <= 3; c++ )//遍历三个相机
{
const vector<vector<Point2f> >& imgpt0 = c == 1 ? imagePoints1 : c == 2 ? imagePoints2 : imagePoints3;//相机c 识别的标定板角点
imgpt.clear();
int N = 0;
for( i = 0; i < (int)imgpt0.size(); i++ )
if( !imgpt0[i].empty() )
{
imgpt.push_back(imgpt0[i]);//相机c 识别的角点列表 imgpt0[i]:第i行 角点。
N += (int)imgpt0[i].size();//角点总数
}
if( imgpt.size() < 3 )//角点行数<3
{
printf("Error: not enough views for camera %d\n", c);//相机c 视图不够
return false;
}
objpt.resize(imgpt.size(),objpt[0]);//理论角点与识别的角点 统一数量
Mat cameraMatrix = Mat::eye(3, 3, CV_64F);//相机内参矩阵
if( flags & CALIB_FIX_ASPECT_RATIO )
cameraMatrix.at<double>(0,0) = aspectRatio;
Mat distCoeffs = Mat::zeros(5, 1, CV_64F);//畸变系数
//校准相机 理论角点,识别的角点,图像尺寸,相机内参矩阵,畸变系数矩阵,旋转矩阵矢量,平移矢量,
double err = calibrateCamera(objpt, imgpt, imageSize, cameraMatrix,
distCoeffs, rvecs, tvecs,
flags|CALIB_FIX_K3/*|CALIB_FIX_K4|CALIB_FIX_K5|CALIB_FIX_K6*/);
bool ok = checkRange(cameraMatrix) && checkRange(distCoeffs);//检查参数范围
if(!ok)
{
printf("Error: camera %d was not calibrated\n", c);//相机校准失败
return false;
}
printf("Camera %d calibration reprojection error = %g\n", c, sqrt(err/N));//相机c校准投影误差
if( c == 1 )
cameraMatrix1 = cameraMatrix, distCoeffs1 = distCoeffs;
else if( c == 2 )
cameraMatrix2 = cameraMatrix, distCoeffs2 = distCoeffs;
else
cameraMatrix3 = cameraMatrix, distCoeffs3 = distCoeffs;
}
vector<vector<Point2f> > imgpt_right;//像素点 补偿
// step 2: calibrate (1,2) and (3,2) pairs 校准(1,2)和(3,2)对
for( c = 2; c <= 3; c++ )
{
const vector<vector<Point2f> >& imgpt0 = c == 2 ? imagePoints2 : imagePoints3;//相机c 识别的像素坐标
imgpt.clear();
imgpt_right.clear();
int N = 0;
for( i = 0; i < (int)std::min(imagePoints1.size(), imgpt0.size()); i++ )
if( !imagePoints1.empty() && !imgpt0[i].empty() )//相机1和 相机c 角点像素坐标矢量非空
{
imgpt.push_back(imagePoints1[i]);//相机1识别的角点像素坐标第i行
imgpt_right.push_back(imgpt0[i]);//相机c识别的角点像素坐标第i行
N += (int)imgpt0[i].size();//角点数量
}
if( imgpt.size() < 3 )//角点坐标矢量 需三行以上
{
printf("Error: not enough shared views for cameras 1 and %d\n", c);//摄像机 1 和 c 的共享视图不足
return false;
}
//objpt 存储标定角点在世界坐标系中的位置
objpt.resize(imgpt.size(),objpt[0]);//理论角点二维数组 与相机1 统一行、列数
Mat cameraMatrix = c == 2 ? cameraMatrix2 : cameraMatrix3;//相机c内参矩阵
Mat distCoeffs = c == 2 ? distCoeffs2 : distCoeffs3;//相机c畸变系数
Mat R, T, E, F;
//objpt 存储标定角点在世界坐标系中的位置
//imgpt 存储标定角点在第一个摄像机下的投影后的亚像素坐标
//imgpt_right 存储标定角点在第二个摄像机下的投影后的亚像素坐标
//cameraMatrix1 输入/输出型的第一个摄像机的相机矩阵
//distCoeffs1 第一个摄像机的输入/输出型畸变向量
//cameraMatrix 输入/输出型的第二个摄像机的相机矩阵
//distCoeffs 第二个摄像机的输入/输出型畸变向量
//imageSize-图像的大小
//R-输出型,第一和第二个摄像机之间的旋转矩阵
//T - 输出型,第一和第二个摄像机之间的平移矩阵
//E - 输出型,基本矩阵
//F - 输出型,基础矩阵
//CALIB_FIX_INTRINSIC 如果该标志被设置,那么就会固定输入的cameraMatrix和distCoeffs不变,只求解R,T,E,F
//TermCriteria-迭代优化的终止条件
double err = stereoCalibrate(objpt, imgpt, imgpt_right, cameraMatrix1, distCoeffs1,
cameraMatrix, distCoeffs,
imageSize, R, T, E, F,
CALIB_FIX_INTRINSIC,
TermCriteria(TermCriteria::COUNT, 30, 0));//标定一个立体摄像头的,也就是同时标定两个摄像头。标定的结果除了能够求出两个摄像头的内外参数矩阵,跟能够得出两个摄像头的位置关系R,T。
printf("Pair (1,%d) calibration reprojection error = %g\n", c, sqrt(err/(N*2)));//相机1与c的校准投影误差
if( c == 2 )
{
cameraMatrix2 = cameraMatrix;
distCoeffs2 = distCoeffs;
R12 = R; T12 = T;
}
else
{
R13 = R; T13 = T;
}
}
return true;
}
//从FileStorage文件读取文件名列表
static bool readStringList( const string& filename, vector<string>& l )
{
l.resize(0);//重置字符串列表
FileStorage fs(filename, FileStorage::READ);
if( !fs.isOpened() )
return false;
FileNode n = fs.getFirstTopLevelNode();//返回映射(mapping)顶层的第一个元素:
if( n.type() != FileNode::SEQ )
return false;
FileNodeIterator it = n.begin(), it_end = n.end();
for( ; it != it_end; ++it )
l.push_back((string)*it);
return true;
}
int main( int argc, char** argv )
{
int i, k;
int flags = 0;
Size boardSize, imageSize;
float squareSize, aspectRatio;
string outputFilename;
string inputFilename = "";
vector<vector<Point2f> > imgpt[3];//三个相机识别的标定板角点
vector<string> imageList;//图像列表
cv::CommandLineParser parser(argc, argv,
"{help ||}{w||}{h||}{s|1|}{o|out_camera_data.yml|}"
"{zt||}{a|1|}{p||}{@input||}");
if (parser.has("help"))
{
help(argv);
return 0;
}
boardSize.width = parser.get<int>("w");//标定板宽度
boardSize.height = parser.get<int>("h");//标定板高度
squareSize = parser.get<float>("s");//网格尺寸
aspectRatio = parser.get<float>("a");//纵横比 1
if (parser.has("a"))
flags |= CALIB_FIX_ASPECT_RATIO;
if (parser.has("zt"))
flags |= CALIB_ZERO_TANGENT_DIST;
if (parser.has("p"))
flags |= CALIB_FIX_PRINCIPAL_POINT;
outputFilename = parser.get<string>("o");//输出文件名 默认 out_camera_data.yml
inputFilename = parser.get<string>("@input"); //输入图片列表文件名
if (!parser.check())
{
help(argv);
parser.printErrors();
return -1;
}
if (boardSize.width <= 0)
return fprintf( stderr, "Invalid board width\n" ), -1;
if (boardSize.height <= 0)
return fprintf( stderr, "Invalid board height\n" ), -1;
if (squareSize <= 0)
return fprintf( stderr, "Invalid board square width\n" ), -1;
if (aspectRatio <= 0)
return printf("无效的纵横比\n" ), -1;
if( inputFilename.empty() ||
!readStringList(inputFilename, imageList) || //读取图片列表
imageList.size() == 0 || imageList.size() % 3 != 0 )
{
printf("错误:输入图像列表未指定,或无法读取,或文件数不能被3整除\n");
return -1;
}
Mat view, viewGray;
Mat cameraMatrix[3], distCoeffs[3], R[3], P[3], R12, T12;
for( k = 0; k < 3; k++ )
{//初始化内参矩阵3X3,畸变系数5X1
cameraMatrix[k] = Mat_<double>::eye(3,3);
cameraMatrix[k].at<double>(0,0) = aspectRatio;//1 x方向与y方向比例?
cameraMatrix[k].at<double>(1,1) = 1;
distCoeffs[k] = Mat_<double>::zeros(5,1);
}
Mat R13=Mat_<double>::eye(3,3), T13=Mat_<double>::zeros(3,1);//相机3相对1的位姿矩阵
FileStorage fs;
namedWindow( "Image View", 0 );
for( k = 0; k < 3; k++ )
imgpt[k].resize(imageList.size()/3);//相机k+1 识别的角点矢量初始化
//获取所有相机所有图像的识别角点
for( i = 0; i < (int)(imageList.size()/3); i++ )//遍历每个相机采集的图像
{
for( k = 0; k < 3; k++ )//三个相机
{
int k1 = k == 0 ? 2 : k == 1 ? 0 : 1;//k1= 2,0,1
printf("%s\n", imageList[i*3+k].c_str());//读取相机k的第i张图像
view = imread(imageList[i*3+k], 1);
if(!view.empty())
{
vector<Point2f> ptvec;//角点向量
imageSize = view.size();
cvtColor(view, viewGray, COLOR_BGR2GRAY);//图像转灰度图
bool found = findChessboardCorners( view, boardSize, ptvec, CALIB_CB_ADAPTIVE_THRESH );//识别角点
drawChessboardCorners( view, boardSize, Mat(ptvec), found );//绘制角点
if( found )
{
imgpt[k1][i].resize(ptvec.size());//相机k1识别的角点矢量 第i张图像角点初始化
std::copy(ptvec.begin(), ptvec.end(), imgpt[k1][i].begin());//将识别的第i张图像角点拷贝给imgpt[k1][i]
}
//imshow("view", view);
//int c = waitKey(0) & 255;
//if( c == 27 || c == 'q' || c == 'Q' )
// return -1;
}
}
}
printf("Running calibration ...\n");
//校准三个相机
run3Calibration(imgpt[0], imgpt[1], imgpt[2], imageSize,
boardSize, squareSize, aspectRatio, flags|CALIB_FIX_K4|CALIB_FIX_K5,
cameraMatrix[0], distCoeffs[0],
cameraMatrix[1], distCoeffs[1],
cameraMatrix[2], distCoeffs[2],
R12, T12, R13, T13);
//保存校准参数
fs.open(outputFilename, FileStorage::WRITE);
fs << "cameraMatrix1" << cameraMatrix[0];
fs << "cameraMatrix2" << cameraMatrix[1];
fs << "cameraMatrix3" << cameraMatrix[2];
fs << "distCoeffs1" << distCoeffs[0];
fs << "distCoeffs2" << distCoeffs[1];
fs << "distCoeffs3" << distCoeffs[2];
fs << "R12" << R12;
fs << "T12" << T12;
fs << "R13" << R13;
fs << "T13" << T13;
fs << "imageWidth" << imageSize.width;
fs << "imageHeight" << imageSize.height;
Mat Q;
// step 3: find rectification transforms 计算三头相机的校正变换,其中所有头都在同一条线上
double ratio = rectify3Collinear(cameraMatrix[0], distCoeffs[0], cameraMatrix[1],
distCoeffs[1], cameraMatrix[2], distCoeffs[2],
imgpt[0], imgpt[2],
imageSize, R12, T12, R13, T13,
R[0], R[1], R[2], P[0], P[1], P[2], Q, -1.,
imageSize, 0, 0, CALIB_ZERO_DISPARITY);
Mat map1[3], map2[3];//
fs << "R1" << R[0];
fs << "R2" << R[1];
fs << "R3" << R[2];
fs << "P1" << P[0];
fs << "P2" << P[1];
fs << "P3" << P[2];
fs << "disparityRatio" << ratio;//视差比
fs.release();
printf("Disparity ratio = %g\n", ratio);
for( k = 0; k < 3; k++ )
initUndistortRectifyMap(cameraMatrix[k], distCoeffs[k], R[k], P[k], imageSize, CV_16SC2, map1[k], map2[k]);//计算无畸变和修正转换映射。
Mat canvas(imageSize.height, imageSize.width*3, CV_8UC3), small_canvas;
destroyWindow("view");
canvas = Scalar::all(0);//初始化三个相机采集的整体图像?
for( i = 0; i < (int)(imageList.size()/3); i++ )
{
canvas = Scalar::all(0);
for( k = 0; k < 3; k++ )
{
int k1 = k == 0 ? 2 : k == 1 ? 0 : 1;//2,0,1
int k2 = k == 0 ? 1 : k == 1 ? 0 : 2;//1,0,2
view = imread(imageList[i*3+k], 1);//依次读取相机0,1,2 采集的第k张图像
if(view.empty())
continue;
Mat rview = canvas.colRange(k2*imageSize.width, (k2+1)*imageSize.width);
remap(view, rview, map1[k1], map2[k1], INTER_LINEAR);//重映射,就是把一幅图像中某位置的像素放置到另一图像指定位置的过程
}
printf("%s %s %s\n", imageList[i*3].c_str(), imageList[i*3+1].c_str(), imageList[i*3+2].c_str());
resize( canvas, small_canvas, Size(1500, 1500/3), 0, 0, INTER_LINEAR_EXACT );
for( k = 0; k < small_canvas.rows; k += 16 )
line(small_canvas, Point(0, k), Point(small_canvas.cols, k), Scalar(0,255,0), 1);
imshow("rectified", small_canvas);
char c = (char)waitKey(0);
if( c == 27 || c == 'q' || c == 'Q' )
break;
}
return 0;
}
笔记:
1. OpenCV之Imgcodecs类读写
https://blog.youkuaiyun.com/kicinio/article/details/120686590
Imgcodecs
该类常用于媒体资源文件的读写,如imread()与imwrite(),在没有指明特定情况下,Mat对象的加载模式为CV_8UC3
imread(filePath):参数为被读取的文件路径
imwrite(filePath, Mat):第一个参数为待写入的文件路径,第二个为Mat对象
https://www.w3cschool.cn/opencv/opencv-lwfg2dr3.html
https://www.w3cschool.cn/opencv/
2. OpenCv学习笔记(二)------imgproc模块
https://blog.youkuaiyun.com/yijiull/article/details/81545619
线性滤波
形态学处理(Morphology Operations)
Image Pyramids
Thresholding Operations
边缘检测
检测直线
仿射变换
2.1 线性滤波
一般就是用来去噪点,用不同的卷积核进行操作,具体参数可以看官方文档。
blur()
GaussianBlur()
medianBlur()
bilateralFilter()
使用filter2D函数可以自定义自己的滤波。
filter2D(src, dst, ddepth , kernel, anchor, delta, BORDER_DEFAULT );
src:源图像
dst 目的地图像
ddepth:dst的深度。负值(例如−1) 表示深度与震源相同。
kernel:通过图像扫描的内核
anchor:锚相对于内核的位置。默认情况下,位置点(-1,-1)表示中心。
delta:在相关过程中添加到每个像素的值。默认值为0
BORDER_DEFAULT:我们默认使用这个值(更多细节在下面的教程中)
上面我们忽略了一个问题,那就是图像边缘部分没有办法应用核。需要用copyMakeBorder扩展边缘(有两种扩展方式)。
copyMakeBorder( src, dst, top, bottom, left, right, borderType, value );
src:源图像
dst 目的地图像
top, bottom, left, right:图像两侧边框的长度(以像素为单位)。我们将它们定义为图像原始大小的5%。
borderType:定义应用的边框类型。在这个例子中,它可以是常量,也可以是复制的。
value:如果borderType是BORDER_常量,则这是用于填充边框像素的值。
形态学处理(Morphology Operations)
形态学是一组图像处理操作,基于预定义的结构元素(也称为核)处理图像。
输出图像中每个像素的值基于输入图像中对应像素与其相邻像素的比较。
通过选择内核的大小和形状,可以构造对输入图像的特定形状敏感的形态学操作。
简单来说,就是预定义一个核(矩阵),对输入的图像进行处理,每个像素依赖于其周围的像素,采用不同的核得到不同的输出,所以核是关键。
腐蚀(Erosion)和膨胀(Dilation):
分别在卷积核范围内取最小、最大值。
由于图像是黑白图像,白色是主题pixel值大,黑色是背景。
- cv::erode:腐蚀运算,取小值,白色变黑,图形缩小。
- cv::dilate:膨胀运算,与腐蚀相反。
在这两种运算的基础上,还有Opening,Closing, Morphological Gradient, Top Hat,Black Hat运算,具体可以查看文档。
知道腐蚀和膨胀的原理之后,还可以提取图像中的特殊线条,比如横线竖线等。
可以学一下这个函数createTrackbar()显示一个进度条,便于调节参数直观看到效果
2.2 图像金字塔 Image Pyramids
图像金字塔?进行图像的缩放。
有两种常用的方法:
Gaussian pyramid
Laplacian pyramid
高斯核:
缩小时:对图像用高斯核进行卷积运算,然后去掉偶数行和列,将图案缩小至原来的四分之一。
放大时:用高斯核做卷积估计缺失的像素。
OpenCV里提供了两个函数:
pyrUp()
pyrDown()
2.3 阈值运算 Thresholding Operations
HSV色彩空间:
hue(色彩):描述颜色,根据颜色分割物体时有用
saturation(饱和度):从白逐渐变黑?
Value:描述颜色的亮度或者密度
RGB色彩空间:
每种颜色三个通道,无法用来根据颜色分割物体。
可以用cv::cvtColor()函数转换色彩空间
// Convert from BGR to HSV colorspace
cvtColor(frame, frame_HSV, COLOR_BGR2HSV);
threshold()函数:根据某个阈值将图像分割。
inRange(src, low, up, dst)函数:简单来说就是将src图像中颜色在low和up之间的像素提取到dst,是threshold函数的进阶版。
2.4 边缘检测
2.4.1 一阶导
这里用到了导数。图像边缘就是像素值变化较大的区域,即像素值变化率大,即导数值。
OpenCV提供了Sobel()函数。
Sobel(src_gray, grad_x, ddepth, 1, 0, ksize, scale, delta, BORDER_DEFAULT);
Sobel(src_gray, grad_y, ddepth, 0, 1, ksize, scale, delta, BORDER_DEFAULT);
src_gray:在我们的示例中,输入图像。这是CV_8U
grad_x/grad_y:输出图像。
ddepth:输出图像的深度。我们将其设置为CV_16S以避免溢出。
x_order:导数在x方向上的阶数。
y_order:导数在y方向上的阶数。
BORDER_DEFAULT、增量和边界默认值:我们使用默认值。
这里分别计算了x方向和y方向的边缘grad_x和grad_y,然后再相加就得到图像的边缘。
2.4.2 二阶导
还可以用二阶导数值为0来判断边缘,OpenCV提供了 Laplacian() 函数
2.4.3 Canny Edge Detector
cv::Canny函数
2.5 检测直线
在检测出边缘的基础上检测直线
上式中若 (x0,y0)(x0,y0)为定值,那么任意 (θ,rθ)(θ,rθ)对表示经过 (x0,y0)(x0,y0)的直线簇。
若三点的直线簇有公共直线,那么这三点共线。
HoughLines():得到检测到的直线的(θ,rθ)(θ,rθ)
HoughLinesP():得到直线的两端点
检测圆:先做边缘检测推断圆心,然后对待定圆心找到最合适的半径。用这个函数HoughCircles()。
2.6 仿射变换
cv::remap:对像素进行变换,比如水平翻转图像
getAffineTransform:得到仿射变换矩阵
cv::warpAffine:根据变换矩阵进行变换,扭曲?
cv::getRotationMatrix2D :旋转
2.7 OpenCV图像处理(imgproc模块)
OpenCV平滑图像
OpenCV侵蚀和扩张
OpenCV更多形态转化
OpenCV Hit-or-Miss
OpenCV通过使用形态学操作来提取水平和垂直线
OpenCV图像金字塔
OpenCV基本阈值操作
OpenCV使用inRange的阈值操作
OpenCV制作自己的线性滤镜
OpenCV添加边框到您的图像
OpenCV Sobel衍生物
OpenCV Laplace Operator
OpenCV Canny边缘检测器
OpenCV Hough Line变换
OpenCV Hough Circle 变换
OpenCV重新映射(Remapping)
OpenCV仿射变换
Opencv直方图同等化(Equalization)
OpenCV直方图计算
OpenCV直方图比较
OpenCV后投影
OpenCV模板匹配
OpenCV在图像中查找轮廓
Convex Hull
OpenCV为轮廓创建边界框和圆
Image Moments
OpenCV点多边形测试
距离变换的图像分割和Watershed算法
3. calib3d OpenCV相机校准和3D重建(calib3d模块)
相机校准与方形棋盘
使用OpenCV相机校准
OpenCV纹理对象的实时姿态估计
互动摄像机校准应用
4. OpenCV高级GUI和媒体(highgui模块)
在我们的应用程序中添加一个Trackbar
通过使用cv :: createTrackbar在OpenCV窗口中添加一个Trackbar
4.1 说明
我们只分析与Trackbar相关的代码:
首先,我们加载两个将要混合的图像。
src1 = imread(“../data/LinuxLogo.jpg”);
src2 = imread(“../data/WindowsLogo.jpg”);
要创建一个跟踪栏,首先我们必须创建一个它将被放置的窗口。所以:
namedWindow("Linear Blend", WINDOW_AUTOSIZE); // Create Window
现在我们可以创建Trackbar:
char TrackbarName [50];
sprintf(TrackbarName,“Alpha x%d”,alpha_slider_max);
createTrackbar(TrackbarName,“Linear Blend”,&alpha_slider,alpha_slider_max,on_trackbar);
请注意以下事项:
我们的Trackbar有一个标签TrackbarName
Trackbar位于名为Linear Blend的窗口中
Trackbar值的范围为到alpha_slider_max(最小限制始终为零)。0
Trackbar的数值存储在alpha_slider中
每当用户移动的TrackBar,回调函数on_trackbar被称为
最后,我们必须定义回调函数on_trackbar
static void on_trackbar( int, void* )
{
alpha = (double) alpha_slider/alpha_slider_max ;
beta = ( 1.0 - alpha );
addWeighted( src1, alpha, src2, beta, 0.0, dst);
imshow( "Linear Blend", dst );
}
注意:
我们使用alpha_slider(integer)的值来获取alpha的double值。
每次当用户移动轨迹栏时,alpha_slider都会被更新。
我们将src1,src2,dist,alpha,alpha_slider和beta定义为全局变量,因此可以随处使用。
5. OpenCV文件"opencv2/core/utility.hpp"的说明
6.OPENCV 学习(3)几何图形的简单识别
https://www.freesion.com/article/952767796/
/********************************************************************
** 文 件 名:几何图形的简单判断
** 创 建 人:yk
** 最后修改时间:2018年9月1日
*********************************************************************/
#include "stdafx.h"
#include <iostream>
#include <stdio.h>
#include "opencv2/core.hpp"
#include "opencv2/core/utility.hpp"
#include "opencv2/core/ocl.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
void YKrectangle(Mat &image, Point Start, Point End, int flog)
{
if (flog == 0)
{
for (int i = Start.y; i <= End.y; i++)
{
for (int j = Start.x; j <= End.x; j++)
{
image.at<Vec3b>(i, j)[0] = 0;
image.at<Vec3b>(i, j)[1] = 255; //绿色
image.at<Vec3b>(i, j)[2] = 0;
}
}
}
else if (flog == 1)//渐变色 四条边框直线
{
for (int i = Start.y; i <= End.y; i++)
{
for (int j = Start.x; j <= End.x; j++)
{
if (i == Start.y || i == End.y || j == Start.x || j == End.x)
{
image.at<Vec3b>(i, j)[0] = i;
image.at<Vec3b>(i, j)[1] = j;
image.at<Vec3b>(i, j)[2] = (i + j) / 2;
}
}
}
}
}
int main()
{
int x = 0, y = 0, x1 = 0, y1 = 0;
Mat img = imread("G:\\yk\\Desktop\\文件\\aI\\opencv\\opencv图\\3.bmp", 0);
Mat img2 = imread("G:\\yk\\Desktop\\文件\\aI\\opencv\\opencv图\\3.bmp", 0);;
Mat img1 = Mat::zeros(img.rows, img.cols, CV_8UC3);//黑色图片
vector<vector<Point>>conts;
vector<Vec4i>hicouts;
threshold(img, img, 100, 254, 1);//阈值分割 100-254
blur(img, img, Size(3, 3));//均值滤波
imshow("3", img2);
img = ~img;
imshow("1", img);
for (int i = 0; i < img2.rows; i++)//画到原图上
{
for (int j = 0; j < img2.cols; j++)
{
if ((int)img2.at<uchar>(i, j) > 0)
{
img2.at<uchar>(i, j) = 255;
}
}
}
for (int i = 0; i < img.rows; i++)//画到原图上
{
for (int j = 0; j < img.cols; j++)
{
img1.at<Vec3b>(i, j)[0] = img.at<uchar>(i, j);
img1.at<Vec3b>(i, j)[1] = img.at<uchar>(i, j);
img1.at<Vec3b>(i, j)[2] = img.at<uchar>(i, j);
//cout << (int)img.at<uchar>(i, j) << endl;
}
}
Canny(img, img, 30, 70);//边缘检测
findContours(img, conts, hicouts, CV_RETR_TREE, CHAIN_APPROX_SIMPLE);//找到轮廓
for (int i = 0; i < conts.size(); i++)//遍历轮廓
{
int temp = conts[i][0].x, temp1 = conts[i][0].y, temp2 = conts[i][0].x, temp3 = conts[i][0].y;//初始化x,y的最小 最大值
for (int j = 0; j < conts[i].size(); j++)
{
if (conts[i][j].x < temp)
{
temp = conts[i][j].x;//最小的x
}
if (conts[i][j].y < temp1)
{
temp1 = conts[i][j].y;//最小的y
}
if (conts[i][j].x > temp2)
{
temp2 = conts[i][j].x;//最大的x
}
if (conts[i][j].y > temp3)
{
temp3 = conts[i][j].y;//最大的y
}
}
//cout << (int)img.at<uchar>(temp - 10, temp1 - 10) << endl;
if (fabs((temp3 - temp1) - (temp2 - temp)) < 5)//两边边长相差5以内: 圆形
{
Scalar color(0, 255, 0);
drawContours(img1, conts, i, color, -1, 8, hicouts);//绘制第i个轮廓,绿色,圆形
}
else if ((int)img2.at<uchar>(temp + 50, temp1 + 3) == 0)//轮廓内部点黑色
{
Scalar color(255, 0, 0);
drawContours(img1, conts, i, color, -1, 8, hicouts);//绘制为红色,三角形
}
else
{
Scalar color(0, 0, 255);
drawContours(img1, conts, i, color, -1, 8, hicouts);//矩形框,蓝色
}
Point start(temp - 5, temp1 - 5), end(temp2 + 5, temp3 + 5);//
YKrectangle(img1, start, end, 1);//绘制渐变矩形框
}
imshow("s", img1);
imwrite("G:\\yk\\Desktop\\文件\\aI\\opencv\\opencv图\\3result.bmp", img1);
waitKey(0);
}
blur
Blurs an image using the normalized box filter.
blur的作用是对输入的图像src进行均值滤波后用dst输出。
函数原型
C++: void blur(InputArray src, OutputArray dst, Size ksize, Point anchor=Point(-1,-1), int borderType=BORDER_DEFAULT )
参数详解如下:
第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。该函数对通道是独立处理的,且可以处理任意通道数的图片,但需要注意,待处理的图片深度应该为CV_8U, CV_16U, CV_16S, CV_32F 以及 CV_64F之一。
第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。比如可以用Mat::Clone,以源图片为模板,来初始化得到如假包换的目标图。
第三个参数,Size类型(对Size类型稍后有讲解)的ksize,内核的大小。一般这样写Size( w,h )来表示内核的大小( 其中,w 为像素宽度, h为像素高度)。Size(3,3)就表示3x3的核大小,Size(5,5)就表示5x5的核大小
第四个参数,Point类型的anchor,表示锚点(即被平滑的那个点),注意他有默认值Point(-1,-1)。如果这个点坐标是负值的话,就表示取核的中心为锚点,所以默认值Point(-1,-1)表示这个锚点在核的中心。
第五个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。有默认值BORDER_DEFAULT,我们一般不去管它。
drawContours函数的作用
主要用于画出图像的轮廓
void drawContours(InputOutputArray image, InputArrayOfArrays contours, int contourIdx, const Scalar& color, int thickness=1, int lineType=8, InputArray hierarchy=noArray(), int maxLevel=INT_MAX, Point offset=Point() )
函数参数详解:
其中第一个参数image表示目标图像,
第二个参数contours表示输入的轮廓组,每一组轮廓由点vector构成,
第三个参数contourIdx指明画第几个轮廓,如果该参数为负值,则画全部轮廓,
第四个参数color为轮廓的颜色,
第五个参数thickness为轮廓的线宽,如果为负值或CV_FILLED表示填充轮廓内部,
第六个参数lineType为线型,
第七个参数为轮廓结构信息,
第八个参数为maxLevel
提取到轮廓后,其实我们更关心的是如果把这些轮廓转换为可以利用的特征,也就是涉及到轮廓的描述问题,这时就有多种方法可以选择,比如矢量化为多边形、矩形、椭圆等。OpenCV里提供了一些这样的函数。
// 轮廓表示为一个矩形
Rect r = boundingRect(Mat(contours[0]));
rectangle(result, r, Scalar(255), 2);
// 轮廓表示为一个圆
float radius;
Point2f center;
minEnclosingCircle(Mat(contours[1]), center, radius);
circle(result, Point(center),
static_cast<int>(radius),
Scalar(255),
2);
// 轮廓表示为一个多边形
vector<Point> poly;
approxPolyDP(Mat(contours[2]), poly, 5,
true
);
vector<Point>::const_iterator itp = poly.begin();
while(itp != (poly.end() - 1))
{
line(result, *itp, *(itp + 1), Scalar(255), 2);
++itp;
}
line(result, *itp, *(poly.begin()), Scalar(255), 2);
// 轮廓表示为凸多边形
vector<Point> hull;
convexHull(Mat(contours[3]), hull);
vector<Point>::const_iterator ith = hull.begin();
while(ith != (hull.end() - 1))
{
line(result, *ith, *(ith + 1), Scalar(255), 2);
++ith;
}
line(result, *ith, *(hull.begin()), Scalar(255), 2);
以下几个函数可以尝试:minAreaRect:计算一个最小面积的外接矩形,contourArea可以计算轮廓内连通区域的面积;pointPolygenTest可以用来判断一个点是否在一个多边形内。mathShapes可以比较两个形状的相似性,相当有用的一个函数。
cv2.drawContours()
cv2.drawContours(image, contours, contourIdx, color, thickness=None, lineType=None, hierarchy=None, maxLevel=None, offset=None)
第一个参数是指明在哪幅图像上绘制轮廓;image为三通道才能显示轮廓
第二个参数是轮廓本身,在Python中是一个list;
第三个参数指定绘制轮廓list中的哪条轮廓,如果是-1,则绘制其中的所有轮廓。后面的参数很简单。其中thickness表明轮廓线的宽度,如果是-1(cv2.FILLED),则为填充模式。
7. opencv立体标定函数 stereoCalibrate()
stereoCalibrate() 是用来标定一个立体摄像头的,也就是同时标定两个摄像头。标定的结果除了能够求出两个摄像头的内外参数矩阵,跟能够得出两个摄像头的位置关系R,T。
https://blog.youkuaiyun.com/zfjBIT/article/details/94436150
double stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints1,
InputArrayOfArrays imagePoints2, InputOutputArray cameraMatrix1,InputOutputArray distCoeffs1,
InputOutputArray cameraMatrix2, InputOutputArray distCoeffs2, Size imageSize, OutputArray R,
OutputArray T, OutputArray E, OutputArray F, int
flags=CALIB_FIX_INTRINSIC , TermCriteria criteria=
TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 1e-6))
objectPoints- vector<point3f> 型的数据结构,存储标定角点在世界坐标系中的位置
imagePoints1- vector<vector<point2f>> 型的数据结构,存储标定角点在第一个摄像机下的投影后的亚像素坐标
imagePoints2- vector<vector<point2f>> 型的数据结构,存储标定角点在第二个摄像机下的投影后的亚像素坐标
cameraMatrix1-输入/输出型的第一个摄像机的相机矩阵。如果CV_CALIB_USE_INTRINSIC_GUESS , CV_CALIB_FIX_ASPECT_RATIO ,CV_CALIB_FIX_INTRINSIC , or CV_CALIB_FIX_FOCAL_LENGTH其中的一个或多个标志被设置,该摄像机矩阵的一些或全部参数需要被初始化
distCoeffs1-第一个摄像机的输入/输出型畸变向量。根据矫正模型的不同,输出向量长度由标志决定
cameraMatrix2-输入/输出型的第二个摄像机的相机矩阵。参数意义同第一个相机矩阵相似
distCoeffs2-第二个摄像机的输入/输出型畸变向量。根据矫正模型的不同,输出向量长度由标志决定
imageSize-图像的大小
R-输出型,第一和第二个摄像机之间的旋转矩阵
T-输出型,第一和第二个摄像机之间的平移矩阵
E-输出型,基本矩阵
F-输出型,基础矩阵
flag-
CV_CALIB_FIX_INTRINSIC 如果该标志被设置,那么就会固定输入的cameraMatrix和distCoeffs不变,只求解R,T,E,F
CV_CALIB_USE_INTRINSIC_GUESS 根据用户提供的cameraMatrix和distCoeffs为初始值开始迭代
CV_CALIB_FIX_PRINCIPAL_POINT 迭代过程中不会改变主点的位置
CV_CALIB_FIX_FOCAL_LENGTH 迭代过程中不会改变焦距
CV_CALIB_SAME_FOCAL_LENGTH 强制保持两个摄像机的焦距相同
CV_CALIB_ZERO_TANGENT_DIST 切向畸变保持为零
CV_CALIB_FIX_K1,...,CV_CALIB_FIX_K6 迭代过程中不改变相应的值。如果设置了CV_CALIB_USE_INTRINSIC_GUESS 将会使用用户提供的初始值,否则设置为零
CV_CALIB_RATIONAL_MODEL 畸变模型的选择,如果设置了该参数,将会使用更精确的畸变模型,distCoeffs的长度就会变成8
term_crit-迭代优化的终止条件
8. OpenCV FileStorage读写操作 https://blog.youkuaiyun.com/qq_16952303/article/details/80259660
有时我们需要保存一些训练数据,或是相机标定结果,亦或是临时存储一些图片等等。这时,我们可以使用FileStorage加上FileNode来完成这些操作。
FileStorage 和 FileNode
FileStorage这个类可以看成是纸+笔的功能,负责保存内容和写入内容;而FileNode可以看成是眼睛,读取纸上的内容。FileStorage拥有真正的内容,而FileNode仅拥有读取的功能。这两个类支持读写XML, YAML, JSON 格式文件,后缀名为.xml , .yml , .yaml , .json
8.1 打开文件
FileStorage storage("file.xml", FileStorage::WRITE);
8.2 单值写入
像matrix,int,double这三个字段对应单值,这种情况下写法为:
storage <<
"matrix", (Mat_<float>(2, 2) << 1, 2, 3, 4),
"int", 1,
"double", 2.2;
这里我重载了FileStorage的逗号运算符,可看下之前的文章:C++逗号和移位运算符简化写入操作
FileStorage& operator , (FileStorage& out, const T& data)
{
out << data;
return out;
}
8.3 数组写入
像strings就是一个数组,里面包含多个String元素,这种类型的写法为:
storage<<"strings", "[:", "123", "456", "end", "]";//Note:重载了逗号运算符
也就是说数组卸载中括号中,只是开始的括号后面要加个冒号,这么做貌似能更好的兼容python之类的,具体的解释可以看OpenCV的注释。
8.4 元组 / 嵌套数组 写入
数组保存数据类型相同的值,而元组可以保存不同数据类型的值,本质是数组里套了数组,OpenCV没有那样注释,这是我自己的理解。要是错了,请大家指正。上面表格中的features就可以看成是元组,可以这样写入
storage<<"features", "[:"; //元组依然包含在中括号中
for (int i = 0; i < 3; ++i) {
storage << "{:", //元素以大括号开头,!Note 重载了逗号运算符
"x", i,
"y", i*i,
"lb", "[:"; //元素可以包含数组、元组嵌套
for (int j = 0; j < 4; ++j) {
storage << j;
}
storage << "]" << "}"; //一个元素写入结束,加上结尾括号
}
storage << "]"; //元组写入完毕,加上结尾括号
也就是说,元组还是包含在中括号中,只是每一个元素要包含在大括号中,同样的,为了保持兼容,我们括号开头都跟一个冒号。
8.5 FileNode读操作
打开文件
FileStorage storage("file.xml", FileStorage::READ);
8.6 读单值
cout << "matrix:", storage["matrix"].mat(), '\n', //!Note重载了逗号运算符
"int:", storage["int"].real(), '\n',
"double:", storage["double"].real(), '\n';
8.7 读数组
由于FileNodeIterator的存在,并且FileNode有begin()和end()方法,于是我们可以用C++11简洁的遍历方式!
cout << "strings:";
for (auto&iter : storage["strings"]) {
cout << iter.string(), ','; //!重载了逗号运算符
}
8.8 读元组 / 嵌套数组
cout << "features:" << endl;
for (auto& iter : storage["features"]) {
cout << "- {",
"x:", iter["x"].real(), ",",
"y:", iter["y"].real(), ",",
"lb:[";
for (auto& lbIter : iter["lb"]) {
cout << lbIter.real(), ",";
}
cout << "]}\n";
}
#include <opencv2\core.hpp>
#include <iostream>
using namespace cv;
using std::cout;
using std::endl;
using std::ostream;
template <typename T>
FileStorage& operator , (FileStorage& out, const T& data)
{
out << data;
return out;
}
template<typename T>
ostream& operator ,(ostream& out, const T& data)
{
out << data;
return out;
}
int main()
{
//write
{
FileStorage storage("file.xml", FileStorage::WRITE);
storage <<
"matrix", (Mat_<float>(2, 2) << 1, 2, 3, 4),
"int", 1,
"double", 2.2,
"strings", "[:", "123", "456", "end", "]",
"features", "[:";
for (int i = 0; i < 3; ++i) {
storage << "{:",
"x", i,
"y", i*i,
"lb", "[:";
for (int j = 0; j < 4; ++j) {
storage << j;
}
storage << "]" << "}";
}
storage << "]";
}
//read
{
FileStorage storage("file.xml", FileStorage::READ);
cout << "matrix:", storage["matrix"].mat(), '\n',
"int:", storage["int"].real(), '\n',
"double:", storage["double"].real(), '\n';
cout << "strings:";
for (auto&iter : storage["strings"]) {
cout << iter.string(), ',';
}
cout << endl;
cout << "features:" << endl;
for (auto& iter : storage["features"]) {
cout << "- {",
"x:", iter["x"].real(), ",",
"y:", iter["y"].real(), ",",
"lb:[";
for (auto& lbIter : iter["lb"]) {
cout << lbIter.real(), ",";
}
cout << "]}\n";
}
}
return 0;
}
https://www.cnblogs.com/feifanrensheng/p/9132209.html
https://vimsky.com/examples/ 代码示例
https://www.cnblogs.com/feifanrensheng/p/9132209.html
9. cv.rectify3Collinear
https://kyamagu.github.io/mexopencv/matlab/rectify3Collinear.html
计算三头相机的校正变换,其中所有头都在同一条线上
S = cv.rectify3Collinear(cameraMatrix1, distCoeffs1, cameraMatrix2,
distCoeffs2, cameraMatrix3, distCoeffs3, imageSize, R12, T12, R13, T13)
输入
cameraMatrix1 第一个相机矩阵 3x3。
distCoeffs1 4、5、8、12 或 14 个元素的第一个相机畸变参数。
cameraMatrix2 第二个相机矩阵 3x3。
distCoeffs2 4、5、8、12 或 14 个元素的第二个相机畸变参数。
cameraMatrix3 第三个相机矩阵 3x3。
distCoeffs3 4、5、8、12 或 14 个元素的第三个相机畸变参数。
imageSize 用于立体校准的图像大小 [w,h]。
R12 第一个和第二个摄像机坐标系之间的旋转矩阵,3x3/3x1(参见 cv.Rodrigues)
T12 第一个和第二个相机坐标系之间的平移向量,3x1。
R13 第一个和第三个摄像机坐标系之间的旋转矩阵,3x3/3x1(见 cv.Rodrigues)
T13 第一个和第三个相机坐标系之间的平移向量,3x1。
输出
S 标量结构具有以下字段:
第一个相机的 R1 3x3 整流变换(旋转矩阵)。
第二个相机的 R2 3x3 校正变换(旋转矩阵)。
第三个相机的 R3 3x3 整流变换(旋转矩阵)。
P1 3x4 投影矩阵在第一台相机的新(校正)坐标系中。
第二个相机的新(校正)坐标系中的 P2 3x4 投影矩阵。
第三个摄像机的新(校正)坐标系中的 P3 3x4 投影矩阵。
Q 4x4 视差到深度映射矩阵(参见 cv.reprojectImageTo3D)。
roi1, roi2 校正图像内的矩形,其中所有像素都有效 [x,y,w,h]。 如果 Alpha=0,则 ROI 覆盖整个图像。 否则,它们可能会更小。
ratio 视差比,浮点数值标量。
选项
ImgPoints1, ImgPoints3 二维点{[x,y], ...}的可选单元阵列,用于调整第三个矩阵P3。默认不设置。
Alpha 自由缩放参数。如果为 -1 或不存在,则函数执行默认缩放。否则,该参数应介于 0 和 1 之间。 Alpha=0 表示对校正后的图像进行缩放和移动,以便只有有效像素可见(校正后没有黑色区域)。 Alpha=1 意味着对校正后的图像进行抽取和移位,以便来自相机的原始图像中的所有像素都保留在校正后的图像中(不会丢失源图像像素)。显然,任何中间值都会在这两种极端情况之间产生中间结果。默认 -1
NewImageSize 校正后的新图像分辨率。应该将相同的大小传递给 cv.initUndistortRectifyMap。当 [0,0] 被传递(默认)时,它被设置为原始的 imageSize。将其设置为较大的值可以帮助您保留原始图像中的细节,尤其是当径向失真较大时。
ZeroDisparity 如果设置了该标志,该函数使每个相机的主点在校正视图中具有相同的像素坐标。如果未设置该标志,该功能仍可能在水平或垂直方向上移动图像(取决于核线的方向)以最大化有用的图像区域。默认为真。
cv.stereoRectify
计算校准立体相机的每个头部的校正变换
10. initUndistortRectifyMap
CV_EXPORTS_W void initUndistortRectifyMap( InputArray cameraMatrix, InputArray distCoeffs,
InputArray R, InputArray newCameraMatrix,
Size size, int m1type, OutputArray map1, OutputArray map2 );
https://blog.youkuaiyun.com/u013341645/article/details/78710740
https://cloud.tencent.com/developer/article/1759505
函数功能:计算无畸变和修正转换映射。
参数说明:
1.cameraMatrix:输入相机矩阵
2.distCoeffs:输入参数,相机的畸变系数:,有4,5,8,12或14个元素。如果这个向量是空的,就认为是零畸变系数。
3.R:可选的修正变换矩阵,是个3*3的矩阵。通过stereoRectify计算得来的R1或R2可以放在这里。如果这个矩阵是空的,就假设为单位矩阵。在cvInitUndistortMap中,R被认为是单位矩阵。
4.newCameraMatrix:新的相机矩阵
5.size:未畸变的图像尺寸。
6.m1type:第一个输出的映射的类型,可以为 CV_32FC1, CV_32FC2或CV_16SC2,参见cv::convertMaps。
7.map1:第一个输出映射。
8.map2:第二个输出映射。
函数说明:
这个函数用于计算无畸变和修正转换关系,为了重映射,将结果以映射的形式表达。无畸变的图像看起来就想原始的图像,就像这个图像是用内参为newCameraMatrix的且无畸变的相机采集得到的。
在单目相机例子中,newCameraMatrix一般和cameraMatrix相等,或者可以用cv::getOptimalNewCameraMatrix来计算,获得一个更好的有尺度的控制结果。
在双目相机例子中,newCameraMatrix一般是用cv::stereoRectify计算而来的,设置为P1或P2。
此外,根据R,新的相机在坐标空间中的取向是不同的。例如,它帮助配准双目相机的两个相机方向,从而使得两个图像的极线是水平的,且y坐标相同(在双目相机的两个相机谁水平放置的情况下)。
该函数实际上为反向映射算法构建映射,供反向映射使用。也就是,对于在已经修正畸变的图像中的每个像素,该函数计算原来图像(从相机中获得的原始图像)中对应的坐标系。这个过程是这样的:
在双目相机的例子中,这个函数调用两次:一次是为了每个相机的朝向,经过stereoRectify之后,依次调用cv::stereoCalibrate。但是如果这个双目相机没有标定,依然可以使用cv::stereoRectifyUncalibrated直接从单应性矩阵H中计算修正变换。对每个相机,函数计算像素域中的单应性矩阵H作为修正变换,而不是3D空间中的旋转矩阵R。R可以通过H矩阵计算得来:
11. OpenCV中Mat类rowRange和colRange的用法
Mat类中的rowRange和colRange 提取某些行或列。
rowRange
为指定的行空间创建矩阵标头。
C++: Mat Mat::rowRange(int startrow, int endrow)
colRange
为指定的列范围创建矩阵标头。
C++: Mat Mat::colRange(int startcol, int endcol)