【opencv3】PnP测距(完整流程附C++代码)

2023-05-16

概述

我们只要获得特征点的世界坐标(三维坐标)、2D坐标(像素坐标)、相机内参矩阵、相机畸变参数矩阵以上四个参数即可以解得相机与标志物之间的外参(旋转矩阵R、平移矩阵T),并以此求得相机的世界坐标(以标志物为世界坐标平面,且原点为标志物已知某一点)。

Ref:
PnP 单目相机位姿估计(二):solvePnP利用二维码求解相机世界坐标

相机内参矩阵、相机畸变参数矩阵可通过相机标定获取。

最后由旋转向量和平移矩阵求出深度信息:
先将旋转向量转化为旋转矩阵再转置,与平移矩阵相乘,得到的z坐标即深度信息。

P = − i n v e r s e ( R ) ∗ T P = -inverse (R) * T P=inverse(R)T

准备工作

1.相机标定简介

1.为什么需要对摄像头进行标定?
摄像头存在畸变,畸变可以拓宽视野,但会影响图像识别和测量的精度。
2.摄像头参数:
1)相机矩阵:包括焦距(fx,fy),光学中心(Cx,Cy),完全取决于相机本身,是相机的固有属性,只需要计算一次,可用矩阵表示如下:[fx, 0, Cx; 0, fy, cy; 0,0,1];
2) 畸变系数:畸变数学模型的5个参数 D = (k1,k2, P1, P2, k3);
3)相机内参:相机矩阵和畸变系数统称为相机内参,在不考虑畸变的时候,相机矩阵也会被称为相机内参;
4) 相机外参:通过旋转和平移变换将3D的坐标转换为相机2维的坐标,其中的旋转矩阵和平移矩阵就被称为相机的外参;描述的是将世界坐标系转换成相机坐标系的过程。
3.摄像头标定的流程:
相机的标定过程实际上就是在4个坐标系转化的过程中求出相机的内参和外参的过程。这4个坐标系分别是:世界坐标系(描述物体真实位置),相机坐标系(摄像头镜头中心),图像坐标系(图像传感器成像中心,图片中心,影布中心,单位mm),像素坐标系(图像左上角为原点,描述像素的位置,单位是多少行,多少列)。
(1)世界坐标系 → 相机坐标系:等比例缩小,外加旋转平移,称之为刚体变换;求解摄像头外参(旋转和平移矩阵);
(2)相机坐标系 → 图像坐标系:称为投影;求解相机内参(摄像头矩阵和畸变系数);
(3)图像坐标系 → 像素坐标系:将图像坐标离散抽样;求解像素转化矩阵(可简单理解为原点从图片中心到左上角,单位厘米变行列)
4.相机标定方法分类:
传统相机标定法、主动视觉相机标定法、相机自标定法。张正友相机标定法介于传统标定法和自标定法之间,但克服了传统标定法需要的高精度标定物的缺点。张氏标定法使用二维方格组成的标定板进行标定,采集标定板不同位姿图片,提取图片中角点像素坐标,通过单应矩阵计算出相机的内外参数初始值,利用非线性最小二乘法估计畸变系数,最后使用极大似然估计法优化参数。该方法操作简单,而且精度较高,可以满足大部分场合。

Ref:

  • 机器视觉的相机标定到底是什么?
  • (五)单目摄像头标定与畸变矫正(C++,opencv)

2.标定过程

(1)首先用摄像头拍摄一定数量的 标定板(棋盘图) 照片,大概10-20张左右,角度尽可能不同,拍照时要把完整的棋盘拍进去,不然找不到角点。

例如:
在这里插入图片描述

  • 这部分需要用自己的摄像头进行测试,如果没有别的摄像头可以用笔记本自带的摄像头。
VideoCapture inputVideo(0);
  • openCV的安装目录中有标定用到的标定板(棋盘图 chessboard.png )
    我的 chessboard.png 路径:
E:\openCV3.4.3\opencv\sources\samples\data

chessboard.png:
请添加图片描述
(2)拍摄之后进行相机内外参标定,这里我采用张正友相机标定法进行标定。

3.截取图像C++代码

