相机标定:项目篇

目录

  • 相机标定
  • 环境准备
  • 创建项目
  • 简单配置
  • 编写Qt
  • 编写代码
  • 结果测试
  • 打包发布
  • 问题解决
    • 无法查找或打开 PDB 文件
  • 作者声明

相机标定

  • 相机标定:原理篇
  • 相机标定:项目篇,项目单张图片标定(vstudio2017+opencv4.5.3+qt5.13.1)

环境准备

  1. 使用vstudio2017+opencv4.5.3+qt5.13.1开发环境
  2. 标定文件:黑白棋盘格图,使用A4纸打印即可
  3. 三个软件的安装见
  • vstudio2017+opencv4.5.3+qt5.13.1:安装篇
  1. 三个软件的配置见
  • vstudio2017+opencv4.5.3+qt5.13.1:配置篇
  1. 打包发布Qt和*.exe见
  • vstudio2017+opencv4.5.3+qt5.13.1:项目篇,项目打开摄像头+打开图片+关闭摄像头和图片

创建项目

  1. 文件》新建》项目
    相机标定:项目篇_第1张图片

  2. 已安装》Visual C++》Qt》Qt Widgets Application》输入项目名
    相机标定:项目篇_第2张图片

  3. next》next》finish
    相机标定:项目篇_第3张图片
    相机标定:项目篇_第4张图片

  4. 项目结果
    相机标定:项目篇_第5张图片

简单配置

  • 这里的简单配置是在“环境准备”配置篇完成后进行的
  1. 调整为Release|x64模式
    相机标定:项目篇_第6张图片

  2. 进入配置:

  • 双击右侧》Release|x64》Microsoft.Cpp.x64.user
  • 或者 右键项目》选择属性
    相机标定:项目篇_第7张图片
  1. 检查:通用属性》链接器》输入》附加依赖项(值包含:opencv_world453.lib)
    相机标定:项目篇_第8张图片

编写Qt

如何编写Qt的过程中出现问题可以参见

  • vstudio2017+opencv4.5.3+qt5.13.1:项目篇,项目打开摄像头+打开图片+关闭摄像头和图片
  1. 打开Qt编写界面:左侧栏》Form files》双击 oneImageCalibration.ui
  • 蓝框是所用组件类别,做Qt编写界面左侧拖动即可
  • 共使用了4个Label,3个Push Button,2个Text Edit
    相机标定:项目篇_第9张图片
  1. 保存编写结果
  • 文件》Save “oneImageCalibration.ui”
  • 或者,文件》保存所有文件
    相机标定:项目篇_第10张图片
  1. 生成解决方案:vstudio》生成》生成解决方案
  • 作用:使 oneImageCalibration.ui生效
    相机标定:项目篇_第11张图片

编写代码

  1. 项目结构
  • oneImageCalibration.h
  • oneImageCalibration.cpp
  • InnerOuterParam.h
  • InnerOuterParam.cpp
  • main.cpp
    相机标定:项目篇_第12张图片
  1. InnerOuterParam.h
#pragma once
#include 
#include 
#include "ui_oneImageCalibration.h"
#include "opencv2/opencv.hpp"
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include 
#include 
#include 
#include 
#include 

using namespace cv;

class InnerOuterParam
{
public:
	//摄像机内参数矩阵 3x3矩阵
	Mat cameraMatrix;
	//摄像机的5个畸变系数:k1,k2,p1,p2,k3  1x5矩阵
	Mat distCoeffs;
	//每幅图像的旋转向量
	std::vector<Mat> rvecsMat;
	//每幅图像的平移向量
	std::vector<Mat> tvecsMat;

	InnerOuterParam(Mat cameraMatrix, Mat distCoeffs, std::vector<Mat> rvecsMat, std::vector<Mat> tvecsMat);
	~InnerOuterParam();
};
  1. InnerOuterParam.cpp
#include "InnerOuterParam.h"
#include 
#include 
using namespace std;

/*
构造函数
*/
InnerOuterParam::InnerOuterParam(Mat cameraMatrix, Mat distCoeffs, vector<Mat> tvecsMat, vector<Mat> rvecsMat)
{
	this->cameraMatrix = cameraMatrix;
	this->distCoeffs = distCoeffs;
	this->tvecsMat = tvecsMat;
	this->rvecsMat = rvecsMat;
}

