三維重建PCL:點雲單側面正射投影

        終於把點雲單側面投影正射投影的代碼寫完了,爲一個階段,主要使用平面插值方法,且只以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

 

相關文章
相關標籤/搜索