功能:拍摄一定数量的棋盘图照片,用于后续的相机标定。
运行时,键盘按下 “k” 或者 “K” (注意是英文输入法下),截取一张图片;键盘按下 “q” 或者 “Q” ,退出程序运行,图像截取结束。截取的图片依次输出为 1.jpg、2.jpg、3.jpg…

#include <opencv2/opencv.hpp>
#include <string>
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
	VideoCapture inputVideo(0);
	if (!inputVideo.isOpened())
	{
		cout << "Could not open the input video " << endl;
		return -1;
	}

	Mat frame;
	string imgname;
	int f = 1;
	while (1) 
	{
		inputVideo >> frame;              
		if (frame.empty()) 
			break;        
		imshow("Camera", frame);
		char key = waitKey(1);
		if (key == 'q' || key == 'Q') // 退出运行
			break;
		if (key == 'k' || key == 'K') // 截取图片
		{
			cout << "frame:" << f << endl;
			imgname = to_string(f++) + ".jpg";
			imwrite(imgname, frame);
		}
	}
	cout << "Finished writing" << endl;
	return 0;
}

截取完毕后输出图片在VS当前工程目录下:
在这里插入图片描述

这里我拍摄了10张图片。

4.标定C++代码

Ref:
openCV—相机内外参标定
【笔记】Opencv张正友相机标定傻瓜教程

首先新建文本文档 ”calibdata.txt”,将刚才输出的图片名写入文件 calibdata.txt 然后保存.

1.jpg    
2.jpg  
3.jpg  
4.jpg  
5.jpg  
6.jpg  
7.jpg  
8.jpg  
9.jpg
10.jpg  

注意文件末尾不要留空行,否则会出现报错:

Assertion failed (dims <= 2 && step[0] > 0) in cv::Mat::locateROI

标定代码(C++):

#include <iostream>
#include <sstream>
#include <time.h>
#include <stdio.h>
#include <fstream>

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#include <opencv2/highgui/highgui.hpp>

using namespace cv;
using namespace std;