/*
析构函数
*/
InnerOuterParam::~InnerOuterParam()
{

}

  1. oneImageCalibration.h
#pragma once
#include "InnerOuterParam.h"
#include 
#include 
#include "ui_oneImageCalibration.h"
#include "opencv2/opencv.hpp"
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include 
#include 
#include 
#include 
#include 

using namespace cv;

class oneImageCalibration : public QMainWindow
{
	Q_OBJECT

public:
	oneImageCalibration(QWidget* parent = Q_NULLPTR);
	~oneImageCalibration();
	QImage MatImageToQt(const Mat& src);

	//在“原始画面”显示拍照画面
	void takePhotoShow();
	//世界坐标系,获取物体点坐标(单位mm)
	std::vector<std::vector<Point3f>> getObjectPoints();
	//像素坐标系,提取角点坐标(单位pixel)并展示
	std::vector<std::vector<Point2f>> extractConers();
	//标定,世界坐标系-物体点坐标(单位mm)》像素坐标系-角点坐标(单位pixel)
	InnerOuterParam* calibrateObjectPointsConers(std::vector<std::vector<Point3f>> objectPoints, std::vector<std::vector<Point2f>> cornersList);
	//校正,校正并存储校正图像
	void undistortImageStore(InnerOuterParam* innerOuterParam);
	//展示内参外参
	void showParams(InnerOuterParam* innerOuterParam);

private slots:
	void on_label_linkActivated(const QString& link);
	//点击“打开相机”按钮》打开摄像头
	void on_pushButton_camera_clicked();
	//点击“标定”按钮》进行标定
	void on_pushButton_calibration_clicked();
	//点击“显示矫正画面”按钮》展示矫正画面
	void on_pushButton_show_calibration_clicked();
	//读取当前帧信息
	void readFarme();

private:
	Ui::oneImageCalibrationClass* ui;
	//视频捕获
	VideoCapture cap;
	Mat src_image;
	QTimer* timer;
	QImage* image;
};

  1. oneImageCalibration.cpp
#include "oneImageCalibration.h"
#include "InnerOuterParam.h"
#include 
#include 
using namespace std;

/*
构造函数
*/
oneImageCalibration::oneImageCalibration(QWidget* parent)
	: QMainWindow(parent)
	, ui(new Ui::oneImageCalibrationClass)
{
	ui->setupUi(this);
	timer = new QTimer(this);
	image = new QImage();

	//信号和槽
	//时间到,读取当前摄像头信息
	connect(timer, SIGNAL(timeout()), this, SLOT(readFarme()));
}

/*
析构函数
*/
oneImageCalibration::~oneImageCalibration()
{
	delete ui;
}

void oneImageCalibration::on_label_linkActivated(const QString& link)
{

}

/*
打开摄像头
*/
void oneImageCalibration::on_pushButton_camera_clicked()
{
	cout << "打开摄像头";
	cap.open(0);
	timer->start(33);
}

/*
进行标定
*/
void oneImageCalibration::on_pushButton_calibration_clicked()
{
	this->takePhotoShow();
	if (src_image.empty()) {
		cout << "读取图像失败" << endl;
		exit(1);
	}

	vector<vector<Point3f>> objectPoints = this->getObjectPoints();
	vector<vector<Point2f>> cornersList = this->extractConers();
	InnerOuterParam* innerOuterParam = this->calibrateObjectPointsConers(objectPoints, cornersList);
	this->undistortImageStore(innerOuterParam);
	this->showParams(innerOuterParam);
	//释放堆区的innerOuterParam对象
	delete innerOuterParam;
}

/*
在“原始画面”显示拍照画面
*/
void oneImageCalibration::takePhotoShow()
{
	QImage imag = MatImageToQt(src_image);
	//ui对象就是*.ui
	ui->origin_label->setPixmap(QPixmap::fromImage(imag));
	//关闭摄像头
	timer->stop();
	cap.release();
}

