投影矩阵(Projection Matrix)

摘要:

       选择和设计合适的投影矩阵,实质上就是在决定如何解释和利用原始数据的内在结构与特性,从而服务于我们的数据分析或机器学习任务。投影矩阵的设计和应用能帮助实现诸如数据压缩、噪声去除、特征提取和模式识别等目标。

       投影矩阵在降维过程中起到了桥梁作用,它不仅改变了数据的表示形式,而且有助于解决高维数据带来的问题,如“维度灾难”以及数据可视化困难等。通过合理的投影,可以将复杂的高维数据转化为更易于理解的低维视图。 

       在机器学习和数据分析中,投影矩阵是一个非常重要的工具,它主要用于将高维数据降维或者变换到新的坐标系中。这个过程通常被称为线性变换或投影。

  1. 过程

    • 假设我们有一个原始的高维数据集X,其中每一列代表一个特征,每一行代表一个样本。
    • 投影矩阵P是一个m×n的矩阵(m是目标维度,n是原始维度),通过X乘以P(X*P),我们可以将原始的n维数据映射到m维的新空间中。
    • 这个新空间的基就是投影矩阵P的列向量,每个列向量定义了一个新的特征轴或者主成分。
  2. 原理

    • 不同的投影矩阵对应了不同的变换方式。例如,在PCA(主成分分析)中,投影矩阵是由原始数据协方差矩阵的前k个最大特征值对应的特征向量构成的,这样可以使得数据在新空间中的方差最大化,从而保留主要的信息和变化趋势。

    • 在LDA(线性判别分析)中,投影矩阵则是设计用来最大化类别间差异和最小化类别内差异的,以便于分类任务。

  3. 应用举例

    • 如果我们想降低数据维度以简化模型或减少计算复杂度,可以选择保留大部分能量(信息)的主成分作为新的特征轴,这就是PCA的作用。
    • 在图像识别等领域,可能需要根据类别标签对数据进行最优的线性划分,这时就可以用到LDA,通过特定的投影矩阵实现。

       因此,选择和设计合适的投影矩阵,实质上就是在决定如何解释和利用原始数据的内在