void main()
{
	ifstream fin("calibdata.txt");             /* 标定所用图像文件的路径 */
	ofstream fout("caliberation_result.txt");  /* 保存标定结果的文件 */

											   // 读取每一幅图像,从中提取出角点,然后对角点进行亚像素精确化
	int image_count = 0;  /* 图像数量 */
	Size image_size;      /* 图像的尺寸 */
	Size board_size = Size(7, 7);             /* 标定板上每行、列的角点数 */
	vector<Point2f> image_points_buf;         /* 缓存每幅图像上检测到的角点 */
	vector<vector<Point2f>> image_points_seq; /* 保存检测到的所有角点 */
	string filename;      // 图片名
	vector<string> filenames;

	while (getline(fin, filename))
	{
		++image_count;
		Mat imageInput = imread(filename);
		filenames.push_back(filename);

		// 读入第一张图片时获取图片大小
		if (image_count == 1)
		{
			image_size.width = imageInput.cols;
			image_size.height = imageInput.rows;
		}

		/* 提取角点 */
		if (0 == findChessboardCorners(imageInput, board_size, image_points_buf))
		{
			cout << "can not find chessboard corners!\n";  // 找不到角点
			exit(1);
		}
		else
		{
			Mat view_gray;
			cvtColor(imageInput, view_gray, CV_RGB2GRAY);  // 转灰度图

			/* 亚像素精确化 */
			// image_points_buf 初始的角点坐标向量,同时作为亚像素坐标位置的输出
			// Size(5,5) 搜索窗口大小
			// (-1,-1)表示没有死区
			// TermCriteria 角点的迭代过程的终止条件, 可以为迭代次数和角点精度两者的组合
			cornerSubPix(view_gray, image_points_buf, Size(5, 5), Size(-1, -1), TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1));

			image_points_seq.push_back(image_points_buf);  // 保存亚像素角点

			/* 在图像上显示角点位置 */
			drawChessboardCorners(view_gray, board_size, image_points_buf, false); // 用于在图片中标记角点

			imshow("Camera Calibration", view_gray);       // 显示图片

			waitKey(500); //暂停0.5S      
		}
	}
	int CornerNum = board_size.width * board_size.height;  // 每张图片上总的角点数

	//-------------以下是摄像机标定------------------

	/*棋盘三维信息*/
	Size square_size = Size(10, 10);         /* 实际测量得到的标定板上每个棋盘格的大小 */
	vector<vector<Point3f>> object_points;   /* 保存标定板上角点的三维坐标 */

	/*内外参数*/
	Mat cameraMatrix = Mat(3, 3, CV_32FC1, Scalar::all(0));  /* 摄像机内参数矩阵 */
	vector<int> point_counts;   // 每幅图像中角点的数量
	Mat distCoeffs = Mat(1, 5, CV_32FC1, Scalar::all(0));       /* 摄像机的5个畸变系数:k1,k2,p1,p2,k3 */
	vector<Mat> tvecsMat;      /* 每幅图像的旋转向量 */
	vector<Mat> rvecsMat;      /* 每幅图像的平移向量 */

	/* 初始化标定板上角点的三维坐标 */
	int i, j, t;
	for (t = 0; t<image_count; t++)
	{
		vector<Point3f> tempPointSet;
		for (i = 0; i<board_size.height; i++)
		{
			for (j = 0; j<board_size.width; j++)
			{
				Point3f realPoint;

				/* 假设标定板放在世界坐标系中z=0的平面上 */
				realPoint.x = i * square_size.width;
				realPoint.y = j * square_size.height;
				realPoint.z = 0;
				tempPointSet.push_back(realPoint);
			}
		}
		object_points.push_back(tempPointSet);
	}

	/* 初始化每幅图像中的角点数量,假定每幅图像中都可以看到完整的标定板 */
	for (i = 0; i<image_count; i++)
	{
		point_counts.push_back(board_size.width * board_size.height);
	}

	/* 开始标定 */
	// object_points 世界坐标系中的角点的三维坐标
	// image_points_seq 每一个内角点对应的图像坐标点
	// image_size 图像的像素尺寸大小
	// cameraMatrix 输出,内参矩阵
	// distCoeffs 输出,畸变系数
	// rvecsMat 输出,旋转向量
	// tvecsMat 输出,位移向量
	// 0 标定时所采用的算法
	calibrateCamera(object_points, image_points_seq, image_size, cameraMatrix, distCoeffs, rvecsMat, tvecsMat, 0);

	//------------------------标定完成------------------------------------

	// -------------------对标定结果进行评价------------------------------

	double total_err = 0.0;         /* 所有图像的平均误差的总和 */
	double err = 0.0;               /* 每幅图像的平均误差 */
	vector<Point2f> image_points2;  /* 保存重新计算得到的投影点 */
	fout << "每幅图像的标定误差:\n";

	for (i = 0; i<image_count; i++)
	{
		vector<Point3f> tempPointSet = object_points[i];

		/* 通过得到的摄像机内外参数,对空间的三维点进行重新投影计算,得到新的投影点 */
		projectPoints(tempPointSet, rvecsMat[i], tvecsMat[i], cameraMatrix, distCoeffs, image_points2);

		/* 计算新的投影点和旧的投影点之间的误差*/
		vector<Point2f> tempImagePoint = image_points_seq[i];
		Mat tempImagePointMat = Mat(1, tempImagePoint.size(), CV_32FC2);
		Mat image_points2Mat = Mat(1, image_points2.size(), CV_32FC2);

		for (int j = 0; j < tempImagePoint.size(); j++)
		{
			image_points2Mat.at<Vec2f>(0, j) = Vec2f(image_points2[j].x, image_points2[j].y);
			tempImagePointMat.at<Vec2f>(0, j) = Vec2f(tempImagePoint[j].x, tempImagePoint[j].y);
		}
		err = norm(image_points2Mat, tempImagePointMat, NORM_L2);
		total_err += err /= point_counts[i];
		fout << "第" << i + 1 << "幅图像的平均误差:" << err << "像素" << endl;
	}
	fout << "总体平均误差:" << total_err / image_count << "像素" << endl << endl;

	//-------------------------评价完成---------------------------------------------

	//-----------------------保存定标结果------------------------------------------- 
	Mat rotation_matrix = Mat(3, 3, CV_32FC1, Scalar::all(0));  /* 保存每幅图像的旋转矩阵 */
	fout << "相机内参数矩阵:" << endl;
	fout << cameraMatrix << endl << endl;
	fout << "畸变系数:\n";
	fout << distCoeffs << endl << endl << endl;
	for (int i = 0; i<image_count; i++)
	{
		fout << "第" << i + 1 << "幅图像的旋转向量:" << endl;
		fout << tvecsMat[i] << endl;

		/* 将旋转向量转换为相对应的旋转矩阵 */
		Rodrigues(tvecsMat[i], rotation_matrix);
		fout << "第" << i + 1 << "幅图像的旋转矩阵:" << endl;
		fout << rotation_matrix << endl;
		fout << "第" << i + 1 << "幅图像的平移向量:" << endl;
		fout << rvecsMat[i] << endl << endl;
	}
	fout << endl;

	//--------------------标定结果保存结束-------------------------------

	//----------------------显示定标结果--------------------------------

	Mat mapx = Mat(image_size, CV_32FC1);
	Mat mapy = Mat(image_size, CV_32FC1);
	Mat R = Mat::eye(3, 3, CV_32F);
	string imageFileName;
	std::stringstream StrStm;
	for (int i = 0; i != image_count; i++)
	{
		initUndistortRectifyMap(cameraMatrix, distCoeffs, R, cameraMatrix, image_size, CV_32FC1, mapx, mapy);
		Mat imageSource = imread(filenames[i]);
		Mat newimage = imageSource.clone();
		remap(imageSource, newimage, mapx, mapy, INTER_LINEAR);
		StrStm.clear();
		imageFileName.clear();
		StrStm << i + 1;
		StrStm >> imageFileName;
		imageFileName += "_d.jpg";
		imwrite(imageFileName, newimage);
	}

	fin.close();
	fout.close();
	return;
}