/*
世界坐标系,获取物体点坐标(单位mm)
*/
vector<vector<Point3f>> oneImageCalibration::getObjectPoints()
{
	//保存每个标定板上角点的三维坐标 ,真实世界坐标
	vector<vector<Point3f>> objectPoints;
	//初始化标定板上角点的三维坐标,真实世界坐标
	vector<Point3f> tempPointSet;
	//实际测量得到的标定板上每个棋盘格的大小,单位mm
	Size squareSize = Size(28, 28);
	//标定板上每行、列的角点数,简单理解为去掉最外面一圈图块,注意宽=8和高=6的顺序
	Size patternSize = Size(8, 6);
	//patternSize宽=8和高=6
	for (int i = 0; i < patternSize.height; i++)
	{
		for (int j = 0; j < patternSize.width; j++)
		{
			//真实世界坐标
			Point3f realPoint;
			//假设标定板放在世界坐标系中z=0的平面上 squareSize.width=28mm squareSize.height=28mm
			realPoint.x = j * squareSize.width;
			realPoint.y = i * squareSize.height;
			realPoint.z = 0;
			tempPointSet.push_back(realPoint);
		}
	}

	objectPoints.push_back(tempPointSet);

	return objectPoints;
}

/*
像素坐标系,提取角点坐标(单位pixel)并展示
*/
vector<vector<Point2f>> oneImageCalibration::extractConers()
{
	/*
	角点:提取角点并展示到origin_label
	extractConersShow
	*/
	//保存每张标定图上检测到的所有角点,保存每张标定图上检测到的所有亚像素精确化角点 
	vector<vector<Point2f>> cornersList;
	//标定板上每行、列的角点数,简单理解为去掉最外面一圈图块,注意宽=8和高=6的顺序
	Size patternSize = Size(8, 6);
	//缓存每幅图像上检测到的角点
	vector<Point2f> corners;
	string filename;
	//存储所有文件名
	vector<string> filenameList;

	//提取角点
	if (findChessboardCorners(src_image, patternSize, corners) == 0) {
		cout << "找不到角点" << endl;
		exit(1);
	}
	//亚像素精确化
	else
	{
		Mat gray;
		cvtColor(src_image, gray, COLOR_BGR2GRAY);
		//对粗提取的角点进行精确化	
		find4QuadCornerSubpix(gray, corners, Size(5, 5));
		//对粗提取的角点进行精确化,保存亚像素角点											
		cornersList.push_back(corners);
		cout << "角点数量: " << corners.size() << endl;
		//在图像上显示角点位置,并展示在origin_label中
		drawChessboardCorners(src_image, patternSize, corners, true);
		QImage imag = MatImageToQt(src_image);
		ui->origin_label->clear();
		ui->origin_label->setPixmap(QPixmap::fromImage(imag));
	}

	return cornersList;
}

/*
标定,世界坐标系-物体点坐标(单位mm)》像素坐标系-角点坐标(单位pixel)
*/
InnerOuterParam* oneImageCalibration::calibrateObjectPointsConers(vector<vector<Point3f>> objectPoints, vector<vector<Point2f>> cornersList)
{
	//图像整体像素大小
	Size imageSize;
	imageSize.height = src_image.rows;
	imageSize.width = src_image.cols;
	//摄像机内参数矩阵 3x3矩阵
	Mat cameraMatrix = Mat(3, 3, CV_32FC1, Scalar::all(0));
	//摄像机的5个畸变系数:k1,k2,p1,p2,k3  1x5矩阵
	Mat distCoeffs = Mat(1, 5, CV_32FC1, Scalar::all(0));
	//每幅图像的平移向量
	vector<Mat> tvecsMat;
	//每幅图像的旋转向量
	vector<Mat> rvecsMat;

	/*
	标定:真实世界坐标点+像素坐标点》内参+外参
	*/
	calibrateCamera(objectPoints, cornersList, imageSize, cameraMatrix, distCoeffs, rvecsMat, tvecsMat, 0);
	//new 堆区创建对象必须手动销毁
	InnerOuterParam* innerOuterParam = new InnerOuterParam(cameraMatrix, distCoeffs, rvecsMat, tvecsMat);
	return innerOuterParam;
}

/*
校正,校正并存储校正图像
*/
void oneImageCalibration::undistortImageStore(InnerOuterParam* innerOuterParam)
{
	/*
	矫正
	*/
	Mat dst_image = src_image.clone();
	undistort(src_image, dst_image, innerOuterParam->cameraMatrix, innerOuterParam->distCoeffs);

	/*
	存储标定结果
	*/
	QImage imag_dst = MatImageToQt(dst_image);
	imag_dst.save("calibdata.BMP", "BMP", 100);
}

