KMEANS轮廓点排序

OPENCV:Kmeans的四个轮廓角点,进行逆时针排序。

代码整体思路为:

  • canny提取轮廓
  • 开闭操作
  • 提取最大轮廓(实际应用对象为一个带圆角的矩形)
  • 多边形拟合轮廓
  • 轮廓分割的比较好的话 使用Kmeans 聚类四个点
  • 对四个点进行排序(顺序正好为Label的顺序 因为轮廓点的顺序是按照逆时针来的),并对排序的点对应应该的矩形大小找单应变换矩阵
  • 把拍到的图像通过单应变换变回去

如何对Kmeans的四个聚类点进行排序并通过单应矩阵变换

Mat labels,centers;//聚类的点
cv::kmeans(Mat(polypoints), 4, labels, TermCriteria(TermCriteria::EPS + TermCriteria::MAX_ITER, 10, 1.0),5, cv::KMEANS_RANDOM_CENTERS, centers);

vector<Point2f> pixel_points4;
for (int i = 0; i < centers.rows; i++) {
	cv::drawMarker(testimg_real, Point(int(centers.at<float>(i, 0)), int(centers.at<float>(i, 1))), (255, 255, 255), 1, 20, 5);
	pixel_points4.push_back(Point2f(centers.at<float>(i, 0), centers.at<float>(i, 1)));
	}
cout << pixel_points4<<"四个聚类点的坐标" << endl;

//提取多边形顶点的顺序:labels本身的顺序继承了多边形点的顺序,也就是轮廓的顺序,逆时针顺序
vector<int> labels_lst = { labels.at<int>(0,0) };//**花括号元素赋初值 第一个点属于哪个类型 
vector<int>::iterator ret;//**迭代器

for (int i = 0; i < labels.rows; i++) {
	ret = std::find(labels_lst.begin(), labels_lst.end(), labels.at<int>(0, i));
	if (ret == labels_lst.end()) {
		//not find
		labels_lst.push_back(labels.at<int>(i, 0));
		}
}
//顶点顺序重构,多边形顶点顺序为逆时针
	vector<Point2f> pixel_points4_regroup;
	int x_min_idx;
	float x_min = 9999.0;
	for (int i = 0; i < labels_lst.size(); i++) {
		pixel_points4_regroup.push_back(pixel_points4[labels_lst[i]]);
		if (pixel_points4_regroup[i].x < x_min) {
				//找pixel_points4_regroup中的最小x的索引
				x_min = pixel_points4_regroup[i].x;
				x_min_idx = i;
				}
	}
//长短边探测,找到真正的起始点
			vector<Point2f> pixel_points4_regroup2;
			//起始点
			Point2f pt1 = pixel_points4_regroup[x_min_idx];
			//起始点逆时针下一个点
			Point2f pt2 = pixel_points4_regroup[(x_min_idx + 1) % 4];
			//起始点逆时针上一个点
			Point2f pt3 = pixel_points4_regroup[(x_min_idx + 3) % 4];
			cout << "pt1\t" << pt1 << endl;
			cout << "pt2\t" << pt2 << endl;
			cout << "pt3\t" << pt3 << endl;
			if (getDistance(pt1, pt2) > getDistance(pt1, pt3)) {
				for (int i = 0; i < 4; i++) {
					pixel_points4_regroup2.push_back(pixel_points4_regroup[(x_min_idx + 3 + i) % 4]);
				}
			}
			else {
				for (int i = 0; i < 4; i++) {
					pixel_points4_regroup2.push_back(pixel_points4_regroup[(x_min_idx + i) % 4]);
				}
			}
			//cv::putText(testimg_real, "1", Point2f(pixel_points4_regroup[0].x, pixel_points4_regroup[0].y), 1, 10, cv::Scalar(255, 255, 255));
			cout << pixel_points4_regroup2 << endl;
			//最小外接矩形顶点,奇葩,输出来的是矩阵
			Mat point4;
			cv::RotatedRect rect = cv::minAreaRect(c);
			cv::boxPoints(rect, point4);
			//cout << point4.size() << endl;
			//cout << point4.at(0, 3) << endl;

			//找到pixel_points4_regroup2与最小外接矩形顶点最近的点,并将pixel_points4_regroup2的点向该方向拓展
			//保证截取的画面包含圆形按钮
			float rate = 0.5;
			for (int i = 0; i < 4; i++) {
				float min_norm = 99999;
				int min_idx = -1;
				for (int j = 0; j < 4; j++) {
					float tmp_norm = getDistancepp(pixel_points4_regroup2[i].x, pixel_points4_regroup2[i].y, point4.at<float>(j, 0), point4.at<float>(j, 1));
					if (tmp_norm < min_norm) {
						min_idx = j;
						min_norm = tmp_norm;
					}
				}
				if (min_idx >= 0) {
					pixel_points4_regroup2[i].x += rate * (point4.at<float>(min_idx, 0) - pixel_points4_regroup2[i].x);
					pixel_points4_regroup2[i].y += rate * (point4.at<float>(min_idx, 1) - pixel_points4_regroup2[i].y);
				}
			}


			//cout << pixel_points4_regroup2 << endl;
			//需要变换到的坐标
			//正面
			//vector h_p = { Point2f(0, 0),Point2f(0, 500) ,Point2f(1043, 500) ,Point2f(1043, 0) };
			//背面
			vector<Point2f> h_p = { Point2f(0, 0),Point2f(0, 341) ,Point2f(697, 341) ,Point2f(697, 0) };
			Mat warp_img;
			Mat M = cv::findHomography(pixel_points4_regroup2, h_p);
			//cv::warpPerspective(testimg_gray, warp_img, M, cv::Size(1043, 500));
			cv::warpPerspective(testimg_gray, warp_img, M, cv::Size(697, 341));


			cv::imshow("img", testimg_real);
			cv::imshow("warp_img", warp_img);
			//cv::waitKey();

double cv::kmeans( InputArray data, int K, InputOutputArray bestLabels, TermCriteria citeria, int attempts, int flags, OutputArray)

(1)data: 需要自动聚类的数据,一般是一个Mat。浮点型的矩阵,每行为一个样本。
(2)k: 取成几类,比较关键的一个参数。
(3)bestLabels: 返回的类别标记,整型数字。
(4)criteria: 算法结束的标准,获取期望精度的迭代最大次数
(5)attempts: 判断某个样本为某个类的最少聚类次数,比如值为3时,则某个样本聚类3次都为同一个类,则确定下来。
(6)flags: 确定簇心的计算方式。有三个值可选:KMEANS_RANDOM_CENTERS 表示随机初始化簇心。KMEANS_PP_CENTERS 表示用kmeans++算法来初始化簇心(没用过),KMEANS_USE_INITIAL_LABELS 表示第一次聚类时用用户给定的值初始化聚类,后面几次的聚类,则自动确定簇心。
(7)centers: 用来初始化簇心的。与前一个flags参数的选择有关。如果选择KMEANS_RANDOM_CENTERS随机初始化簇心,则这个参数可省略。

你可能感兴趣的:(C++,学习)