角点检测效果:
在这里插入图片描述

运行后VS当前工程目录下输出对应的标定后10张图片:
在这里插入图片描述

并且在 caliberation_result.txt 中得到标定结果:

每幅图像的标定误差:
第1幅图像的平均误差:0.013196像素
第2幅图像的平均误差:0.0216657像素
第3幅图像的平均误差:0.0217481像素
第4幅图像的平均误差:0.0254981像素
第5幅图像的平均误差:0.0239333像素
第6幅图像的平均误差:0.017826像素
第7幅图像的平均误差:0.0232136像素
第8幅图像的平均误差:0.0231145像素
第9幅图像的平均误差:0.0470236像素
第10幅图像的平均误差:0.0154766像素
总体平均误差:0.0232695像素

相机内参数矩阵:
[505.1666120558573, 0, 310.1660811401983;
 0, 488.6105193422827, 249.9495562500046;
 0, 0, 1]

畸变系数:
[0.1311093141581356, -0.9654453502019078, 0.001118708198552768, -0.001126801336773416, 2.286059640823912]


第1幅图像的旋转向量:
[-37.91579109884059;
 -29.12008303891709;
 145.038522323583]
第1幅图像的旋转矩阵:
[-0.2578169368901203, -0.8295425114162431, -0.4953681951900302;
 0.9564626210175542, -0.2917064863318113, -0.009304860459955183;
 -0.1367833383450855, -0.4762001129621674, 0.8686332774917115]
第1幅图像的平移向量:
[2.171107396538949;
 2.104249697603109;
 0.3380832402207642]

第2幅图像的旋转向量:
[-29.36929014204156;
 -30.43767724876516;
 145.8627065313358]
第2幅图像的旋转矩阵:
[0.4953381063692663, -0.8244703490881966, -0.2736581879880385;
 0.8651086842696649, 0.4967904900977356, 0.06918217508622276;
 0.07891213328581717, -0.2710126425516693, 0.9593357195463131]
第2幅图像的平移向量:
[2.192414199084543;
 2.167143157075016;
 -0.02241130380397732]