/*
展示内参外参
*/
void oneImageCalibration::showParams(InnerOuterParam* innerOuterParam)
{
	//将旋转向量转换为相对应的旋转矩阵 
	Mat rotationMatrix = Mat(3, 3, CV_32FC1, Scalar::all(0));
	Rodrigues(innerOuterParam->rvecsMat[0], rotationMatrix);
	//将平移向量转化为相对应的平移矩阵
	Mat translationMatrix = Mat(innerOuterParam->tvecsMat[0]);
	/*
	展示内参外参
	*/
	ui->text_inner->document()->setMaximumBlockCount(100);
	ui->text_outer->document()->setMaximumBlockCount(100);
	stringstream stream;
	QString str;
	stream << innerOuterParam->cameraMatrix;
	str = QString::fromStdString(stream.str());
	ui->text_inner->append("cameraMatrix: " + str);

	stream.clear();
	stream << innerOuterParam->distCoeffs;
	str = QString::fromStdString(stream.str());
	ui->text_inner->append("distCoeffs: " + str);

	stream.clear();
	stream << rotationMatrix;
	str = QString::fromStdString(stream.str());
	ui->text_outer->append("rotationMatrix: " + str);

	stream.clear();
	stream << translationMatrix;
	str = QString::fromStdString(stream.str());
	ui->text_outer->append("translationMatrix: " + str);
}

/*
展示矫正画面
*/
void oneImageCalibration::on_pushButton_show_calibration_clicked()
{
	Mat srcImage = imread("calibdata.BMP");
	QImage imag = MatImageToQt(srcImage);
	ui->fix_label->setPixmap(QPixmap::fromImage(imag));
}

/*
原始画面
*/
void oneImageCalibration::readFarme()
{
	//将相机画面读取到src_image中
	cap.read(src_image);
	QImage imag = MatImageToQt(src_image);
	//ui对象就是*.ui
	ui->origin_label->setPixmap(QPixmap::fromImage(imag));
}

/*
Mat转成QImage
*/
QImage oneImageCalibration::MatImageToQt(const Mat& src)
{
	if (src.type() == CV_8UC1)
	{
		QImage qImage(src.cols, src.rows, QImage::Format_Indexed8);

		qImage.setColorCount(256);

		for (int i = 0; i < 256; i++)
		{
			qImage.setColor(i, qRgb(i, i, i));
		}

		uchar* pSrc = src.data;
		for (int row = 0; row < src.rows; row++)
		{
			uchar* pDest = qImage.scanLine(row);
			memcmp(pDest, pSrc, src.cols);
			pSrc += src.step;
		}
		return qImage;
	}
	else if (src.type() == CV_8UC3)
	{
		const uchar* pSrc = (const uchar*)src.data;
		QImage qImage(pSrc, src.cols, src.rows, src.step, QImage::Format_RGB888);
		return qImage.rgbSwapped();
	}
	else if (src.type() == CV_8UC4)
	{
		const uchar* pSrc = (const uchar*)src.data;
		QImage qImage(pSrc, src.cols, src.rows, src.step, QImage::Format_ARGB32);
		return qImage.copy();
	}
	else
	{
		return QImage();
	}
}
  1. main.cpp
#include "oneImageCalibration.h"
#include 

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    oneImageCalibration w;
    w.show();
    return a.exec();
}

结果测试

  • 这里仅仅使用平板做演示,做法并不标准
    相机标定:项目篇_第13张图片

打包发布

参见下面文章中 章节“打包发布Qt和*.exe”

  • vstudio2017+opencv4.5.3+qt5.13.1:项目篇,项目打开摄像头+打开图片+关闭摄像头和图片

问题解决

无法查找或打开 PDB 文件

  1. vstudio》调试》选项
    相机标定:项目篇_第14张图片
  2. 调试》常规》勾选-启用源服务器支持
    相机标定:项目篇_第15张图片
    相机标定:项目篇_第16张图片
  3. 调试》符号》勾选-Microsoft 符号服务器
    相机标定:项目篇_第17张图片
    相机标定:项目篇_第18张图片

作者声明

  • 文章如有问题,欢迎指正!!!

你可能感兴趣的:(homework,qt,c++,opencv,计算机视觉,visual,studio)