一、单目相机位姿估计
如上图所示,根据图像的情况反推相机的运动情况。
如实现上述功能则需要拍摄当前物体的图像,然后拍摄一段时间之后物体的图像,然后联合两张图像则可以获取两个时刻的相机位姿关系。
位姿估计函数:
bool cv:solvePnP( InputArray objectPoints, InputArray imagePoints, InputArray cameraMatrix, InputArray distCoeffs, OutputArray rvec, OutputArray tvec, useExtrinsicGuess =, bool false, int flags = 5OLVEPNP_ITERATIVE)
objectPoints:前一时刻世界坐标系中的3D点的三维坐标。
imagePoints: 3D点在图像中对应的像素点的二维坐标。
cameraMatrix:相机的内参矩阵。
distCoeffs:相机的畸变系数矩阵。
rvec:世界坐标系变换到相机坐标系的旋转向量。
tvec:世界坐标系变换到相机坐标系的平移向量。
uscExtrinsicGuess:是否使用旋转向量初值和平移向量初值的标志。
flags:选择解算PnP问题方法的标志。
本节应用案例如下:
int main() {
//读取图片
Mat src = imread("left1.jpg");
Mat gray;
if (src.empty())
{
printf("不能打开空图片");
return -1;
}
cvtColor(src, gray, COLOR_BGR2GRAY);
vector<Point2f> imgpoints;
Size boardSize = Size(9,6);
//计算标定板的角点
findChessboardCorners(gray, boardSize, imgpoints);
//细化方格标定板角点坐标
find4QuadCornerSubpix(gray, imgpoints, Size(5, 5));
//棋盘格每个方格的真实尺寸
Size squareSize = Size(10, 10);
vector<Point3f> PointSets;
for (int j = 0; j < boardSize.height; j++)
{
for (int k = 0; k < boardSize.width; k++)
{
Point3f realPoint;
//假设标定板为世界坐标系的Z平面,及z=0
realPoint.x = j * squareSize.width;
realPoint.y = k * squareSize.height;
realPoint.z = 0;
PointSets.push_back(realPoint);
}
}
//输入计算的内参矩阵和畸变矩阵
Mat cameraMatrix = (Mat_<float>(3, 3) << 120.8643306554273, 0, 94.55565247064737,
0, 119.979406894919, 55.48571212317609,
0, 0, 1);
Mat distCoeffs = (Mat_<float>(1, 5) << -0.5559208449775317, 3.15840209023594, -0.001816753642197531, -0.01817901488786, -7.629569308066959);
//用PnP算法计算旋转和平移量
Mat rvec, tvec;
solvePnP(PointSets, imgpoints, cameraMatrix, distCoeffs, rvec, tvec);
cout << "旋转向量为: " << rvec << endl;
//旋转向量转换旋转矩阵
Mat R;
Rodrigues(rvec, R);
cout << "旋转矩阵为:" << R << endl;
//用PnP + Ransac算法计算旋转向量和平移向量
Mat rvecRansac, tvecRansac;
solvePnPRansac(PointSets, imgpoints, cameraMatrix, distCoeffs, rvecRansac, tvecRansac);
Mat RRansac;
Rodrigues(rvecRansac, RRansac);
cout << "旋转矩阵" << RRansac << endl;
waitKey(0);
return 0;
}
二、插值法从视频中跟踪移动的物体
计算差值绝对值函数:
void cv::absdiff ( InputArray src1, InputArray src2, OutputArray dst)
srcl:第一个数组或者Mat类矩阵。
src2:第二个数组或者Mat类矩阵,需要与第一个参数具有相同的尺寸和数据类型。
dst:两个数据差值的绝对值,与输入数据具有相同的尺寸和数据类型。
本节应用案例如下:
int main() {
//读取视频
VideoCapture capture("1.mp4");
if (!capture.isOpened())
{
printf("不能打开空图片");
return -1;
}
//获取视频相关信息
//帧率
int fps = capture.get(CAP_PROP_FPS);
//宽度
int wideth = capture.get(CAP_PROP_FRAME_WIDTH);
//高度
int height = capture.get(CAP_PROP_FRAME_HEIGHT);
//总帧数
int num_of_frames = capture.get(CAP_PROP_FRAME_COUNT);
//读取视频中第一幅图像作为前一帧图像,并进行灰度化
Mat preFrame, preGray;
capture.read(preFrame);
cvtColor(preFrame, preGray, COLOR_BGR2GRAY);
//对图像进行高斯滤波
GaussianBlur(preGray, preGray, Size(0, 0), 15);
Mat binary;
Mat frame, gray;
//形态学操作的矩形模板
Mat k = getStructuringElement(MORPH_RECT, Size(7, 7), Point(-1, -1));
while (true)
{
//视频中所有图像处理完成后退出循环
if (!capture.read(frame))
{
break;
}
//对当前帧进行灰度化
cvtColor(frame, gray, COLOR_BGR2GRAY);
GaussianBlur(gray, gray, Size(0, 0), 15);
//计算当前帧与前一帧差值的绝对值
absdiff(gray, preGray, binary);
//对计算结果二值化进行开运算,减少噪声的干扰
threshold(binary, binary, 10, 255, THRESH_BINARY | THRESH_OTSU);
morphologyEx(binary, binary, MORPH_OPEN, k);
imshow("input", frame);
imshow("result", binary);
//延迟5毫秒延迟判断是否退出程序,按ESC退出
char c = waitKey(5);
//if (c = 27)
//{
// break;
//}
}
waitKey(0);
return 0;
}
三、稠密光流法实现物体跟踪
void cv::calcOpticalFlowFarneback ( InputArray prev, InputArray next, InputOutputArray flow,double pyr_scale, int levels, int winsize, int iterations, int poly_n. double poly_sigma, int flags)
prev:前一帧图像,必须是CV_8UC1类型。
next:当前帧图像,与前一帧图像具有相同的尺寸和数据类型。
flow:输出的光流图像,图像与前一帧图像具有相同的尺寸,为CV_32F双通道图像。
pyr_scale:图像金字塔两层之间尺寸缩放的比例。
levels:构建图像金字塔的层数。
winsize:均值窗口的尺寸。
iterations:算法在每个金字塔图层中迭代的次数。
poly_n:在每个像素中找到多项式展开的像素邻域的大小。
poly_sigma:高斯标准差。
flags:计算方法标志。
本节应用案例如下:
int main() {
//读取视频
VideoCapture capture("1.mp4");
if (!capture.isOpened())
{
printf("不能打开空图片");
return -1;
}
//读取视频中第一幅图像作为前一帧图像,并进行灰度化
Mat preFrame, preGray;
capture.read(preFrame);
cvtColor(preFrame, preGray, COLOR_BGR2GRAY);
while (true)
{
Mat nextFrame, nextGray;
//视频中所有图像处理完成后退出循环
if (!capture.read(nextFrame))
{
break;
}
imshow("input", nextFrame);
//计算稠密光流
cvtColor(nextFrame, nextGray, COLOR_BGR2GRAY);
//两个方向的运动速度
Mat_<Point2f> flow;
calcOpticalFlowFarneback(preGray, nextGray, flow, 0.5, 3, 15, 3, 5, 1.2, 0);
//x方向移动速度
Mat xV = Mat::zeros(preFrame.size(), CV_32FC1);
//y方向移动速度
Mat yV = Mat::zeros(preFrame.size(), CV_32FC1);
//获取两个方向的速度
for (int row = 0; row < flow.rows; row++)
{
for (int col = 0; col < flow.cols; col++)
{
const Point2f& flow_xy = flow.at<Point2f>(row, col);
xV.at<float>(row, col) = flow_xy.x;
yV.at<float>(row, col) = flow_xy.y;
}
}
//计算向量角度和幅度
Mat magnitude, angle;
cartToPolar(xV, yV, magnitude, angle);
//将角度转换为角度制
angle = angle * 180.0 / CV_PI / 2.0;
//将幅值归一化到0-255
normalize(magnitude, magnitude, 0, 255, NORM_MINMAX);
//计算角度和幅值的绝对值
convertScaleAbs(magnitude, magnitude);
convertScaleAbs(angle, angle);
//将运动的赋值和角度生成HSV颜色的空间图像
Mat HSV = Mat::zeros(preFrame.size(), preFrame.type());
vector<Mat> result;
split(HSV, result);
//颜色
result[0] = angle;
result[1] = Scalar(255);
//形态
result[2] = magnitude;
merge(result, HSV);
//将HSV颜色转换为RGB
Mat rgbImg;
cvtColor(HSV, rgbImg, COLOR_HSV2BGR);
imshow("result", rgbImg);
//延迟5毫秒延迟判断是否退出程序,按ESC退出
char c = waitKey(5);
//if (c = 27)
//{
// break;
//}
}
waitKey(0);
return 0;
}
四、稀疏光流法实现物体跟踪
与稀疏光流法的计算方式相同,但唯一不同的地方就是稀疏光流法并不是计算整个图像的像素点,而是选取图像中有代表性的点来实现物体的跟踪。
void cv::calcOpticalFlowPyrLK ( InputArray previmg, InputArray nextlmg, InputArray prevPts, InputOutputArray nextPts, OutputArray status, OutputArray err, Size winSize = Size(21,21), int maxLevel = 3, criteria =, TermCriteria Termcriteria(TermCriteria: :COUNT+TermCriteria: : EPS,30,8.81), int flags =0, double minEligThreshold = 1e-4)
prevPts:前一帧图像的稀疏光流点坐标,必须是单精度浮点数。
nextPts:当前帧中与前一帧图像稀疏光流点匹配成功的稀疏光流点坐标,同样必须是单精度浮点数。
status:输出状态向量,如果在两帧图像中寻找到相对应的稀疏光流点,那么向量值为1,否则向量值为0。
err:输出误差向量,向量每个元素都设置为对应点的误差,度量误差的
标准可以在flags参数中设置。
winSize:每层金字塔中搜索窗口的大小,默认Size(21,21)。
maxLevel:构建图像金字塔层数,参数值为从0开始的整数。
criteria:迭代搜索的终止条件。
flags:寻找匹配光流点的操作标志。
minEig Threshold:响应的最小特征值。
本节应用案例如下:
//颜色查找表
vector<Scalar>color_lut;
void draw_lines(Mat &image, vector<Point2f> pt1, vector<Point2f> pt2)
{
RNG rng(10000);
if (color_lut.size() < pt1.size())
{
for (size_t t = 0; t < pt1.size(); t++)
{
color_lut.push_back(Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)));
}
}
for (size_t t = 0; t < pt1.size(); t++)
{
line(image, pt1[t], pt2[t], color_lut[t], 2, 8, 0);
}
}
int main() {
//读取视频
VideoCapture capture("1.mp4");
Mat preframe, preImg;
if (!capture.read(preframe))
{
printf("不能打开空图片");
return -1;
}
//读取视频中第一幅图像作为前一帧图像,并进行灰度化
cvtColor(preframe, preImg, COLOR_BGR2GRAY);
//角点检测相关参数设置
vector<Point2f> Points;
double qualityLevel = 0.01;
int minDistance = 10;
int blockSize = 3;
bool useHarrisDetector = false;
double k = 0.04;
int Corners = 5000;
//开始角点检测
vector<Point2f> prevPts; //前一幅图像的角点坐标
vector<Point2f> nextPts; //当前帧图像的角点坐标
vector<uchar> status; //检点检测到的状态
vector<float> err;
TermCriteria criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 30, 0.01);
double derivlambda = 0.5;
int flags = 0;
//初始状态的角点
vector<Point2f>initPoints;
initPoints.insert(initPoints.end(), Points.begin(), Points.end());
//前一帧图像中角点坐标
prevPts.insert(prevPts.end(), Points.begin(), Points.end());
while (true)
{
//读取视频
Mat nextFrame, nextImg;
if (!capture.read(nextFrame))
{
break;
}
//光流法跟踪
cvtColor(nextFrame, nextImg, COLOR_BGR2GRAY);
imshow("nextFrame", preImg);
calcOpticalFlowPyrLK(preImg, nextImg, prevPts, nextPts, status, err,
Size(31, 31), 3, criteria, derivlambda, flags);
//判断角点是否移动,如果不移动就删除
size_t i, k;
for (i = k = 0; i < nextPts.size(); i++)
{
//距离与状态测量
double dist = abs(prevPts[i].x - nextPts[i].x + abs(prevPts[i].y - nextPts[i].y));
if (status[i] && dist > 2)
{
prevPts[k] = prevPts[i];
initPoints[k] = initPoints[i];
nextPts[k++] = nextPts[i];
circle(nextFrame, nextPts[i], 3, Scalar(0, 255, 0), -1, 8);
}
}
//更新移动角点数目
nextPts.resize(k);
prevPts.resize(k);
initPoints.resize(k);
//绘制跟踪轨迹
draw_lines(nextFrame, initPoints, nextPts);
imshow("result", nextFrame);
//延迟5毫秒延迟判断是否退出程序,按ESC退出
char c = waitKey(50);
//更新角点坐标和前一帧图像
std::swap(nextPts, prevPts);
nextImg.copyTo(preImg);
//如果角点数目少于30,就重新检测角点
if (initPoints.size() < 30)
{
goodFeaturesToTrack(preImg, Points, Corners, qualityLevel,
minDistance, Mat(), blockSize, useHarrisDetector, k);
initPoints.insert(initPoints.end(), Points.begin(), Points.end());
prevPts.insert(prevPts.end(), Points.begin(), Points.end());
}
}
return 0;
}