第3幅图像的旋转向量:
[43.41090882554612;
 -29.90585262472088;
 130.4223820748588]
第3幅图像的旋转矩阵:
[-0.5974917709340989, -0.7122356798120717, 0.3684072747227861;
 0.4805682690426949, -0.6858362778564604, -0.5465187451179547;
 0.641917223990245, -0.1494956065529423, 0.7520594000250418]
第3幅图像的平移向量:
[0.06297801857808814;
 -2.912208294692916;
 -0.02236073998847246]
 
第4幅图像的旋转向量:
[22.3196598690343;
 -24.93883415944185;
 120.89012963209]
第4幅图像的旋转矩阵:
[0.9753046439544359, 0.2153626974780553, 0.04898734546682917;
 -0.2171670754976463, 0.9755052657394266, 0.03504194393081224;
 -0.04024068588767379, -0.04481500920033632, 0.9981845030602625]
第4幅图像的平移向量:
[-0.0839898065700953;
 -3.059111495460135;
 -0.651727632156657]
... 

由此我们得到相机内参数矩阵和畸变系数,在后续的测距中将会用到(用相同的摄像头)。

PnP测距

代码

本来想直接贴这部分的代码,毕竟前面的准备工作在网上搜一下有很多可以参考的,不过为了方便读者使用,还是简单介绍一下前面的部分。

版本:VS2015 + openCV3.4.3

#include <opencv2/opencv.hpp>
#include <math.h>

using namespace std;
using namespace cv;

int main(int argc, char **argv)
{
	Mat image = imread("14.jpg");

	// 2D 特征点像素坐标,这里是用PS找出,也可以用鼠标事件画出特征点
	vector<Point2d> image_points;
	image_points.push_back(Point2d(152, 92));
	image_points.push_back(Point2d(426, 94));
	image_points.push_back(Point2d(428, 394));
	image_points.push_back(Point2d(126, 380));

	// 画出四个特征点
	for (int i = 0; i < image_points.size(); i++)
	{
		circle(image, image_points[i], 3, Scalar(0, 0, 255), -1);
	}

	// 3D 特征点世界坐标,与像素坐标对应,单位是mm
	std::vector<Point3d> model_points;
	model_points.push_back(Point3d(-42.5f, -42.5f, 0)); // 左上角(-42.5mm,-42.5mm)
	model_points.push_back(Point3d(+42.5f, -42.5f, 0));
	model_points.push_back(Point3d(+42.5f, +42.5f, 0));
	model_points.push_back(Point3d(-42.5f, +42.5f, 0));
	// 注意世界坐标和像素坐标要一一对应

	// 相机内参矩阵和畸变系数均由相机标定结果得出
	// 相机内参矩阵
	Mat camera_matrix = (Mat_<double>(3, 3) << 505.1666120558573, 0, 310.1660811401983,
	0, 488.6105193422827, 249.9495562500046,
	0, 0, 1);
	// 相机畸变系数
	Mat dist_coeffs = (Mat_<double>(5, 1) << 0.1311093141581356, -0.9654453502019078,
	0.001118708198552768, -0.001126801336773416, 2.286059640823912);

	cout << "Camera Matrix " << endl << camera_matrix << endl << endl;
	// 旋转向量
	Mat rotation_vector;
	// 平移向量
	Mat translation_vector;

	// pnp求解
	solvePnP(model_points, image_points, camera_matrix, dist_coeffs, \
		rotation_vector, translation_vector, 0, CV_ITERATIVE);
	// 默认ITERATIVE方法,可尝试修改为EPNP(CV_EPNP),P3P(CV_P3P)

	cout << "Rotation Vector " << endl << rotation_vector << endl << endl;
	cout << "Translation Vector" << endl << translation_vector << endl << endl;

	Mat Rvec;
	Mat_<float> Tvec;
	rotation_vector.convertTo(Rvec, CV_32F);  // 旋转向量转换格式
	translation_vector.convertTo(Tvec, CV_32F); // 平移向量转换格式 

	Mat_<float> rotMat(3, 3);
	Rodrigues(Rvec, rotMat);
	// 旋转向量转成旋转矩阵
	cout << "rotMat" << endl << rotMat << endl << endl;

	Mat P_oc;
	P_oc = -rotMat.inv() * Tvec;
	// 求解相机的世界坐标,得出p_oc的第三个元素即相机到物体的距离即深度信息,单位是mm
	cout << "P_oc" << endl << P_oc << endl;

	imshow("Output", image);
	waitKey(0);
}
  • 注意相机内参数矩阵和畸变系数要根据自己的摄像头来调整;
  • 2D 特征点像素坐标,我这里是用PS找出,也可以用鼠标事件找出特征点;
  • 3D 特征点世界坐标也要根据实际物体大小调整。

