相机已经存在了很长时间。然而,随着 20 世纪后期廉价针孔相机的推出,它们在我们的日常生活中变得司空见惯。不幸的是,这种廉价是有代价的:严重的失真。幸运的是,这些是常数,通过校准和一些重新映射,我们可以纠正这一点。此外,通过校准,您还可以确定相机的自然单位(像素)与现实世界单位(例如毫米)之间的关系。
理论
对于畸变,OpenCV 考虑了径向和切向因素。对于径向因子,使用以下公式:
因此,对于 (x,y) 坐标处未失真的像素点,它在失真图像上的位置将为 (x_{distorted} y_{distorted})。径向畸变的存在以“桶”或“鱼眼”效应的形式表现出来。(x,y)(xdIS T ORTEdydIS T ORTEd)
切向畸变的发生是因为拍摄图像的镜头与成像平面不完全平行。它可以通过以下公式表示:
因此,我们有五个失真参数,在 OpenCV 中,这些参数表示为具有 5 列的一行矩阵:
现在对于单位转换,我们使用以下公式:
在这里,w 的存在通过使用单调坐标系(和 w=Z)来解释。未知参数是 f_x 和 f_y(相机焦距)和 (c_x, c_y),它们是以像素坐标表示的光学中心。如果对于两个轴,使用具有给定纵横比(通常为 1)的公共焦距,则 f_y=f_xa,在上面的公式中,我们将有一个焦距 f。包含这四个参数的矩阵称为相机矩阵*。虽然无论使用何种相机分辨率,失真系数都是相同的,但这些失真系数应与校准分辨率的当前分辨率一起缩放。ww=Zfxfy(cx,cy)一个fy=fx∗af
确定这两个矩阵的过程就是校准。这些参数的计算是通过基本的几何方程完成的。使用的方程式取决于所选的校准对象。目前 OpenCV 支持三种类型的对象进行校准:
- 经典黑白棋盘
- ChArUco板图案
- 对称圆形图案
- 不对称圆形图案
基本上,您需要用相机拍摄这些模式的快照,并让 OpenCV 找到它们。每个找到的模式都会产生一个新方程。要求解方程,您至少需要预定数量的模式快照来形成一个适配方程组。这个数字对于棋盘模式来说较高,而对于圆盘模式来说,这个数字较小。例如,从理论上讲,棋盘模式至少需要两个快照。然而,在实践中,我们的输入图像中存在大量的噪点,因此为了获得良好的效果,您可能需要至少 10 张不同位置的输入模式的良好快照。
目标
示例应用程序将:
- 确定失真矩阵
- 确定相机矩阵
- 从相机、视频和图像文件列表中获取输入
- 从 XML/YAML 文件读取配置
- 将结果保存到 XML/YAML 文件中
- 计算重投影误差
源代码
您也可以在 OpenCV 源代码库的文件夹中找到源代码或从此处下载。对于程序的用法,请使用参数运行它。该程序有一个基本参数:其配置文件的名称。如果没有给出,那么它将尝试打开名为“default.xml”的那个。下面是 XML 格式的示例配置文件。在配置文件中,您可以选择使用相机作为输入、视频文件或图像列表。如果选择最后一个,则需要创建一个配置文件,在其中枚举要使用的映像。下面是一个示例。要记住的重要部分是,需要使用应用程序工作目录中的绝对路径或相对路径来指定图像。您可以在上面提到的示例目录中找到所有这些内容。samples/cpp/tutorial_code/calib3d/camera_calibration/``-h
应用程序从配置文件中读取设置后启动。虽然这是其中的一个重要部分,但它与本教程的主题无关:相机校准。因此,我选择不在此处发布该部分的代码。有关如何执行此操作的技术背景,请参阅使用 XML 和 YAML 文件的文件输入和输出教程。
解释
-
阅读设置
设置 s;
const string inputSettingsFile = parser.get(0);
FileStorage fs(inputSettingsFile, FileStorage::READ);读取设置
如果 (!fs.isOpened())
{
cout << “无法打开配置文件:\”“ << inputSettingsFile << ”\“” << endl;
解析器.printMessage();
返回 -1;
}
fs[“设置”] >> s;
fs.release();关闭设置文件
为此,我使用了简单的 OpenCV 类输入操作。阅读文件后,我有一个额外的后处理功能来检查输入的有效性。只有当所有输入都良好时,goodInput 变量才会为 true。
-
获取下一个输入,如果它失败了,或者我们有足够的输入 - 校准
在此之后,我们有一个大循环,我们在其中执行以下操作:从图像列表、相机或视频文件中获取下一张图像。如果这失败了,或者我们有足够的图像,那么我们就会运行校准过程。在图像的情况下,我们跳出循环,否则,通过从检测模式更改为校准模式,剩余的帧将不会失真(如果设置了选项)。
为(;😉
{
垫子视图;
bool blinkOutput = 假;
视图 = s.nextImage();
----- 如果没有更多的图像,或者没有足够的图像,则停止校准并显示结果-------------
if( mode == CAPTURING && imagePoints.size() >= (size_t)s.nrFrames )
{
if(runCalibrationAndSave(s, imageSize, cameraMatrix, distCoeffs, imagePoints, grid_width,
release_object))
模式 = 校准;
还
模式 = 检测;
}
if(view.empty()) // 如果没有更多的图片,请停止循环
{
如果尚未达到校准阈值,请立即校准
if( mode != 校准 && !imagePoints.empty() )
runCalibrationAndSave(s, imageSize, cameraMatrix, distCoeffs, imagePoints, grid_width,
release_object);
破;
}
对于某些相机,我们可能需要翻转输入图像。在这里,我们也这样做。
-
在当前输入中查找模式
我上面提到的方程式的形成旨在找到输入中的主要模式:在棋盘的情况下,这是正方形的角,对于圆来说,嗯,圆本身。ChArUco 棋盘相当于棋盘,但角由 ArUco 标记进行加工。它们的位置将形成结果,该结果将被写入 pointBuf 向量。
vector pointBuf;
发现布尔;
int chessBoardFlags = CALIB_CB_ADAPTIVE_THRESH |CALIB_CB_NORMALIZE_IMAGE;
如果(!s.useFisheye) {
快速检查错误地失败,出现鱼眼等高失真
chessBoardFlags |= CALIB_CB_FAST_CHECK;
}
switch( s.calibrationPattern ) // 在输入格式上查找特征点
{
箱设置::CHESSBOARD:
found = findChessboardCorners( view, s.boardSize, pointBuf, chessBoardFlags);
破;
箱设置::CHARUCOBOARD:
ch_detector.detectBoard( view, pointBuf, markerIds);
found = pointBuf.size() == (size_t)((s.boardSize.height - 1)*(s.boardSize.width - 1));
破;
箱设置::CIRCLES_GRID:
found = findCirclesGrid( view, s.boardSize, pointBuf );
破;
箱设置::ASYMMETRIC_CIRCLES_GRID:
found = findCirclesGrid( view, s.boardSize, pointBuf, CALIB_CB_ASYMMETRIC_GRID );
破;
默认值:
发现 = 假;
破;
}
根据输入模式的类型,您可以使用 cv::findChessboardCorners 或 cv::findCirclesGrid 函数或 cv::aruco::CharucoDetector::d etectBoard 方法。对于所有这些,您传递当前图像和电路板的大小,您将获得图案的位置。cv::findChessboardCorners 和 cv::findCirclesGrid 返回一个布尔变量,该变量说明是否在输入中找到了模式(我们只需要考虑那些确实如此的图像! 可以检测部分可见的图案,并返回可见内角的坐标和 ID。
CharucoDetector::detectBoard
-
注意
棋盘大小和匹配点数对于棋盘、圆圈网格和 ChArUco 是不同的。所有与棋盘相关的算法都期望内角的数量作为棋盘的宽度和高度。圆网格的板大小只是两个网格尺寸的圆数。ChArUco 板尺寸以正方形为单位定义,但检测结果是内角列表,这就是为什么在两个尺寸中都小 1 的原因。
再说一次,对于相机,我们只在输入延迟时间过后才拍摄相机图像。这样做是为了让用户移动棋盘并获得不同的图像。相似的图像会产生相似的方程,而校准步骤中的相似方程将形成一个病态问题,因此校准将失败。对于正方形图像,角的位置只是近似值。我们可以通过调用 cv::cornerSubPix 函数来改进这一点。( 用于控制搜索窗口的边长。其默认值为 11。 可以通过命令行参数进行更改。它将产生更好的校准结果。在此之后,我们将有效的输入结果添加到 imagePoints 向量中,以将所有方程收集到一个容器中。最后,出于可视化反馈目的,我们将使用 cv::findChessboardCorners 函数在输入图像上绘制找到的点。
winSize``winSize``--winSize=<number>
if (found) // 如果成功完成,
{
提高棋盘找到的角坐标精度
if( s.calibrationPattern == 设置::CHESSBOARD)
{
垫子视图灰色;
cvtColor(视图, 视图灰色, COLOR_BGR2GRAY);
cornerSubPix( viewGray, pointBuf, Size(winSize,winSize),
Size(-1,-1), TermCriteria( TermCriteria::EPS+TermCriteria::COUNT, 30, 0.0001 ));
}
if( mode == CAPTURING && // 对于相机,只在延迟时间后拍摄新样本
(!s.inputCapture.isOpened() || clock() - prevTimestamp > s.delay1e-3CLOCKS_PER_SEC) ) )
{
imagePoints.push_back(点);
prevTimestamp = 时钟();
blinkOutput = s.inputCapture.isOpened();
}
画出角落。
if(s.calibrationPattern == 设置::CHARUCOBOARD)
drawChessboardCorners( view, cv::Size(s.boardSize.width-1, s.boardSize.height-1), Mat(pointBuf), 找到 );
还
drawChessboardCorners( view, s.boardSize, Mat(pointBuf), 找到 );
}
-
-
向用户显示状态和结果,以及应用程序的命令行控制
此部分显示图像上的文本输出。
string msg = (mode == CAPTURING) ?“100/100”:
模式 == 校准 ?“Calibrated” : “按’g’开始”;
int 基线 = 0;
尺寸 textSize = getTextSize(msg, 1, 1, 1, &baseLine);
点 textOrigin(view.cols - 2textSize.width - 10, view.rows - 2baseLine - 10);
if( 模式 == 捕获 )
{
if(s.showUndistorted)
msg = cv::format( “%d/%d Undist”, (int)imagePoints.size(), s.nrFrames );
还
msg = cv::format( “%d/%d”, (int)imagePoints.size(), s.nrFrames );
}
putText( view, msg, textOrigin, 1, 1, mode == 校准 ?绿色:红色);
如果( blink输出 )
bitwise_not(视图,视图);
如果我们运行校准并得到带有失真系数的相机矩阵,我们可能需要使用 cv::undistort 函数校正图像:
if( mode == 校准 && s.showUndistorted )
{
垫温度 = view.clone();
如果 (s.useFisheye)
{
垫子 newCamMat;
fisheye::estimateNewCameraMatrixForUndistortRectify(cameraMatrix, distCoeffs, imageSize,
Matx33d::eye(), newCamMat, 1);
cv::fisheye::undistortImage(temp, view, cameraMatrix, distCoeffs, newCamMat);
}
还
undistort(temp, view, cameraMatrix, distCoeffs);
}
然后我们显示图像并等待输入键,如果这是 u,我们切换失真消除,如果是 g,我们再次开始检测过程,最后对于 ESC 键,我们退出应用程序:
imshow(“图像视图”, 视图);
char key = (char)waitKey(s.inputCapture.isOpened() ? 50 : s.delay);
if( 键 == ESC_KEY )
破;
if( key == ‘u’ && mode == 校准 )
s.showUndistorted = !s.showUndistorted;
if( s.inputCapture.isOpened() && 键 == ‘g’ )
{
mode = 捕获;
imagePoints.clear();
}
-
同时显示图像的失真消除
使用图像列表时,无法消除循环内部的失真。因此,您必须在循环之后执行此操作。现在,我将利用这一点来扩展 cv::undistort 函数,该函数实际上首先调用 cv::initUndistortRectifyMap 来查找变换矩阵,然后使用 cv::remap 函数执行变换。因为,在成功校准图计算后,只需执行一次,因此通过使用此扩展表单,您可以加快应用速度:
if( s.inputType == Settings::IMAGE_LIST && s.showUndistorted && !cameraMatrix.empty())
{
垫视图、rview、map1、map2;
如果 (s.useFisheye)
{
垫子 newCamMat;
fisheye::estimateNewCameraMatrixForUndistortRectify(cameraMatrix, distCoeffs, imageSize,
Matx33d::eye(), newCamMat, 1);
fisheye::initUndistortRectifyMap(cameraMatrix, distCoeffs, Matx33d::eye(), newCamMat, imageSize,
CV_16SC2, map1, map2);
}
还
{
cameraMatrix、distCoeffs、Mat()、
getOptimalNewCameraMatrix(cameraMatrix, distCoeffs, imageSize, 1, imageSize, 0), imageSize,
CV_16SC2, map1, map2);
}
for(size_t i = 0; i < s.imageList.size(); i++ )
{
视图 = imread(s.imageList[i], IMREAD_COLOR);
如果(view.empty())
继续;
remap(view, rview, map1, map2, INTER_LINEAR);
imshow(“图像视图”, rview);
char c = (char)waitKey();
if( c == ESC_KEY || c == ‘q’ || c == ‘Q’ )
破;
}
}
校准和保存
由于每台相机只需进行一次校准,因此在校准成功后保存校准是有意义的。这样,以后您就可以将这些值加载到程序中。因此,我们首先进行校准,如果校准成功,我们将结果保存到 OpenCV 样式的 XML 或 YAML 文件中,具体取决于您在配置文件中提供的扩展名。
因此,在第一个函数中,我们只是拆分了这两个进程。由于我们想要保存许多校准变量,因此我们将在此处创建这些变量,并将这两个变量传递给校准和保存函数。同样,我不会显示保存部分,因为这与校准几乎没有共同之处。浏览源文件,以了解如何以及内容:
bool runCalibrationAndSave(Settings&s, Size imageSize, Mat&cameraMatrix, Mat&distCoeffs,
vector<vector >imagePoints、float grid_width、bool release_object)
{
vectorrvecs、tvecs;
vector reprojErrs;
双倍总计AvgErr = 0;
vector newObjPoints;
bool ok = runCalibration(s, imageSize, cameraMatrix, distCoeffs, imagePoints, rvecs, tvecs, reprojErrs,
totalAvgErr, newObjPoints, grid_width, release_object);
cout << (好吗?“Calibration succeeds” : “校准失败”)
<< “。avg re projection error = “ << totalAvgErr << endl;
如果(确定)
saveCameraParams(s, imageSize, cameraMatrix, distCoeffs, rvecs, tvecs, reprojErrs, imagePoints,
totalAvgErr, newObjPoints);
返回 OK;
}
我们在 cv::calibrateCameraRO 函数的帮助下进行校准。它具有以下参数:
-
对象指向。这是 Point3f 向量的向量,对于每个输入图像,它描述了图案的外观。如果我们有一个平面图案(如棋盘),那么我们可以简单地将所有 Z 坐标设置为零。这是存在这些重要点的点的集合。因为,我们对所有输入图像使用单一模式,因此我们只需计算一次,然后将其乘以所有其他输入视图。我们使用 calcBoardCornerPositions 函数计算角点,如下所示:
static void calcBoardCornerPositions(Size boardSize, float squareSize, vector& corners,
设置:😛 attern patternType /= 设置::CHESSBOARD/)
{
corners.clear();
开关(模式类型)
{
箱设置::CHESSBOARD:
箱设置::CIRCLES_GRID:
for (int i = 0; i < boardSize.height; ++i) {
for (int j = 0; j < boardSize.width; ++j) {
corners.push_back(Point3f(jsquareSize, isquareSize, 0));
}
}
破;
箱设置::CHARUCOBOARD:
for (int i = 0; i < boardSize.height - 1; ++i) {
for (int j = 0; j < boardSize.width - 1; ++j) {
corners.push_back(Point3f(jsquareSize, isquareSize, 0));
}
}
破;
箱设置::ASYMMETRIC_CIRCLES_GRID:
for (int i = 0; i < boardSize.height; i++) {
for (int j = 0; j < boardSize.width; j++) {
corners.push_back(Point3f((2 * j + i % 2)squareSize, isquareSize, 0));
}
}
破;
默认值:
破;
}
}
然后乘以:
vector<vector > objectPoints(1);
calcBoardCornerPositions(s.boardSize, s.squareSize, objectPoints[0], s.calibrationPattern);
objectPoints[0][s.boardSize.width - 1].x = objectPoints[0][0].x + grid_width;
newObjPoints = objectPoints[0];
objectPoints.resize(imagePoints.size(),objectPoints[0]);
-
注意
如果您的校准板不准确、未测量、大致是平面目标(使用现成打印机在纸上的棋盘图案是最方便的校准目标,但大多数都不够准确),可以使用[250]中的方法来显着提高估计相机内在参数的精度。如果提供了命令行参数,则将调用此新的校准方法。在上面的代码片段中,实际上是由 设置的值。它是图案网格点的左上角 (0, 0, 0) 和右上角 (s.squareSize*(s.boardSize.width-1), 0, 0) 之间的测量距离。它应该用尺子或游标卡尺精确测量。校准后,newObjPoints 将更新为对象点的优化 3D 坐标。
-d=<number>``grid_width``-d=<number>
-
-
图像指向。这是 Point2f 向量的向量,对于每个输入图像,它包含重要点的坐标(棋盘的角和圆图案的圆心)。我们已经从 cv::findChessboardCorners 或 cv::findCirclesGrid 函数中收集了它。我们只需要传递它。
-
从相机、视频文件或图像获取的图像的大小。
-
要固定的对象点的索引。我们将其设置为 -1 以请求标准校准方法。如果要使用新的对象释放方法,请将其设置为校准板网格右上角点的索引。有关详细说明,请参见 cv::calibrateCameraRO。
int iFixedPoint = -1;
如果 (release_object)
iFixedPoint = s.boardSize.width - 1;
-
相机矩阵。如果我们使用固定纵横比选项,我们需要设置:
fx
cameraMatrix = Mat::eye(3, 3, CV_64F);
if( !s.useFisheye && s.flag & CALIB_FIX_ASPECT_RATIO )
cameraMatrix.at(0,0) = s.aspectRatio;
-
失真系数矩阵。使用零初始化。
distCoeffs = Mat::zeros(8, 1, CV_64F);
-
对于所有视图,该函数将计算旋转和平移向量,这些向量将对象点(在模型坐标空间中给出)转换为图像点(在世界坐标空间中给出)。第 7 个和第 8 个参数是矩阵的输出向量,在第 i 个位置包含第 i 个对象点到第 i 个图像点的旋转和平移向量。
-
校准模式点的更新输出向量。使用标准校准方法忽略此参数。
-
最后一个参数是标志。您需要在此处指定选项,例如固定焦距的纵横比、假设切向失真为零或固定主点。在这里,我们使用CALIB_USE_LU来获得更快的校准速度。
rms = calibrateCameraRO(objectPoints, imagePoints, imageSize, iFixedPoint,
cameraMatrix、distCoeffs、rvecs、tvecs、newObjPoints、
s.flag旗 |CALIB_USE_LU);
-
该函数返回平均重新投影误差。这个数字可以很好地估计所找到参数的精度。这应该尽可能接近于零。给定固有矩阵、失真矩阵、旋转矩阵和平移矩阵,我们可以通过使用 cv::p rojectPoints 首先将对象点转换为图像点来计算一个视图的误差。然后,我们计算变换得到的绝对范数与角/圆查找算法之间的绝对范数。为了找到平均误差,我们计算了所有校准图像计算的误差的算术平均值。
静态双精度 computeReprojectionErrors( const vector<vector >& objectPoints,
const vector<vector >& imagePoints,
const vector& rvecs, const vector& tvecs,
常量Mat& cameraMatrix , const Mat& distCoeffs,
vector&perViewErrors,bool fisheye)
{
vectorimagePoints2;
size_t 总积分 = 0;
double totalErr = 0, 错误;
perViewErrors.resize(objectPoints.size());
for(size_t i = 0; i < objectPoints.size(); ++i )
{
if(鱼眼)
{
fisheye::p rojectPoints(objectPoints[i], imagePoints2, rvecs[i], tvecs[i], cameraMatrix,
distCoeffs);
}
还
{
projectPoints(objectPoints[i], rvecs[i], tvecs[i], cameraMatrix, distCoeffs, imagePoints2);
}
err = norm(imagePoints[i], imagePoints2, NORM_L2);
size_t n = objectPoints[i].size();
perViewErrors[i] = (float) std::sqrt(err*err/n);
totalErr += 错误*错误;
总积分 += n;
}
返回 std::sqrt(totalErr/totalPoints);
}
然后作为配置文件中的输入传递。下面是在应用程序运行时发现的棋盘模式:images/CameraCalibration/VID5/VID5.XML
应用失真去除后,我们得到:
通过将输入宽度设置为 4,将高度设置为 11,这同样适用于这种不对称圆形图案。这一次,我通过为输入指定其 ID(“1”)来使用实时摄像机源。下面是检测到的模式的外观:
在这两种情况下,在指定的输出 XML/YAML 文件中,您都会找到相机和失真系数矩阵:
如何学习大模型 AI ?
由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。
但是具体到个人,只能说是:
“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。
这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。
我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。
😝有需要的小伙伴,可以点击下方链接免费领取或者V扫描下方二维码免费领取🆓
第一阶段(10天):初阶应用
该阶段让大家对大模型 AI有一个最前沿的认识,对大模型 AI 的理解超过 95% 的人,可以在相关讨论时发表高级、不跟风、又接地气的见解,别人只会和 AI 聊天,而你能调教 AI,并能用代码将大模型和业务衔接。
- 大模型 AI 能干什么?
- 大模型是怎样获得「智能」的?
- 用好 AI 的核心心法
- 大模型应用业务架构
- 大模型应用技术架构
- 代码示例:向 GPT-3.5 灌入新知识
- 提示工程的意义和核心思想
- Prompt 典型构成
- 指令调优方法论
- 思维链和思维树
- Prompt 攻击和防范
- …
第二阶段(30天):高阶应用
该阶段我们正式进入大模型 AI 进阶实战学习,学会构造私有知识库,扩展 AI 的能力。快速开发一个完整的基于 agent 对话机器人。掌握功能最强的大模型开发框架,抓住最新的技术进展,适合 Python 和 JavaScript 程序员。
- 为什么要做 RAG
- 搭建一个简单的 ChatPDF
- 检索的基础概念
- 什么是向量表示(Embeddings)
- 向量数据库与向量检索
- 基于向量检索的 RAG
- 搭建 RAG 系统的扩展知识
- 混合检索与 RAG-Fusion 简介
- 向量模型本地部署
- …
第三阶段(30天):模型训练
恭喜你,如果学到这里,你基本可以找到一份大模型 AI相关的工作,自己也能训练 GPT 了!通过微调,训练自己的垂直大模型,能独立训练开源多模态大模型,掌握更多技术方案。
到此为止,大概2个月的时间。你已经成为了一名“AI小子”。那么你还想往下探索吗?
- 为什么要做 RAG
- 什么是模型
- 什么是模型训练
- 求解器 & 损失函数简介
- 小实验2:手写一个简单的神经网络并训练它
- 什么是训练/预训练/微调/轻量化微调
- Transformer结构简介
- 轻量化微调
- 实验数据集的构建
- …
第四阶段(20天):商业闭环
对全球大模型从性能、吞吐量、成本等方面有一定的认知,可以在云端和本地等多种环境下部署大模型,找到适合自己的项目/创业方向,做一名被 AI 武装的产品经理。
- 硬件选型
- 带你了解全球大模型
- 使用国产大模型服务
- 搭建 OpenAI 代理
- 热身:基于阿里云 PAI 部署 Stable Diffusion
- 在本地计算机运行大模型
- 大模型的私有化部署
- 基于 vLLM 部署大模型
- 案例:如何优雅地在阿里云私有部署开源大模型
- 部署一套开源 LLM 项目
- 内容安全
- 互联网信息服务算法备案
- …
学习是一个过程,只要学习就会有挑战。天道酬勤,你越努力,就会成为越优秀的自己。
如果你能在15天内完成所有的任务,那你堪称天才。然而,如果你能完成 60-70% 的内容,你就已经开始具备成为一名大模型 AI 的正确特征了。
这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费
】
😝有需要的小伙伴,可以Vx扫描下方二维码免费领取==🆓