这是之前生成核线影像的代码, #include <iostream> #include <opencv2/opencv.hpp> #include <windows.h> using namespace cv; using namespace std; // 投影矩阵结构体 struct ProjectionMatrix { double data[3][4]; }; // 读取投影矩阵 ProjectionMatrix readProjectionMatrix() { ProjectionMatrix P; P.data[0][0] = 5526.4864457012472; P.data[0][1] = 6363.7183421283298; P.data[0][2] = -4131.0482340017224; P.data[0][3] = -24448406087.5781860000000; P.data[1][0] = 6172.8397894815880; P.data[1][1] = -5588.8214670529014; P.data[1][2] = -3384.5650572725663; P.data[1][3] = 15591209748.2791840000000; P.data[2][0] = -0.0319353657001; P.data[2][1] = 0.0213700694115; P.data[2][2] = -0.9992614535500; P.data[2][3] = -54202.4500385644710; return P; } ProjectionMatrix readProjectionMatrix2() { ProjectionMatrix P; P.data[0][0] = 5544.3514690660313; P.data[0][1] = 6327.0017140488953; P.data[0][2] = -4163.3807394948353; P.data[0][3] = -24333678727.6017230000000; P.data[1][0] = 6125.8241039199602; P.data[1][1] = -5628.1100012630295; P.data[1][2] = -3404.8221606458665; P.data[1][3] = 15747662227.6377830000000; P.data[2][0] = -0.0362355312611; P.data[2][1] = 0.0203282471373; P.data[2][2] = -0.9991365015065; P.data[2][3] = -48380.1370322157820; return P; } // 图像处理函数(保持原始位深) Mat processImage(const Mat& input) { Mat output = input.clone(); // 通道处理 if (output.channels() == 1) { cvtColor(output, output, COLOR_GRAY2BGR); } else if (output.channels() == 4) { cvtColor(output, output, COLOR_BGRA2BGR); } return output; } // 保存图像并验证 bool saveImageWithCheck(const string& path, const Mat& image, const string& name) { if (image.empty()) { cerr << "错误: " << name << " 图像为空!" << endl; return false; } double minVal, maxVal; minMaxLoc(image, &minVal, &maxVal); cout << name << " 像素范围: " << minVal << " - " << maxVal << endl; if (minVal == maxVal && minVal == 0) { cerr << "警告: " << name << " 可能是全黑图像!" << endl; } // 转换为8位保存 Mat saveImg; if (image.depth() == CV_16U) { image.convertTo(saveImg, CV_8U, 1.0 / 256.0); } else { image.convertTo(saveImg, CV_8U); } return imwrite(path, saveImg); } int main() { // 输入文件路径 string img1_path = "D:/data1/data1/017ER030.tif"; string img2_path = "D:/data1/data1/017ER031.tif"; string output_dir = "D:/data1/shuchu/"; // 读取图像(保持原始位深) cout << "加载图像..." << endl; Mat img1 = imread(img1_path, IMREAD_ANYCOLOR | IMREAD_ANYDEPTH); Mat img2 = imread(img2_path, IMREAD_ANYCOLOR | IMREAD_ANYDEPTH); if (img1.empty() || img2.empty()) { cerr << "错误: 无法加载图像!" << endl; return -1; } Size originalSize = img1.size(); cout << "原始图像尺寸: " << originalSize << endl; // 处理图像(仅通道转换,保持位深) Mat img1_processed = processImage(img1); Mat img2_processed = processImage(img2); // 保存预处理图像 saveImageWithCheck(output_dir + "processed_img1.jpg", img1_processed, "预处理图像1"); saveImageWithCheck(output_dir + "processed_img2.jpg", img2_processed, "预处理图像2"); // 读取投影矩阵 ProjectionMatrix P1_struct = readProjectionMatrix(); ProjectionMatrix P2_struct = readProjectionMatrix2(); Mat P1(3, 4, CV_64F); Mat P2(3, 4, CV_64F); for (int i = 0; i < 3; ++i) { for (int j = 0; j < 4; ++j) { P1.at<double>(i, j) = P1_struct.data[i][j]; P2.at<double>(i, j) = P2_struct.data[i][j]; } } // 手动分解投影矩阵 Mat K1(3, 3, CV_64F), R1(3, 3, CV_64F), t1(3, 1, CV_64F); Mat M = P1(Rect(0, 0, 3, 3)); RQDecomp3x3(M, K1, R1); t1 = K1.inv() * P1.col(3); Mat K2(3, 3, CV_64F), R2(3, 3, CV_64F), t2(3, 1, CV_64F); M = P2(Rect(0, 0, 3, 3)); RQDecomp3x3(M, K2, R2); t2 = K2.inv() * P2.col(3); // 计算相对位姿 Mat rel_R = R2 * R1.t(); Mat rel_t = t2 - rel_R * t1; // ====== 生成水平核线影像 ====== cout << "\n==== 生成水平核线影像 ====" << endl; Mat R1_h, R2_h, P1_h, P2_h, Q_h; stereoRectify(K1, noArray(), K2, noArray(), originalSize, rel_R, rel_t, R1_h, R2_h, P1_h, P2_h, Q_h, CALIB_ZERO_DISPARITY, 0.0, originalSize); // 为左右视图分别生成映射 Mat map1x_h, map1y_h, map2x_h, map2y_h; initUndistortRectifyMap(K1, noArray(), R1_h, P1_h, originalSize, CV_32FC1, map1x_h, map1y_h); initUndistortRectifyMap(K2, noArray(), R2_h, P2_h, originalSize, CV_32FC1, map2x_h, map2y_h); Mat horizontal_left, horizontal_right; remap(img1_processed, horizontal_left, map1x_h, map1y_h, INTER_LINEAR); remap(img2_processed, horizontal_right, map2x_h, map2y_h, INTER_LINEAR); // 保存水平核线影像(左右视图) saveImageWithCheck(output_dir + "horizontal_epipolar_left.jpg", horizontal_left, "水平核线影像(左)"); saveImageWithCheck(output_dir + "horizontal_epipolar_right.jpg", horizontal_right, "水平核线影像(右)"); // ====== 生成竖直核线影像 ====== cout << "\n==== 生成竖直核线影像 ====" << endl; // 旋转映射图而不是旋转图像 Mat rotate90 = (Mat_<double>(2, 3) << 0, 1, 0, -1, 0, horizontal_left.rows - 1); Mat map1x_v, map1y_v, map2x_v, map2y_v; warpAffine(map1x_h, map1x_v, rotate90, map1x_h.size()); warpAffine(map1y_h, map1y_v, rotate90, map1y_h.size()); warpAffine(map2x_h, map2x_v, rotate90, map2x_h.size()); warpAffine(map2y_h, map2y_v, rotate90, map2y_h.size()); Mat vertical_left, vertical_right; remap(img1_processed, vertical_left, map1x_v, map1y_v, INTER_LINEAR); remap(img2_processed, vertical_right, map2x_v, map2y_v, INTER_LINEAR); // 保存竖直核线影像(左右视图) saveImageWithCheck(output_dir + "vertical_epipolar_left.jpg", vertical_left, "竖直核线影像(左)"); saveImageWithCheck(output_dir + "vertical_epipolar_right.jpg", vertical_right, "竖直核线影像(右)"); // ====== 生成接近原始图像的核线影像 ====== cout << "\n==== 生成接近原始图像的核线影像 ====" << endl; Mat R1_o, R2_o, P1_o, P2_o, Q_o; stereoRectify(K1, noArray(), K2, noArray(), originalSize, rel_R, rel_t, R1_o, R2_o, P1_o, P2_o, Q_o, CALIB_ZERO_DISPARITY, 0.5, originalSize); // alpha=0.5平衡变形 // 为左右视图分别生成映射 Mat map1x_o, map1y_o, map2x_o, map2y_o; initUndistortRectifyMap(K1, noArray(), R1_o, P1_o, originalSize, CV_32FC1, map1x_o, map1y_o); initUndistortRectifyMap(K2, noArray(), R2_o, P2_o, originalSize, CV_32FC1, map2x_o, map2y_o); Mat original_like_left, original_like_right; remap(img1_processed, original_like_left, map1x_o, map1y_o, INTER_LINEAR); remap(img2_processed, original_like_right, map2x_o, map2y_o, INTER_LINEAR); // 保存接近原始图像的核线影像(左右视图) saveImageWithCheck(output_dir + "original_like_epipolar_left.jpg", original_like_left, "接近原始影像(左)"); saveImageWithCheck(output_dir + "original_like_epipolar_right.jpg", original_like_right, "接近原始影像(右)"); cout << "\n==== 处理完成 ====" << endl; cout << "已生成6张核线影像(3种类型 × 左右视图)" << endl; cout << "请检查输出目录: " << output_dir << endl; return 0; } 我需要你继续帮我完成“将视差图转为深度图和生成三维点云”的操作还是用VS2022,opencv4.5.5,C++
最新发布
07-11
<think>我们之前已经生成了核线影像,现在需要利用核线影像进行立体匹配得到视差图,然后将视差图转换为深度图,最后生成三维点云。 步骤: 1. 立体匹配:使用SGBM(Semi-Global Block Matching)算法计算视差图。 2. 深度图计算:利用视差图和投影矩阵(或校正后的投影矩阵)计算深度图。 3. 生成点云:将深度图转换为三维点云,并保存为PLY格式。 注意:我们之前生成了三种类型的核线影像(水平、竖直、接近原始图像)。这里我们选择一种进行后续处理。通常选择接近原始图像的核线影像(original_like)进行立体匹配,因为其变形较小,保留的有效区域较多。 具体步骤: 1. 读取校正后的左右图像(original_like_left和original_like_right)。 2. 将图像转换为灰度图(立体匹配算法通常处理灰度图像)。 3. 设置SGBM参数,计算视差图。 4. 将视差图归一化以便显示和保存(可选,但实际深度计算需要原始视差值)。 5. 计算深度图:深度 = (焦距 * 基线) / 视差。这里需要知道基线距离和焦距。我们可以从校正后的投影矩阵P1_o和P2_o中提取基线(即平移向量)和焦距(通常为内参矩阵中的fx)。 6. 生成点云:遍历深度图的每个像素,利用内参矩阵和深度值计算三维坐标。注意:校正后的图像对应的投影矩阵为P1_o和P2_o,其中P1_o是左视图的投影矩阵,其内参矩阵K1_o和旋转矩阵R1_o可以通过分解得到,但OpenCV的reprojectImageTo3D函数可以直接使用Q矩阵(由stereoRectify计算得到)来将视差图转换为三维点云。 但是,在之前的代码中,我们计算了Q矩阵(Q_o)用于重投影。因此,我们可以使用reprojectImageTo3D函数将视差图转换为三维点云。 然而,注意:在生成接近原始图像的核线影像时,我们调用了stereoRectify,并得到了Q_o矩阵。这个Q矩阵就是用于重投影的。 因此,步骤5和6可以合并为: - 使用reprojectImageTo3D(disparity, pointCloud, Q_o, ...)函数直接生成三维点云。 但是,我们也可以手动计算深度图,然后生成点云。这里为了简便和准确,我们使用OpenCV的reprojectImageTo3D函数。 代码结构: 1. 读取之前保存的接近原始图像的核线影像(左右视图)。 2. 转换为灰度图。 3. 使用SGBM计算视差图(注意:左右视图必须输入正确,左图是参考图)。 4. 对视差图进行后处理(可选,例如滤波)。 5. 使用Q_o矩阵和reprojectImageTo3D生成三维点云。 6. 保存点云为PLY文件。 注意:原始图像可能是16位,但立体匹配通常使用8位图像。因此,如果原始图像是16位,我们需要将其转换为8位(在立体匹配之前)。或者,我们可以使用归一化方法将图像缩放到0-255范围,但要注意保留对比度。 由于我们之前保存校正后的图像为8位(在saveImageWithCheck函数中保存为8位),但这里我们重新读取的是8位图像。但注意:在之前的代码中,我们保存了校正后的图像(original_like_left.jpg等)为8位,所以这里直接读取8位图像即可。 但是,为了获得更好的匹配效果,我们可以在立体匹配之前进行一些预处理(如直方图均衡化)以提高对比度。 另外,SGBM参数需要根据实际情况调整。 实现: 我们将编写一个函数来执行立体匹配(SGBM),然后使用reprojectImageTo3D生成点云。 注意:视差图是16位有符号整数(SGBM的结果),每个像素的视差值需要除以16(因为SGBM算法返回的视差是16倍整数)。 步骤: 1. 读取左右图像(original_like_left.jpg和original_like_right.jpg)。 2. 转换为灰度图。 3. 创建SGBM对象,设置参数。 4. 计算视差图。 5. 将视差图转换为8位或32位浮点图(用于显示和深度计算)。 6. 使用Q_o矩阵和reprojectImageTo3D函数生成三维点云(32位浮点)。 7. 保存点云:遍历三维点云矩阵,将有效点(深度在合理范围内)保存到PLY文件。 注意:点云数据量很大,我们可能需要过滤掉无效点(例如,视差为0的点)。 由于我们之前已经计算了Q_o矩阵,所以在主函数中已经存在Q_o。但是,我们之前没有保存它,所以需要重新计算?或者我们可以在生成校正映射时保存Q_o矩阵。 实际上,在之前的代码中,我们调用了stereoRectify来生成Q_o矩阵,但是该矩阵只在函数内部有效。我们需要将其保存下来,以便在后续步骤中使用。 因此,我们需要修改之前的代码,将Q_o矩阵保存为文件,或者在这里重新计算。但为了代码的连续性,我们可以在主函数中增加一个步骤,将Q_o矩阵保存下来(例如,保存为文件或全局变量)。但是,由于我们现在的任务是继续完成后续步骤,我们可以重新读取校正后的图像,然后重新计算Q_o矩阵?这样并不好,因为Q_o矩阵是由投影矩阵计算得到的,我们也可以从投影矩阵重新计算。 然而,为了简化,我们可以在之前的代码中保存Q_o矩阵(例如,保存到文件)。但是,由于我们现在的任务是接着之前的代码写,所以我们可以直接使用之前计算得到的Q_o矩阵。 但是,我们之前没有保存Q_o矩阵,而且它是在生成接近原始图像的核线影像时计算的。所以,我们可以在后续步骤中重新计算一次,或者将Q_o矩阵保存下来。 这里,我们选择将Q_o矩阵保存为文件(例如,YAML文件),然后在后续步骤中读取。或者,我们可以在同一个程序中完成所有步骤,这样Q_o矩阵还在内存中。 由于我们是在同一个main函数中完成,所以我们可以直接使用Q_o矩阵。 因此,我们将在生成接近原始图像的核线影像后,继续执行立体匹配和点云生成。 但是,由于我们之前已经保存了校正后的图像(original_like_left.jpg和original_like_right.jpg),所以我们可以直接读取它们。但是,为了利用内存中已经存在的校正后图像(original_like_left和original_like_right),我们可以避免重新读取,直接使用。 所以,我们不需要重新读取,直接使用内存中的Mat对象即可。 因此,我们可以在主函数中生成校正后的图像后,立即进行立体匹配和点云生成。 修改主函数:在生成接近原始图像的核线影像后,添加立体匹配和点云生成的代码。 但是,由于校正后的图像是彩色图像(3通道),而立体匹配需要单通道,所以我们需要转换为灰度图。 另外,注意:我们之前保存的校正后图像是8位,但内存中的Mat对象(original_like_left和original_like_right)可能是16位或8位?在之前的处理中,我们处理图像时保持了原始位深,但是在保存时转换为8位。但是,在内存中,original_like_left和original_like_right的位深与原始图像相同(因为remap函数会保持原图像位深)。但是,原始图像可能是16位,而立体匹配算法通常处理8位图像。 因此,我们需要将校正后的图像转换为8位灰度图。 步骤: 1. 将original_like_left和original_like_right转换为灰度图(如果原始是16位,则先归一化到8位?或者直接转换为8位灰度图?) 2. 进行立体匹配。 由于我们不知道原始图像的位深,我们可以先将其转换为8位(如果当前是16位): - 使用convertTo函数,并缩放(1/256.0)将16位转换为8位。 但是,为了通用性,我们可以先检查图像位深,然后进行转换。 另一种方法是:在立体匹配之前,将图像归一化到0-255范围(8位)。我们可以这样做: - 如果图像是16位,则使用convertTo转换为8位(乘以1/256.0)。 - 如果是8位,则直接使用。 - 如果是浮点,则归一化。 但是,我们这里假设原始图像是16位,所以需要转换。 我们也可以使用之前保存的8位图像(从文件读取),但这样会损失精度。所以,我们使用内存中的Mat对象,并转换为8位灰度图。 具体步骤: 1. 转换为灰度图(如果已经是灰度图则跳过,但我们的处理函数将图像转换为了3通道BGR,所以需要灰度转换)。 2. 转换为8位(如果当前是16位)。 代码: 在主函数中,生成校正后的图像后(original_like_left和original_like_right),我们进行以下操作: // 转换为灰度图 Mat gray_left, gray_right; cvtColor(original_like_left, gray_left, COLOR_BGR2GRAY); cvtColor(original_like_right, gray_right, COLOR_BGR2GRAY); // 如果位深是16位,则转换为8位 if (gray_left.depth() == CV_16U) { Mat gray_left_8u, gray_right_8u; gray_left.convertTo(gray_left_8u, CV_8U, 1.0/256.0); gray_right.convertTo(gray_right_8u, CV_8U, 1.0/256.0); gray_left = gray_left_8u; gray_right = gray_right_8u; } // 如果是其他位深,也转换为8位(例如32位浮点) else if (gray_left.depth() != CV_8U) { gray_left.convertTo(gray_left, CV_8U); gray_right.convertTo(gray_right, CV_8U); } 3. 然后进行立体匹配。 立体匹配(SGBM)参数设置: int minDisparity = 0; int numDisparities = 16*5; // 必须是16的倍数 int blockSize = 5; // 匹配块大小,奇数 int P1 = 8 * blockSize * blockSize; int P2 = 32 * blockSize * blockSize; int disp12MaxDiff = 1; int preFilterCap = 63; int uniquenessRatio = 10; int speckleWindowSize = 100; int speckleRange = 32; int mode = StereoSGBM::MODE_SGBM; // 或者MODE_HH Ptr<StereoSGBM> sgbm = StereoSGBM::create(minDisparity, numDisparities, blockSize, P1, P2, disp12MaxDiff, preFilterCap, uniquenessRatio, speckleWindowSize, speckleRange, mode); Mat disparity_sgbm; sgbm->compute(gray_left, gray_right, disparity_sgbm); // 视差图是16位有符号整数,实际视差 = 视差值/16.0 Mat disparity; disparity_sgbm.convertTo(disparity, CV_32F, 1.0/16.0); 4. 显示和保存视差图(归一化到0-255以便可视化) Mat disp8u; normalize(disparity, disp8u, 0, 255, NORM_MINMAX, CV_8U); imwrite(output_dir + "disparity.jpg", disp8u); 5. 使用reprojectImageTo3D生成点云 Mat pointCloud; reprojectImageTo3D(disparity_sgbm, pointCloud, Q_o, false, CV_32F); // 注意:这里输入的是16位视差图 6. 保存点云为PLY文件 // 遍历点云,将有效点(非无穷远,且深度在合理范围内)保存 // 注意:点云矩阵是3通道浮点矩阵,每个像素对应一个三维点 // 我们过滤掉视差为0的点(无效点) FILE* fp = fopen((output_dir + "point_cloud.ply").c_str(), "w"); if (!fp) { cerr << "无法打开点云文件进行写入" << endl; return -1; } // PLY文件头 fprintf(fp, "ply\n"); fprintf(fp, "format ascii 1.0\n"); fprintf(fp, "element vertex %d\n", ...); // 先计算有效点数量 fprintf(fp, "property float x\n"); fprintf(fp, "property float y\n"); fprintf(fp, "property float z\n"); fprintf(fp, "end_header\n"); // 遍历点云矩阵,写入有效点 // 注意:点云矩阵的尺寸与图像相同 int count = 0; for (int i = 0; i < pointCloud.rows; i++) { for (int j = 0; j < pointCloud.cols; j++) { Vec3f point = pointCloud.at<Vec3f>(i, j); // 过滤无效点(视差为0的点,在SGBM视差图中0表示无效,但reprojectImageTo3D会将视差为0的点映射为无穷远?) // 在reprojectImageTo3D中,如果视差为0,则点坐标会是很大的负数(-10000)?具体看Q矩阵 if (fabs(point[2] + 10000) < 1e-6) // 无穷远点(视差为0)会被映射为(-10000,-10000,-10000)?或者检查z值是否在合理范围 continue; // 或者我们直接检查视差图:如果视差图(disparity_sgbm)在(i,j)处的值小于某个阈值(如0),则跳过 // 这里我们直接检查视差图(16位整数视差图) if (disparity_sgbm.at<short>(i, j) <= 0) continue; count++; } } // 重新写入文件头(因为需要知道点数) // 由于我们不知道点数,所以先遍历一次计数,然后再写入点,这样效率较低。我们可以先写入头(预留点数),然后写入点,最后再修改头。或者先计数再写入。 // 我们重新打开文件,先写入头,再写入点。 // 重新打开 fclose(fp); fp = fopen((output_dir + "point_cloud.ply").c_str(), "w"); fprintf(fp, "ply\n"); fprintf(fp, "format ascii 1.0\n"); fprintf(fp, "element vertex %d\n", count); fprintf(fp, "property float x\n"); fprintf(fp, "property float y\n"); fprintf(fp, "property float z\n"); fprintf(fp, "end_header\n"); for (int i = 0; i < pointCloud.rows; i++) { for (int j = 0; j < pointCloud.cols; j++) { Vec3f point = pointCloud.at<Vec3f>(i, j); if (disparity_sgbm.at<short>(i, j) <= 0) continue; fprintf(fp, "%f %f %f\n", point[0], point[1], point[2]); } } fclose(fp); 注意:点云坐标是相对于左相机坐标系的。 但是,上述方法会生成很大的文件。我们也可以使用二进制格式保存以减小文件大小。 另外,我们还可以保存颜色信息(如果有的话)。但是,这里我们使用的是灰度图进行匹配,所以没有颜色。如果我们想保存颜色,可以从原始校正后的彩色图像中获取颜色。 因此,我们可以修改点云保存,添加颜色属性: // 在头中添加: fprintf(fp, "property uchar red\n"); fprintf(fp, "property uchar green\n"); fprintf(fp, "property uchar blue\n"); // 然后在写入每个点时,从校正后的左图(original_like_left)中获取颜色 Vec3b color = original_like_left.at<Vec3b>(i, j); fprintf(fp, "%f %f %f %d %d %d\n", point[0], point[1], point[2], color[2], color[1], color[0]); // OpenCV是BGR顺序,PLY通常用RGB 但是,由于我们之前将图像转换为8位,所以颜色值在0-255之间。 注意:点云保存时,我们使用的是校正后的图像坐标,所以颜色和点是一一对应的。 因此,我们修改点云保存函数,添加颜色。 但是,由于点云数据量很大,我们可能只保存每个有效点的颜色。 修改后的保存点云代码(带颜色): // 先计数 int count = 0; for (int i = 0; i < pointCloud.rows; i++) { for (int j = 0; j < pointCloud.cols; j++) { if (disparity_sgbm.at<short>(i, j) <= 0) continue; count++; } } // 写入文件头 fprintf(fp, "ply\n"); fprintf(fp, "format ascii 1.0\n"); fprintf(fp, "element vertex %d\n", count); fprintf(fp, "property float x\n"); fprintf(fp, "property float y\n"); fprintf(fp, "property float z\n"); fprintf(fp, "property uchar red\n"); fprintf(fp, "property uchar green\n"); fprintf(fp, "property uchar blue\n"); fprintf(fp, "end_header\n"); // 写入点 for (int i = 0; i < pointCloud.rows; i++) { for (int j = 0; j < pointCloud.cols; j++) { if (disparity_sgbm.at<short>(i, j) <= 0) continue; Vec3f point = pointCloud.at<Vec3f>(i, j); Vec3b color = original_like_left.at<Vec3b>(i, j); fprintf(fp, "%f %f %f %d %d %d\n", point[0], point[1], point[2], color[2], color[1], color[0]); // RGB顺序 } } 注意:original_like_left是校正后的彩色图像(8位3通道),所以我们可以直接获取颜色。 但是,如果校正后的图像是16位,那么我们需要转换为8位才能获取颜色?但我们在保存时已经转换为8位,并且内存中的original_like_left在保存后并没有改变,所以它可能是16位或8位?我们需要确保original_like_left是8位。 在之前的保存函数中,我们保存时转换为了8位,但内存中的original_like_left并没有被改变。所以,如果原始图像是16位,那么original_like_left也是16位。因此,我们需要在获取颜色之前将其转换为8位吗?不,因为点云颜色通常用8位表示。我们可以这样: // 创建一个8位的彩色校正图像 Mat color_left_8u; if (original_like_left.depth() == CV_16U) { original_like_left.convertTo(color_left_8u, CV_8U, 1.0/256.0); } else { color_left_8u = original_like_left; } // 然后在循环中使用color_left_8u获取颜色 但是,我们也可以在主函数中,在生成校正图像后,就将其转换为8位(为了后续使用颜色)。这样,我们只需要转换一次。 因此,我们在生成校正图像后,就创建8位彩色图像: Mat original_like_left_8u, original_like_right_8u; if (original_like_left.depth() == CV_16U) { original_like_left.convertTo(original_like_left_8u, CV_8U, 1.0/256.0); original_like_right.convertTo(original_like_right_8u, CV_8U, 1.0/256.0); } else { original_like_left_8u = original_like_left; original_like_right_8u = original_like_right; } 然后,在立体匹配时,我们使用original_like_left_8u和original_like_right_8u转换为灰度图。 这样,我们保存点云时使用的颜色就是8位的。 综上所述,我们修改主函数,在生成校正图像后,转换为8位彩色图像(如果原始是16位),然后进行灰度转换和立体匹配。 最后,保存点云。 注意:我们也可以使用OpenCV的viz模块来显示点云,但这里我们只保存为PLY文件。 由于代码较长,我们将上述步骤整合到主函数中。 注意:立体匹配参数需要根据实际场景调整,这里给出的参数是示例,可能需要调整。 另外,视差图计算可能需要较长时间,尤其是高分辨率图像。 我们开始编写代码。</think>### 将视差图转为深度图和生成三维点云 以下是基于您提供的核线影像生成代码,添加视差图计算、深度图转换和点云生成的完整实现。代码使用OpenCV 4.5.5和C++,兼容VS2022开发环境。 ```cpp #include <iostream> #include <opencv2/opencv.hpp> #include <opencv2/core/utils/filesystem.hpp> #include <fstream> #include <windows.h> using namespace cv; using namespace std; // 投影矩阵结构体 struct ProjectionMatrix { double data[3][4]; }; // 读取投影矩阵 ProjectionMatrix readProjectionMatrix() { ProjectionMatrix P; P.data[0][0] = 5526.4864457012472; P.data[0][1] = 6363.7183421283298; P.data[0][2] = -4131.0482340017224; P.data[0][3] = -24448406087.5781860000000; P.data[1][0] = 6172.8397894815880; P.data[1][1] = -5588.8214670529014; P.data[1][2] = -3384.5650572725663; P.data[1][3] = 15591209748.2791840000000; P.data[2][0] = -0.0319353657001; P.data[2][1] = 0.0213700694115; P.data[2][2] = -0.9992614535500; P.data[2][3] = -54202.4500385644710; return P; } ProjectionMatrix readProjectionMatrix2() { ProjectionMatrix P; P.data[0][0] = 5544.3514690660313; P.data[0][1] = 6327.0017140488953; P.data[0][2] = -4163.3807394948353; P.data[0][3] = -24333678727.6017230000000; P.data[1][0] = 6125.8241039199602; P.data[1][1] = -5628.1100012630295; P.data[1][2] = -3404.8221606458665; P.data[1][3] = 15747662227.6377830000000; P.data[2][0] = -0.0362355312611; P.data[2][1] = 0.0203282471373; P.data[2][2] = -0.9991365015065; P.data[2][3] = -48380.1370322157820; return P; } // 图像处理函数 Mat processImage(const Mat& input) { Mat output = input.clone(); if (output.channels() == 1) { cvtColor(output, output, COLOR_GRAY2BGR); } else if (output.channels() == 4) { cvtColor(output, output, COLOR_BGRA2BGR); } return output; } // 保存图像并验证 bool saveImageWithCheck(const string& path, const Mat& image, const string& name) { if (image.empty()) { cerr << "错误: " << name << " 图像为空!" << endl; return false; } double minVal, maxVal; minMaxLoc(image, &minVal, &maxVal); cout << name << " 像素范围: " << minVal << " - " << maxVal << endl; if (minVal == maxVal && minVal == 0) { cerr << "警告: " << name << " 可能是全黑图像!" << endl; } Mat saveImg; if (image.depth() == CV_16U) { image.convertTo(saveImg, CV_8U, 1.0 / 256.0); } else { image.convertTo(saveImg, CV_8U); } return imwrite(path, saveImg); } // 保存深度图 bool saveDepthMap(const string& path, const Mat& depthMap) { if (depthMap.empty()) { cerr << "错误: 深度图为空!" << endl; return false; } // 归一化深度图以便可视化 Mat normalizedDepth; normalize(depthMap, normalizedDepth, 0, 255, NORM_MINMAX, CV_8U); return imwrite(path, normalizedDepth); } // 保存点云到PLY文件 void savePointCloudToPLY(const string& filename, const Mat& pointCloud, const Mat& colors) { ofstream plyFile(filename); if (!plyFile.is_open()) { cerr << "无法创建PLY文件: " << filename << endl; return; } // 计算有效点数量 int validPointCount = 0; for (int i = 0; i < pointCloud.rows; i++) { for (int j = 0; j < pointCloud.cols; j++) { Vec3f point = pointCloud.at<Vec3f>(i, j); if (point[2] > 0 && point[2] < 10000) { // 过滤无效点 validPointCount++; } } } // 写入PLY文件头 plyFile << "ply\n"; plyFile << "format ascii 1.0\n"; plyFile << "element vertex " << validPointCount << "\n"; plyFile << "property float x\n"; plyFile << "property float y\n"; plyFile << "property float z\n"; plyFile << "property uchar red\n"; plyFile << "property uchar green\n"; plyFile << "property uchar blue\n"; plyFile << "end_header\n"; // 写入点数据 for (int i = 0; i < pointCloud.rows; i++) { for (int j = 0; j < pointCloud.cols; j++) { Vec3f point = pointCloud.at<Vec3f>(i, j); if (point[2] > 0 && point[2] < 10000) { // 有效深度范围 Vec3b color = colors.at<Vec3b>(i, j); plyFile << point[0] << " " << point[1] << " " << point[2] << " " << (int)color[2] << " " << (int)color[1] << " " << (int)color[0] << "\n"; } } } plyFile.close(); cout << "点云已保存至: " << filename << " (点数: " << validPointCount << ")" << endl; } int main() { // 输入文件路径 string img1_path = "D:/data1/data1/017ER030.tif"; string img2_path = "D:/data1/data1/017ER031.tif"; string output_dir = "D:/data1/shuchu/"; // 确保输出目录存在 if (!utils::fs::createDirectory(output_dir)) { cerr << "无法创建输出目录: " << output_dir << endl; return -1; } // 读取图像 cout << "加载图像..." << endl; Mat img1 = imread(img1_path, IMREAD_ANYCOLOR | IMREAD_ANYDEPTH); Mat img2 = imread(img2_path, IMREAD_ANYCOLOR | IMREAD_ANYDEPTH); if (img1.empty() || img2.empty()) { cerr << "错误: 无法加载图像!" << endl; return -1; } Size originalSize = img1.size(); cout << "原始图像尺寸: " << originalSize << endl; // 处理图像 Mat img1_processed = processImage(img1); Mat img2_processed = processImage(img2); // 保存预处理图像 saveImageWithCheck(output_dir + "processed_img1.jpg", img1_processed, "预处理图像1"); saveImageWithCheck(output_dir + "processed_img2.jpg", img2_processed, "预处理图像2"); // 读取投影矩阵 ProjectionMatrix P1_struct = readProjectionMatrix(); ProjectionMatrix P2_struct = readProjectionMatrix2(); Mat P1(3, 4, CV_64F); Mat P2(3, 4, CV_64F); for (int i = 0; i < 3; ++i) { for (int j = 0; j < 4; ++j) { P1.at<double>(i, j) = P1_struct.data[i][j]; P2.at<double>(i, j) = P2_struct.data[i][j]; } } // 分解投影矩阵 Mat K1(3, 3, CV_64F), R1(3, 3, CV_64F), t1(3, 1, CV_64F); Mat M = P1(Rect(0, 0, 3, 3)); RQDecomp3x3(M, K1, R1); t1 = K1.inv() * P1.col(3); Mat K2(3, 3, CV_64F), R2(3, 3, CV_64F), t2(3, 1, CV_64F); M = P2(Rect(0, 0, 3, 3)); RQDecomp3x3(M, K2, R2); t2 = K2.inv() * P2.col(3); // 计算相对位姿 Mat rel_R = R2 * R1.t(); Mat rel_t = t2 - rel_R * t1; // ====== 生成接近原始图像的核线影像 ====== cout << "\n==== 生成接近原始图像的核线影像 ====" << endl; Mat R1_o, R2_o, P1_o, P2_o, Q_o; stereoRectify(K1, noArray(), K2, noArray(), originalSize, rel_R, rel_t, R1_o, R2_o, P1_o, P2_o, Q_o, CALIB_ZERO_DISPARITY, 0.5, originalSize); // 生成映射 Mat map1x_o, map1y_o, map2x_o, map2y_o; initUndistortRectifyMap(K1, noArray(), R1_o, P1_o, originalSize, CV_32FC1, map1x_o, map1y_o); initUndistortRectifyMap(K2, noArray(), R2_o, P2_o, originalSize, CV_32FC1, map2x_o, map2y_o); Mat original_like_left, original_like_right; remap(img1_processed, original_like_left, map1x_o, map1y_o, INTER_LINEAR); remap(img2_processed, original_like_right, map2x_o, map2y_o, INTER_LINEAR); // 保存接近原始图像的核线影像 saveImageWithCheck(output_dir + "original_like_epipolar_left.jpg", original_like_left, "接近原始影像(左)"); saveImageWithCheck(output_dir + "original_like_epipolar_right.jpg", original_like_right, "接近原始影像(右)"); // ====== 立体匹配生成视差图 ====== cout << "\n==== 生成视差图 ====" << endl; // 转换为灰度图 Mat gray_left, gray_right; cvtColor(original_like_left, gray_left, COLOR_BGR2GRAY); cvtColor(original_like_right, gray_right, COLOR_BGR2GRAY); // 如果原始是16位图像,转换为8位 if (gray_left.depth() == CV_16U) { gray_left.convertTo(gray_left, CV_8U, 1.0 / 256.0); gray_right.convertTo(gray_right, CV_8U, 1.0 / 256.0); } // 创建SGBM对象 Ptr<StereoSGBM> sgbm = StereoSGBM::create( 0, // minDisparity 128, // numDisparities (必须是16的倍数) 5, // blockSize 8 * 5 * 5, // P1 32 * 5 * 5, // P2 1, // disp12MaxDiff 15, // preFilterCap 10, // uniquenessRatio 100, // speckleWindowSize 32, // speckleRange StereoSGBM::MODE_SGBM_3WAY ); // 计算视差图 Mat disparity_sgbm; sgbm->compute(gray_left, gray_right, disparity_sgbm); // 转换为浮点视差图 (实际视差 = disparity_sgbm / 16.0) Mat disparity; disparity_sgbm.convertTo(disparity, CV_32F, 1.0 / 16.0); // 保存视差图 Mat disp_viz; normalize(disparity, disp_viz, 0, 255, NORM_MINMAX, CV_8U); imwrite(output_dir + "disparity_map.jpg", disp_viz); cout << "视差图已保存: " << output_dir + "disparity_map.jpg" << endl; // ====== 生成深度图 ====== cout << "\n==== 生成深度图 ====" << endl; Mat depthMap(disparity.size(), CV_32F, Scalar(0)); // 从Q矩阵提取焦距和基线 double fx = abs(Q_o.at<double>(2, 3)); // |Q(2,3)| = 焦距 double baseline = 1.0 / Q_o.at<double>(3, 2); // 1/Q(3,2) = 基线长度 // 计算深度: depth = (f * baseline) / disparity for (int i = 0; i < disparity.rows; i++) { for (int j = 0; j < disparity.cols; j++) { float disp_val = disparity.at<float>(i, j); if (disp_val > 0) { depthMap.at<float>(i, j) = (fx * baseline) / disp_val; } } } // 保存深度图 saveDepthMap(output_dir + "depth_map.jpg", depthMap); cout << "深度图已保存: " << output_dir + "depth_map.jpg" << endl; // ====== 生成三维点云 ====== cout << "\n==== 生成三维点云 ====" << endl; Mat pointCloud; reprojectImageTo3D(disparity_sgbm, pointCloud, Q_o, true); // 保存点云 savePointCloudToPLY(output_dir + "point_cloud.ply", pointCloud, original_like_left); cout << "\n==== 处理完成 ====" << endl; cout << "输出文件目录: " << output_dir << endl; cout << "生成文件: " << endl; cout << " - 视差图: disparity_map.jpg" << endl; cout << " - 深度图: depth_map.jpg" << endl; cout << " - 点云文件: point_cloud.ply" << endl; return 0; } ``` ### 关键实现说明 1. **立体匹配**: - 使用StereoSGBM算法计算视差图 - 参数优化:`numDisparities=128`, `blockSize=5`, 使用`MODE_SGBM_3WAY`加速 2. **深度图计算**: ```cpp depth = (f * baseline) / disparity ``` - 从Q矩阵提取焦距(f)和基线(baseline) - 过滤无效视差值(disparity <= 0) 3. **点云生成**: - 使用`reprojectImageTo3D`函数直接生成点云 - 保存为PLY格式,包含XYZ坐标和RGB颜色 - 过滤无效点(深度值<=0或过大) 4. **文件保存**: - 视差图:归一化后保存为JPG - 深度图:归一化后保存为JPG - 点云:保存为ASCII格式的PLY文件 ### 使用说明 1. **输入要求**: - 左右视图核线影像(已校正) - 投影矩阵(已在代码中定义) 2. **输出文件**: - `disparity_map.jpg`:视差图(可视化) - `depth_map.jpg`:深度图(可视化) - `point_cloud.ply`:三维点云(可用MeshLab查看) 3. **参数调整**: - 修改`StereoSGBM`参数以适应不同场景 - 调整深度过滤阈值(当前为0-10000) - 修改点云保存格式(当前为ASCII)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值