Ref:
PnP 单目相机位姿估计(二):solvePnP利用二维码求解相机世界坐标

测试

图片中物体距摄像头大概14cm,边长8.5cm
请添加图片描述

输出

ITERATIVE方法:

Camera Matrix
[505.1666120558573, 0, 310.1660811401983;
 0, 488.6105193422827, 249.9495562500046;
 0, 0, 1]

Rotation Vector
[-0.1157002831731034;
 0.1179963668031952;
 0.03141048973023804]

Translation Vector
[-9.106034353775875;
 -4.378822864319527;
 144.5117521867436]

rotMat
[0.99256271, -0.03807259, 0.11562786;
 0.024452539, 0.99283034, 0.1170042;
 -0.11925349, -0.11330661, 0.9863773]

P_oc
[26.378914;
 20.374874;
 -140.97787]

距离为 140.97787 mm

EPNP方法:

Camera Matrix
[505.1666120558573, 0, 310.1660811401983;
 0, 488.6105193422827, 249.9495562500046;
 0, 0, 1]

Rotation Vector
[-0.1616954415750413;
 0.06679582621203471;
 0.03007409518085349]

Translation Vector
[-8.402766835187963;
 -5.11616207912305;
 144.6472787556692]

rotMat
[0.99732399, -0.03530252, 0.064020529;
 0.024530273, 0.98651057, 0.16184933;
 -0.068870619, -0.15984578, 0.98473662]

P_oc
[18.467728;
 27.871765;
 -141.07347]

距离为 141.07347 mm

P3P方法:

Camera Matrix
[505.1666120558573, 0, 310.1660811401983;
 0, 488.6105193422827, 249.9495562500046;
 0, 0, 1]

Rotation Vector
[0.5013850510761655;
 0.606438260299431;
 0.03647540988139936]

Translation Vector
[-6.638223465259809;
 -3.076172335778562;
 146.0742567050607]

rotMat
[0.82479835, 0.11151069, 0.55432212;
 0.17714798, 0.8800413, -0.44061995;
 -0.53696018, 0.46161965, 0.70610273]

P_oc
[84.456192;
 -63.983349;
 -100.81914]

距离为 100.81914 mm

误差有点大…

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【opencv3】PnP测距(完整流程附C++代码) 的相关文章

