OpenCV Kmeans从深度帧获取聚类中心返回错误坐标求助
解决CV_16SC1深度帧聚类中心坐标错误的问题
兄弟,我太懂你现在的憋屈了——对着512×424的16位单通道深度帧折腾聚类,结果要么中心跑到左上角,要么直接没结果,换谁都得挠头。结合你提到的Stack Overflow示例和这类深度数据的特性,咱们一步步拆解问题,把它搞定!
先揪出核心问题
你踩的坑大概率是这几个:
- 数据类型不匹配:SO上很多示例用的是8位灰度图(
CV_8UC1),而你的是16位有符号短整型(CV_16SC1),数值范围差了好几个量级,聚类算法很容易被无效值或极端值带偏; - 无效深度值没过滤:深度相机输出的帧里经常混着无效值(比如0、超出量程的极大值/极小值),这些值大多集中在边缘或左上角,算法误把它们当成了核心聚类;
- 聚类输入逻辑错了:如果直接把16位深度值和坐标混在一起输入,深度的大数值会盖过坐标的权重,导致结果完全偏离预期。
一步步解决的具体方案
1. 先修正数据类型(关键第一步)
深度相机输出的深度数据几乎都是无符号短整型(ushort,范围0~65535),很少会用有符号类型。如果你只是标记错了类型,先把矩阵转换成CV_16UC1:
// 把CV_16SC1转成CV_16UC1(确认无负数无效值的前提下) cv::Mat depth_16u; depth.convertTo(depth_16u, CV_16UC1);
如果确实是有符号数据,先过滤掉所有负数值——深度不可能为负,这些全是无效数据。
2. 过滤无效深度值,只保留有效像素
这一步是解决“左上角错误中心”的关键,先把无效值全部屏蔽:
// 先定义你的相机有效深度范围(比如100~60000,根据实际参数调整) cv::Mat mask; cv::inRange(depth_16u, cv::Scalar(100), cv::Scalar(60000), mask); // 提取有效像素的坐标(后续聚类用) std::vector<cv::Point2f> valid_points; for (int y = 0; y < depth_16u.rows; y++) { for (int x = 0; x < depth_16u.cols; x++) { if (mask.at<uchar>(y, x) == 255) { valid_points.emplace_back(x, y); } } }
3. 用K-Means正确计算聚类中心(以OpenCV为例)
这里分两种场景,你按需选:
场景A:找有效区域的整体坐标中心
如果只是想找所有有效深度像素的中心,直接用坐标作为聚类输入:
// 把坐标转换成K-Means需要的Mat格式 cv::Mat points_mat(valid_points.size(), 2, CV_32F); for (int i = 0; i < valid_points.size(); i++) { points_mat.at<float>(i, 0) = valid_points[i].x; points_mat.at<float>(i, 1) = valid_points[i].y; } // 配置K-Means参数(K=1表示只找一个中心) int K = 1; cv::Mat labels, centers; cv::kmeans(points_mat, K, labels, cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::MAX_ITER, 100, 0.001), 3, cv::KMEANS_PP_CENTERS, centers); // 输出中心坐标,centers里存的就是(x,y) std::cout << "聚类中心坐标:(" << centers.at<float>(0,0) << ", " << centers.at<float>(0,1) << ")" << std::endl;
场景B:按深度值分组,找每组的坐标中心
如果需要把深度相近的像素归为一类,再找每类的中心:
// 先提取有效像素的深度值 std::vector<float> valid_depths; for (int y = 0; y < depth_16u.rows; y++) { for (int x = 0; x < depth_16u.cols; x++) { if (mask.at<uchar>(y, x) == 255) { valid_depths.push_back(static_cast<float>(depth_16u.at<ushort>(y, x))); } } } // 对深度值做聚类(比如分2类) cv::Mat depths_mat(valid_depths.size(), 1, CV_32F); for (int i = 0; i < valid_depths.size(); i++) { depths_mat.at<float>(i, 0) = valid_depths[i]; } int K = 2; cv::Mat depth_labels, depth_centers; cv::kmeans(depths_mat, K, depth_labels, cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::MAX_ITER, 100, 0.001), 3, cv::KMEANS_PP_CENTERS, depth_centers); // 遍历每个聚类,计算对应像素的坐标中心 for (int k = 0; k < K; k++) { float sum_x = 0, sum_y = 0; int count = 0; for (int i = 0; i < depth_labels.rows; i++) { if (depth_labels.at<int>(i, 0) == k) { sum_x += valid_points[i].x; sum_y += valid_points[i].y; count++; } } if (count > 0) { std::cout << "聚类" << k << "的中心坐标:(" << sum_x/count << ", " << sum_y/count << ")" << std::endl; } }
4. 验证结果,可视化确认
把聚类中心画到深度帧上,直观验证是否正确:
// 把16位深度帧转成8位方便显示 cv::Mat depth_8u; cv::normalize(depth_16u, depth_8u, 0, 255, cv::NORM_MINMAX, CV_8UC1); // 画红色中心圆点 cv::circle(depth_8u, cv::Point(centers.at<float>(0,0), centers.at<float>(0,1)), 5, cv::Scalar(0,0,255), -1); cv::imshow("聚类中心预览", depth_8u); cv::waitKey(0);
最后再提两个避坑提醒
- 绝对不要直接把原始16位深度值和坐标混在一起输入聚类,深度的数值范围太大,会完全盖过坐标的权重;
- 如果还是出现无结果,检查一下
valid_points的数量——如果有效像素太少,聚类算法会失效,这时候要调整有效深度的范围。
内容的提问来源于stack exchange,提问作者Nelson Silva
相关产品推荐
相关产品推荐

