終於把點雲單側面投影正射投影的代碼寫完了,爲一個階段,主要使用平面插值方法,且只以XOY平面做爲的正射投影面。有些湊合的地方,待改進。函數
方法思路:使用Mesh模型,對每個表面進行表面重建。藉助OpenCV Mat類型對投影平面進行內點判斷,對內點位置進行插值。
測試
OpenCV cv::polylines 和lines 進行畫圖的時候都會出現問題,所以在某些時刻沒法使用連通域查找的方法進行內點檢測,應該重寫line方法。
this
1.使用Mesh載入ply模型,和同步載入點雲,也能夠從mesh直接Copy點雲。
spa
pcl::PolygonMesh cloudMesh; pcl::io::loadPolygonFileOBJ(ViewPath, cloudMesh); pcl::fromPCLPointCloud2(cloudMesh.cloud, *cloud); ViewPath = "D:/DataSet/RGB_data/teapot.pcd"; pcl::io::savePCDFileASCII(ViewPath, *cloud);//必定要注意高和寬進行賦值 pcl::visualization::PCLVisualizer Viewer;//pcl::visualization::PCLVisualizer ViewerMesh; Viewer.addPolygonMesh(cloudMesh); int FrameX = 1000; int FrameY = 1000; int FrameZ = 1000; int Centroid = 0; int num = 12; float gap = 3.141592653/num; Eigen::Vector4f ViewPoint( 0.0, 0.0, 0.0, 1);//使用弧度 ViewPoint[0] = gap*i; cv::Mat imgGray = viewEx->getCloudViewByEdge( cloud, cloudView, cloudMesh, ViewPath, FrameX, FrameY, FrameZ, Centroid, ViewPoint);
2. 使用平面填充方法進行投影...
調試
//使用多邊形填充的方法進行投影 //獲取點雲側面投影 //輸入:點雲的點集、邊集 cv::Mat CViewExtract::getCloudViewByEdge( pcl::PointCloud<pcl::PointXYZ>::Ptr cloud, pcl::PointCloud<pcl::PointXYZ>::Ptr cloudView, pcl::PolygonMesh &cloudMesh, std::string ViewPath, int FrameX, int FrameY, int FrameZ, int Centroid, Eigen::Vector4f &ViewPoint) { int BbxSize = FrameX; pcl::PointCloud<pcl::PointXYZ>::Ptr cloudTrans(new pcl::PointCloud<pcl::PointXYZ>); this->viewTrans(cloud, cloudTrans, ViewPoint); //對點雲進行側面投影 std::vector<pcl::PointCloud<pcl::PointXYZ>> surfaces; pcl::PointCloud<pcl::PointXYZ>::Ptr surface(new pcl::PointCloud<pcl::PointXYZ>); //計算平面 genSurfaceFromVertices(cloudMesh.polygons, cloudTrans, surface);//由cloud替換cloudtrans,mesh只是一個索引 cv::Mat imgGray = getViewer(surface, cloudTrans, cloudView); return imgGray; }
3. 子函數blog
視點變換索引
float CViewExtract::viewTrans( pcl::PointCloud<pcl::PointXYZ>::Ptr cloudSrc, pcl::PointCloud<pcl::PointXYZ>::Ptr cloudTrans, Eigen::Vector4f &AngleTrans) { //1. Trans the VIew... float AngleA, AngleB, AngleC;//聲明視角//初始化 做爲原始角度 AngleA = AngleTrans[0];//49.0/pi; AngleB = AngleTrans[1];//78.9/pi; AngleC = AngleTrans[2];//34.8/pi; int size = cloudSrc->points.size(); cloudTrans->resize(0); cloudTrans->reserve(size); //位姿識別角度變換矩陣/ Eigen::Matrix4f TransX, TransY, TransZ; //初始化三個矩陣!變換! TransX << 1, 0, 0, 0, 0, cos(AngleA), -sin(AngleA), 0, 0, sin(AngleA), cos(AngleA), 0, 0, 0, 0, 1;// TransY << cos(AngleB), 0, sin(AngleB), 0, 0, 1, 0, 0, -sin(AngleB), 0, cos(AngleB), 0, 0, 0, 0, 1; TransZ << cos(AngleC), -sin(AngleC), 0, 0, sin(AngleC), cos(AngleC), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1; //點雲模型角度變換 Eigen::Vector4f Centroid; Centroid << 0, 0, 0, 0; //pcl::compute3DCentroid(*cloudSrc, Centroid); for (int idx = 0; idx < cloudSrc->points.size(); ++idx){ Eigen::Vector4f PointSrc, PointDest;//維數一致! PointSrc[0] = cloudSrc->points[idx].x - Centroid[0]; PointSrc[1] = cloudSrc->points[idx].y - Centroid[1]; PointSrc[2] = cloudSrc->points[idx].z - Centroid[2]; //PointSrc[3] = 1; PointDest = (TransX*(TransY*(TransZ*PointSrc)));//建立矩陣無效! //cloudSrc->points[idx].x = PointDest[0] + Centroid[0]; //cloudSrc->points[idx].y = PointDest[1] + Centroid[1]; //cloudSrc->points[idx].z = PointDest[2] + Centroid[2]; //cloudSrc->points[idx].rgb = cloudSrc->points[idx].rgb; pcl::PointXYZ p; p.x = PointDest[0] + Centroid[0]; p.y = PointDest[1] + Centroid[1]; p.z = PointDest[2] + Centroid[2]; //p.x *= 5; p.y *= 5; p.z *= 5; cloudTrans->push_back(p); //cloudTrans->points[idx].rgb = cloudSrc->points[idx].rgb; } return 1.0; }
重建表面圖片
//仍然產生整數的空隙,應該把原始點雲擴充到四個整數鄰域//前N個爲原始點雲 int CViewExtract::genSurfaceFromVertices(std::vector< ::pcl::Vertices> &vertices, pcl::PointCloud<pcl::PointXYZ>::Ptr cloud, pcl::PointCloud<pcl::PointXYZ>::Ptr surfaces) { int size = vertices.size(); for ( int i = 0; i < size; ++i ){ pcl::PointCloud<pcl::PointXYZ>::Ptr surface(new pcl::PointCloud<pcl::PointXYZ>);//(&surfaces[i]);// genSurfaceFromVertices( vertices[i], cloud, surface, i); for ( auto p : surface->points){ surfaces->points.push_back(p); } surface->clear(); } return size; }
//從表面獲取點雲,對單個面獲取點雲 int CViewExtract::genSurfaceFromVertices(const pcl::Vertices &vertice, const pcl::PointCloud<pcl::PointXYZ>::Ptr cloud, pcl::PointCloud<pcl::PointXYZ>::Ptr surface, int idx) { int size = 0; int nV = vertice.vertices.size(); pcl::PointCloud<pcl::PointXYZ>::Ptr Votex(new pcl::PointCloud<pcl::PointXYZ>); for (int i = 0; i < nV; ++i){ pcl::PointXYZ p(cloud->points[vertice.vertices[i]]); Votex->points.push_back(p); } int bx, by, bz; std::vector<std::pair<float, float> > minmax(3); //findMinMax( cloud, minmax ); MathCal::findMinMax(Votex, minmax); bx = ceil(minmax[0].second - minmax[0].first); by = ceil(minmax[1].second - minmax[1].first); bz = ceil(minmax[2].second - minmax[2].first); //bx *= 10; by *= 10; bz *= 10; //取平面上的點//以Z軸爲正射方向 std::vector<cv::Point2f> vetexs(0);//生成頂點 int xmin = minmax[0].first; int ymin = minmax[1].first; int zmin = minmax[2].first; for (int i = 0; i < vertice.vertices.size(); ++i){ int idx = vertice.vertices[i]; pcl::PointXYZ p = cloud->points[idx]; cv::Point2f p2 = cv::Point2f(p.x - xmin, p.y - ymin); //p2.x *= 10;p2.y *= 10; vetexs.push_back(p2); } //生成圖像//使用OpenCV畫出對應二維圖片 cv::Mat img = cv::Mat::zeros(by + 1, bx + 1, CV_8UC3); cv::Mat _lableImg; std::vector<std::vector<cv::Point > > foreAreas; //wishchin::findInliners2d( img, vetexs, _lableImg, foreAreas ); MathCal::findInliners2dNoCon(img, vetexs, _lableImg, foreAreas); float zmean = 0; if (foreAreas.size()>0) { size = foreAreas[0].size(); //獲取平面方程//Ax + By + Cz + D //std::vector<float> getPlaneParam(std::vector<cv::Point2f> vetexs); std::vector<pcl::PointXYZ> VotexP; for (int i = 0; i < vetexs.size(); ++i){ pcl::PointXYZ p(vetexs[i].x, vetexs[i].y, (cloud->points[vertice.vertices[i]].z - zmin)); VotexP.push_back(p); zmean += cloud->points[vertice.vertices[i]].z; } zmean /= vetexs.size(); std::vector<float> abcd = MathCal::getPlaneParam(VotexP); //從平面上取點 surface->points.resize(0); float x, y, z;//Mat xy的方向與 PCL是相反的!!! for (int i = 0; i < size; ++i){ x = foreAreas[0][i].x; y = foreAreas[0][i].y; //x = bx + 0 - x; //y = by + 0 - y; z = 0-(abcd[0] * x + abcd[1] * y + abcd[3]) / abcd[2]; pcl::PointXYZ p(x,y, z); p.x += xmin; p.y += ymin; p.z += zmin;//移到原位 surface->points.push_back(p); } surface->height = 1; surface->width = size; } return size; }尋找多邊形的內點
//尋找多邊形的內點//取整數點//只能取凸多邊形 //經過判斷各個邊的左邊右邊來進行計算//經過計算在多邊形的內側外側計算-有點慢 //不使用連通域查找// int MathCal::findInliners2dNoCon(cv::Mat &img, std::vector<cv::Point2f> &vetexs, cv::Mat &_lableImg, std::vector<std::vector<cv::Point > > &foreAreas) { int size = 0; //獲取多邊形邊集 std::vector<std::vector<cv::Point2f>> edges(0); if (vetexs.size() > 2) { std::vector<cv::Point2f> edge(0); edge.push_back(cv::Point2f(vetexs[vetexs.size() - 1])); edge.push_back(cv::Point2f(vetexs[0])); edges.push_back(edge); for (int i = 1; i < vetexs.size(); ++i) { edge.resize(0); edge.push_back(cv::Point2f(vetexs[i - 1])); edge.push_back(cv::Point2f(vetexs[i])); edges.push_back(edge); } } //測試 //bool isIn =isInliner(cv::Point2f(2, 538), vetexs, edges);//true //bool isIn = isInliner(cv::Point2f(476, 258), vetexs, edges);//false //bool isIn = isInliner(cv::Point2f(704, 137), vetexs, edges); //bool isIn = isInliner(cv::Point2f(6, 11), vetexs, edges); //取多邊形的質心 //從質心開始查找連通域//須要提早染色 std::vector<cv::Point2d> inliners; cv::Point2d seed(-1, -1); bool findseed = false; std::vector<cv::Point > foreArea; for (int i = 0; i < img.rows; ++i) { unsigned char *ptrm = img.ptr<unsigned char>(i); for (int j = 0; j < img.cols; ++j) { int c = *ptrm; bool isIn = false; isIn = isInliner(cv::Point2f(j, i), vetexs, edges); //!!!!!出現錯誤!待調試!//出現了兩個方向都奇異的直角三角形 if (isIn){ seed.x = j; seed.y = i; foreArea.push_back(seed); } ++ptrm; } } if (foreArea.size()>0){ foreAreas.push_back(foreArea); } size = foreAreas.size(); return size; }
獲取平面方程ip
//獲取平面方程//Ax + By + Cz + D std::vector<float> MathCal::getPlaneParam(const std::vector<pcl::PointXYZ> &votexs) { std::vector<float> abcd; if (votexs.size() < 3){ return abcd; } else {//取前三個點計算平面 float x1, x2, x3, y1, y2, y3, z1, z2, z3; x1 = votexs[0].x; x2 = votexs[1].x; x3 = votexs[2].x; y1 = votexs[0].y; y2 = votexs[1].y; y3 = votexs[2].y; z1 = votexs[0].z; z2 = votexs[1].z; z3 = votexs[2].z; float A = y1*(z2 - z3) + y2*(z3 - z1) + y3*(z1 - z2); float B = z1*(x2 - x3) + z2*(x3 - x1) + z3*(x1 - x2); float C = x1*(y2 - y3) + x2*(y3 - y1) + x3*(y1 - y2); float D = -(x1*(y2*z3 - y3*z2) + x2*(y3*z1 - y1*z3) + x3*(y1*z2 - y2*z1)); abcd.push_back(A); abcd.push_back(B); abcd.push_back(C); abcd.push_back(D); } return abcd; }
int MathCal::findMinMax(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud, std::vector<std::pair<float, float> > &minmax) { float minX = 10000000; float minY = 10000000; float minZ = 10000000; float maxX = -10000000; float maxY = -10000000; float maxZ = -10000000; for (int i = 0; i < cloud->points.size(); ++i) { pcl::PointXYZ p(cloud->points[i]); if (minX >p.x) minX = p.x; if (minY > p.y) minY = p.y; if (minZ > p.z) minZ = p.z; if (maxX < p.x) maxX = p.x; if (maxY < p.y) maxY = p.y; if (maxZ < p.z) maxZ = p.z; } minmax.resize(0); minmax.push_back(std::pair<float, float>(minX, maxX)); minmax.push_back(std::pair<float, float>(minY, maxY)); minmax.push_back(std::pair<float, float>(minZ, maxZ)); return 1; }
//獲取點雲,直接從上一步獲取 cv::Mat CViewExtract::getViewer(const pcl::PointCloud<pcl::PointXYZ>::Ptr surface, const pcl::PointCloud<pcl::PointXYZ>::Ptr cloud, pcl::PointCloud<pcl::PointXYZ>::Ptr cloudView) { //獲取大包圍盒 int bx, by, bz; std::vector<std::pair<float, float> > minmax(3); MathCal::findMinMax(surface, minmax); float xmin = minmax[0].first; float ymin = minmax[1].first; float zmin = minmax[2].first; bx = ceil(minmax[0].second - minmax[0].first); by = ceil(minmax[1].second - minmax[1].first); bz = ceil(minmax[2].second - minmax[2].first); std::vector<float> bbx; bbx.push_back(bx); bbx.push_back(by); bbx.push_back(bz); //std::vector<bool > visibies(surface->points.size() );//直接從新生成點,不取浮點數 //生成圖像//使用OpenCV畫出對應灰度圖片 cv::Mat img = cv::Mat::zeros(by + 1, bx + 1, CV_32FC1); //for ( pcl::PointXYZ p: surface->points ) for (int i = 0; i < surface->points.size(); ++i) { pcl::PointXYZ p = surface->points[i]; int x = p.x - xmin; int y = p.y - ymin; float z = p.z - zmin + 1; //取最大Z//必須使用四鄰域 int x1 = floor(x); int x2 = ceil(x); //if (x1 < 0) x1 = 0; int y1 = floor(y); int y2 = ceil(y); //if (y1 < 0) y1 = 0; MathCal::cutValue(x1, 0, img.cols - 1); MathCal::cutValue(x2, 0, img.cols - 1); MathCal::cutValue(y1, 0, img.rows - 1); MathCal::cutValue(y2, 0, img.rows - 1); if ( img.at<float>(y1, x1) < z) img.at<float>(y1, x1) = z; if ( img.at<float>(y1, x2) < z) img.at<float>(y1, x2) = z; if ( img.at<float>(y2, x2) < z) img.at<float>(y2, x2) = z; if ( img.at<float>(y2, x1) < z) img.at<float>(y2, x1) = z; } //補加原始點雲的四鄰域//原始點雲已添加,再也不重複補償,原始點雲已刪除 pcl::PointCloud<pcl::PointXYZ>::Ptr cloudFourNear(new pcl::PointCloud<pcl::PointXYZ>); for (int i = 0; i < cloud->points.size(); ++i) { pcl::PointXYZ p = cloud->points[i]; float x = p.x - xmin; float y = p.y - ymin; float z = p.z - zmin + 1; int x1 = floor(x); int x2 = ceil(x); //if (x1<0) x1 = 0; if (x2<0) x2 = 0; int y1 = floor(y); int y2 = ceil(y); //if (y1<0) y1 = 0; if (y2<0) y2 = 0; MathCal::cutValue(x1, 0, img.cols - 1); MathCal::cutValue(x2, 0, img.cols - 1); MathCal::cutValue(y1, 0, img.rows - 1); MathCal::cutValue(y2, 0, img.rows - 1); //重複填充四鄰域 //若未被填充,則填充 if ( 0.0001> img.at<float>(y1, x1) ) img.at<float>(y1, x1) = z; if (0.0001> img.at<float>(y1, x2)) img.at<float>(y1, x2) = z; if (0.0001> img.at<float>(y2, x2)) img.at<float>(y2, x2) = z; if (0.0001> img.at<float>(y2, x1)) img.at<float>(y2, x1) = z; } cloudView->resize(0); cv::Mat imgGray = cv::Mat::zeros(by + 1, bx + 1, CV_8UC1); float x, y, z; for (int i = 0; i < img.rows; ++i) { float *ptr = img.ptr<float>(i); unsigned char *ptrg = imgGray.ptr<unsigned char>(i); for (int j = 0; j < img.cols; ++j) { if (*ptr > 0) { x = j - xmin; y = i - ymin; z = *ptr - zmin-1; cloudView->points.push_back(pcl::PointXYZ(x, y, z)); if (z < 0) z = 0; if (z >255) z = 255; *ptrg = (unsigned char)z; } ++ptr; ++ptrg; } } cloudView->height = 1; cloudView->width = cloudView->points.size(); //cv::flip(imgGray, imgGray, 2); //cv::imshow("imgGray", imgGray); //cv::waitKey(0); return imgGray; }
void MathCal::cutValue(int &inv, const int start, const int end) { if (inv < start) inv = start; if (inv > end) inv = end; //return inv; }
經過傳入viewpoint輸出不一樣的位姿可見面get