随机推荐

  • 手动搭建史上最高效最便宜的全自动图床Typora+PicGo+Gitee

    文章目录 效果图下载软件创建gitee存储配置picgogitee插件后重启picgo开机自启 xff0c 图片重命名查看当前图床的服务端口 xff0c 方便接下来的配置 测试上传 效果图 下载软件 Typora 43 PicGo 创建gi
  • 优秀的 Spring Boot 语言开源项目

    优秀的 Spring Boot 语言开源项目 xff0c 涵盖了企业级系统框架 文件文档系统 秒杀系统 微服务化系统 后台管理系统等各个方面 Spring Boot 中文索引 项目名称 分布式敏捷开发系统架构 项目简介 xff1a 基于 S
  • xxl-job中cron表达式详解

    7个部分组成 秒数 分钟 小时 日期 月份 星期 年份 可为空 5 每部分范围 及 符号解释 秒 0 59 分 0 59 小时 0 23 日期 1 31 L W C 月份 1 12 或者 JAN DEC 可以用0 11 或用字符串 JAN
  • 【高效能办公】快速搭建前端Mock数据接口供后端调用

    配置安装 span class token comment 全局安装 span span class token function npm span i 64 shymean mock server g span class token c
  • 【Linux】上传下载文件命令rz、sz

    文章目录 安装上传下载对比工具 安装 要使用rz sz命令传输文件需要给服务器安装lrzsz yum y install lrzsz 上传 上传文件只需在shell终端仿真器中输入命令 rz xff0c 即可从弹出的对话框中选择本地磁盘上的
  • 【Java】程序制作Docker镜像 推荐方案

    文章目录 背景制作条件Dockerfile指令 xff1a 实践步骤使用官网提供的基础镜像作为镜像基础设定容器的正确的时间和时区容器中采用非 root 用户权限启动应用程序 DockerFile样例COPY 与 ADD 区别 举个栗子 背景
  • 【Linux】ln -sf软连接

    文章目录 功能格式命令参数 xff1a 软链接硬链接 场景情况 功能 功能是为某一个文件在另外一个位置建立一个同步的链接 当 我们需要在不同的目录 xff0c 用到相同的文件时 xff0c 我们不需要在每一个需要的目录下都放一个必须相同的文
  • 【Java】Collection遍历使用Iterable迭代器

    span class token keyword package span span class token namespace com span class token punctuation span example span clas
  • 【SpringBoot】Thymeleaf融合ajax

    文章目录 效果ajax htmlController 效果 局部刷新 不会出现表单重复提交 ajax html span class token operator lt span span class token operator span
  • 【Java】file操作-删除文件中某一行中符合某一规则的

    效果 此处规则 xff0c 删除已空格分隔的域名行 xff0c 为防止因制表符等引起误删 xff0c 强制插入的规则空格分隔 同时要过滤掉 和其他非自己插入的数据格式 xff0c 避免误删 代码 package com ths arsena
  • 【Vue】组件ant design of vue

    文章目录 安装项目引入全局组件常见问题关键字避免处理校验异常es link栈溢出ant图库未加载 git追加到上一次提交文件 简单使用自定义组件 header列表加any问题ts类型 图标iocn 安装 span class token f
  • 【SpringBoot】配置文件application自定义内容引入

    背景 进行Spring Boot配置文件部署时 xff0c 发出警告Spring Boot Configuration Annotation Processor not configured xff0c 但是不影响运行 解决方案 pom x
  • ssh 登录失败

    SSH远程登录失败 xff0c 提示 Password authentication failed The authenticity of host 39 10 3 25 201 10 3 25 201 39 can 39 t be est
  • 【Vue】集成HTTP库Axios

    安装 进入vue项目目录 npm install axios 64 0 21 0 save 版本 简单使用 在一个页面中通过axios发送请求拿到后端数据 Home vue span class token function import
  • 【Nginx】mac快速安装使用

    效果图 安装 查找 brew search nginx 可用版本 使用 brew install nginx 安装nginx 查看brew列表 span class token punctuation span base span clas
  • 【Vue】vue3数据绑定

    文章目录 Vue2分析当前应用环境类型项目代码结构vue3取代生命周期函数的应用 vue3响应式ref xff08 xff09 数据绑定响应式reactive xff08 xff09 数据绑定比较两者 Vue2 分析当前应用环境 企业老项目
  • 【Java】SpringBoot整合多数据源JdbcTemplate、Mybatis、Jpa

    SpringBoot 整合 JdbcTemplate 多数据源 pom span class token comment lt 引入阿里的数据源 gt span span class token tag span class token t
  • 【Vue】集成Vuex存储

    导入 官网https vuex vuejs org zh installation html E7 9B B4 E6 8E A5 E4 B8 8B E8 BD BD cdn E5 BC 95 E7 94 A8 安装 npm install
  • element-plus日历(Calendar)动态渲染+避坑指南

    效果图 实战代码 span class token operator lt span template span class token operator gt span span class token operator lt span
  • 【opencv3】PnP测距(完整流程附C++代码)

    概述 我们只要获得特征点的世界坐标 xff08 三维坐标 xff09 2D坐标 xff08 像素坐标 xff09 相机内参矩阵 相机畸变参数矩阵以上四个参数即可以解得相机与标志物之间的外参 xff08 旋转矩阵R 平移矩阵T xff09 x