如何在CUDA设备上运行thrust::count_if?RANSAC实现报错求助
解决RANSAC GPU内点统计的Thrust嵌套调用问题
首先得明确你遇到的核心问题:你不能在Thrust的设备端函数(比如thrust::transform的lambda)里调用另一个Thrust算法(比如thrust::count_if)。这是因为Thrust的算法本质是启动CUDA内核,而CUDA不允许在一个核函数的线程里直接启动另一个核函数(除非用动态并行,Thrust默认不支持,且这个场景完全没必要)。
针对你的需求——统计500个平面对60k点的内点数量并选出最多的那个,下面给出两种可行的GPU实现方案:
方案1:Thrust批量计算+归约统计(适合快速实现)
这个思路是把所有平面-点对的内点判断展开,再用归约操作统计每个平面的内点总数,充分利用GPU的并行性:
步骤说明
- 生成平面ID数组:每个平面ID重复60k次(总长度
60000*500=3e7),对应每个点和每个平面的组合; - 生成内点标记数组:遍历所有平面-点对,计算点到平面的距离,标记是否为内点;
- 归约统计:用
reduce_by_key把相同平面ID的内点标记求和,得到每个平面的内点数量; - 找出最大值:用
max_element找到内点最多的平面。
代码示例
#include <thrust/device_vector.h> #include <thrust/repeat.h> #include <thrust/transform.h> #include <thrust/reduce.h> #include <thrust/iterator/counting_iterator.h> #include <thrust/tuple.h> // 定义点和平面的数据结构 struct Point { float x, y, z; }; struct Plane { float a, b, c, d; }; // 计算点到平面的距离并判断是否为内点 struct IsInlier { thrust::device_vector<Point> d_points; thrust::device_vector<Plane> d_planes; float threshold; // 预先计算平面的模长,避免重复开平方 thrust::device_vector<float> d_plane_norms; IsInlier(thrust::device_vector<Point>& pts, thrust::device_vector<Plane>& planes, float thresh) : d_points(pts), d_planes(planes), threshold(thresh) { // 提前计算每个平面的模长 d_plane_norms.resize(planes.size()); thrust::transform(planes.begin(), planes.end(), d_plane_norms.begin(), [] __host__ __device__(const Plane& p) { return sqrt(p.a*p.a + p.b*p.b + p.c*p.c); }); } __host__ __device__ bool operator()(int idx) { int plane_id = idx / d_points.size(); int point_id = idx % d_points.size(); const Plane& pl = d_planes[plane_id]; const Point& pt = d_points[point_id]; float norm = d_plane_norms[plane_id]; // 计算点到平面的距离(绝对值除以模长) float dist = fabs(pl.a*pt.x + pl.b*pt.y + pl.c*pt.z + pl.d) / norm; return dist < threshold; } }; int main() { const int num_points = 60000; const int num_planes = 500; const float inlier_threshold = 0.01f; // 假设已完成点和平面的GPU初始化(d_points、d_planes) thrust::device_vector<Point> d_points(num_points); thrust::device_vector<Plane> d_planes(num_planes); // 1. 生成平面ID数组:每个平面ID对应所有点 thrust::device_vector<int> plane_ids(num_points * num_planes); thrust::repeat(thrust::counting_iterator<int>(0), thrust::counting_iterator<int>(num_planes), thrust::make_constant_iterator(num_points), plane_ids.begin()); // 2. 生成内点标记数组 thrust::device_vector<bool> is_inlier(num_points * num_planes); thrust::transform(thrust::counting_iterator<int>(0), thrust::counting_iterator<int>(num_points * num_planes), is_inlier.begin(), IsInlier(d_points, d_planes, inlier_threshold)); // 3. 归约统计每个平面的内点数量 thrust::device_vector<int> plane_counts(num_planes); thrust::reduce_by_key(plane_ids.begin(), plane_ids.end(), is_inlier.begin(), thrust::make_discard_iterator(), plane_counts.begin(), thrust::equal_to<int>(), thrust::plus<int>()); // 4. 找到内点最多的平面 auto max_it = thrust::max_element(plane_counts.begin(), plane_counts.end()); int best_plane_idx = max_it - plane_counts.begin(); int max_inliers = *max_it; return 0; }
方案2:自定义CUDA核函数(内存开销更小)
如果觉得3e7元素的数组内存开销太大,可以直接写自定义核函数,每个线程块处理一个平面,遍历所有点统计内点:
代码示例
#include <thrust/device_vector.h> #include <cuda_runtime.h> struct Point { float x, y, z; }; struct Plane { float a, b, c, d; }; __global__ void countInliersKernel(const Point* points, int num_points, const Plane* planes, const float* plane_norms, int num_planes, float threshold, int* counts) { // 每个线程块处理一个平面 int plane_id = blockIdx.x; if (plane_id >= num_planes) return; const Plane& pl = planes[plane_id]; float norm = plane_norms[plane_id]; int local_count = 0; // 线程块内划分点的遍历任务 for (int i = threadIdx.x; i < num_points; i += blockDim.x) { const Point& pt = points[i]; float dist = fabs(pl.a*pt.x + pl.b*pt.y + pl.c*pt.z + pl.d) / norm; if (dist < threshold) { local_count++; } } // 线程块内汇总计数 __shared__ int shared_counts[256]; shared_counts[threadIdx.x] = local_count; __syncthreads(); for (int s = blockDim.x / 2; s > 0; s >>= 1) { if (threadIdx.x < s) { shared_counts[threadIdx.x] += shared_counts[threadIdx.x + s]; } __syncthreads(); } // 把结果写入全局内存 if (threadIdx.x == 0) { counts[plane_id] = shared_counts[0]; } } int main() { const int num_points = 60000; const int num_planes = 500; const float inlier_threshold = 0.01f; const int block_size = 256; thrust::device_vector<Point> d_points(num_points); thrust::device_vector<Plane> d_planes(num_planes); thrust::device_vector<float> d_plane_norms(num_planes); thrust::device_vector<int> d_counts(num_planes); // 提前计算平面模长 thrust::transform(d_planes.begin(), d_planes.end(), d_plane_norms.begin(), [] __host__ __device__(const Plane& p) { return sqrt(p.a*p.a + p.b*p.b + p.c*p.c); }); // 启动核函数:每个平面一个线程块 countInliersKernel<<<num_planes, block_size>>>( thrust::raw_pointer_cast(d_points.data()), num_points, thrust::raw_pointer_cast(d_planes.data()), thrust::raw_pointer_cast(d_plane_norms.data()), num_planes, inlier_threshold, thrust::raw_pointer_cast(d_counts.data()) ); cudaDeviceSynchronize(); // 找到内点最多的平面 auto max_it = thrust::max_element(d_counts.begin(), d_counts.end()); int best_plane_idx = max_it - d_counts.begin(); int max_inliers = *max_it; return 0; }
关键优化建议
- 预先归一化平面:提前计算每个平面的模长,避免在点距离计算中重复开平方,大幅节省计算时间;
- 选择合适的方案:如果追求开发速度,选方案1;如果追求内存效率,选方案2;
- 避免嵌套Thrust调用:记住Thrust的算法是内核级别的操作,不能在设备端函数(如lambda、自定义 functor)里嵌套调用。
内容的提问来源于stack exchange,提问作者Iter Ator
相关产品推荐
相关产品推荐

