OpenCV学习笔记(六):设计评价提取效果的方法

评价房屋提取

上一步提取出房屋内容后,需要进行提取评价。房屋选择限于无树木遮挡的屋顶,对房屋边缘计算法向量,归化到0-90度之间,根据集中性评价利用性。

系统学习一下vector的用法:https://blog.csdn.net/wkq0825/article/details/82255984

本次评价过程主要为:
1、对grabcut图像去除绿色矩形框(存在边缘渐变的效果);
2、对提取的建筑物进行二值化;
3、获得提取的建筑物的最外层轮廓(闭合),并使用宽度为1的线进行描绘;
4、对表示边缘的线进行法向量的提取,并绘制直方图。该过程中,对边缘提取的点向量取相邻2个(每次取5个点)进行直线拟合,拟合完毕取得角度后,将其归化到0-89度间,用直方图进行表示。

在设计提取的时候犯了错,错误地设计了3*3尺寸的窗口进行方向提取,错误代码保存在以下代码中(已说明)
以下是完整代码

#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
#include <math.h>
#include <opencv2/imgproc.hpp>
#include <vector>
#include<algorithm>//可以对vector进行一些操作
#include <map>//绘制直方图



using namespace std;
using namespace cv;




//------------------------------------------------------------------------------------------
//函数区
int value_test(Mat img);//用于测试传值的函数
int rectoff(Mat img_raw, Mat img);//去除原图上的绿色矩形框
int rgbTobinary(Mat img, Mat img_bin);//rgb图像转到二值图
int getEdge(Mat img_bin, Mat img_edge);//由二值化图获得最外层轮廓的函数
double pointTolineTodirect(Mat img_bin, vector<double> array_direct);//emm,为了保持和前面提取的边缘是一致的,此处使用img_bin(后续可以改)
double adjust_scale(double angle);


//------------------------------------------------------------------------------------------
//误区
double getAngle(Mat img_edge);//计算边缘的法向量并返回边缘点的法向量归化角数组
int getvec(int point_1, int point_2, int vec_return[2]);//输入点序号获得到该点的切向量

//------------------------------------------------------------------------------------------
//主函数区
int main()
{
	//------------------------------------------------------------------------------------------
	//Part I:首先要把grabcut的矩形框去掉
	Mat img_raw = imread("D:/Desktop/2020暑期实习/7月16号重新开始/7.21/test1_raw.png");
	//还是要养成好的习惯检验读取
	if (img_raw.empty() == 1)
	{
		cout << "读取失败";
		return -1;
	}
	cout << "image loaded successfully" << endl;
	imshow("win_raw", img_raw);
	waitKey(0);
	//------------------------------------------------------------------------------------------
	int row = img_raw.rows;
	int col = img_raw.cols;
	Mat img(row, col, CV_8UC3);
	rectoff(img_raw,img);
	//imwrite("D:/Desktop/2020暑期实习/7月16号重新开始/7.21/test1_rectoff.png", img);
	//------------------------------------------------------------------------------------------
	//Part II:找到提取的轮廓
	//1、二值化
	Mat img_bin = Mat::zeros(img.size(), CV_8UC1);//二值化的时候自己手动改一改阈值,会对边缘提取效果提高有大帮助
	rgbTobinary(img, img_bin);
	//imwrite("D:/Desktop/2020暑期实习/7月16号重新开始/7.21/test1_bin.png", img_bin);
	//2、提取边缘
	//------------------------------------------------------------------------------------------
	Mat img_edge = Mat::zeros(img.size(), CV_8UC3);//此处要得到单通道图的话可以采用RGB2GRAY的方式,不需要自己设计函数进行不完全消除。
	getEdge(img_bin, img_edge);
	//imwrite("D:/Desktop/2020暑期实习/7月16号重新开始/7.21/test1_edge.png", img_edge);
	//------------------------------------------------------------------------------------------
	//Part III:对边缘进行法方向分析
	//提取边缘的法向量

	//------------------
	//------------------------------------------------------------------------------------------
	//我的计划是把边缘的法向量给到每一个边界点像素上,通过设计一个3x3的窗口(边缘为一个像素宽度),去对目标点(区域中心)的法向量方向角进行提取
	//由于需要归化到0-90度,故计算斜率的方向角与计算法向量的方向角等效
	//------------------------------------------------------------------------------------------
	//这个方法错误,忽略

	//方法二
	vector<double> array_direct;//作为法方向的存储
	pointTolineTodirect(img_bin, array_direct);


	return 0;
}




int value_test(Mat img)//测试传值有无问题
{
	for (int i = 0; i < img.rows; i++)
		for (int j = 0; j < img.cols; j++)
		{
			img.at<Vec3b>(i, j)[0] = 0;
			img.at<Vec3b>(i, j)[1] = 0;
			img.at<Vec3b>(i, j)[2] = 0;
		}
	return 0;
}
int rectoff(Mat img_raw,Mat img_out)//去掉grabCut函数的矩形框的函数,这个函数目前有点问题,因为那个绿色边框加上去之后会导致边缘不是严格的Green
{                                   //注意此函数由于去绿框时的参数范围设置,在处理绿色提取物的时候有可能发生误去除现象,具体情况具体分析
	//从代码可知,矩形框的RGB值是G:255
	for (int i = 0; i < img_raw.rows; i++)
		for (int j = 0; j < img_raw.cols; j++)
		{
			img_out.at<Vec3b>(i,j)[0] = img_raw.at<Vec3b>(i, j)[0];
			img_out.at<Vec3b>(i, j)[1] = img_raw.at<Vec3b>(i, j)[1];
			img_out.at<Vec3b>(i, j)[2] = img_raw.at<Vec3b>(i, j)[2];

			if (img_raw.at<Vec3b>(i, j)[0] == 0)
				if (img_raw.at<Vec3b>(i, j)[2] == 0)
					if (img_raw.at<Vec3b>(i, j)[1]<=255&& img_raw.at<Vec3b>(i, j)[1]>=0)
					{
						img_out.at<Vec3b>(i, j)[1] = 0;
					}

		}

	cout << "rectangel has been cleared successfully" << endl;

	imshow("win_img", img_out);
	waitKey(0);

	return 0;

}
int rgbTobinary(Mat img, Mat img_bin)
{
	Mat img_gray = Mat::zeros(img.size(), CV_8UC1);
	//此处要得到单通道图的话可以采用RGB2GRAY的方式,不需要自己设计函数进行不完全消除。
	cvtColor(img, img_gray, COLOR_BGR2GRAY);
	//threshold(img_gray, img_bin, 100, 255, THRESH_OTSU);//大津法进行阈值分割
	threshold(img_gray, img_bin, 50, 255, THRESH_BINARY);//这个好像是手动阈值,此处注意这个阈值会导致边缘的变化(因为边缘的色彩会形成渐变)


	cout << "image has been transfrom to binary image successfully" << endl;
	imshow("win_bin", img_bin);
	waitKey(0);

	return 0;
}
int getEdge(Mat img_bin,Mat img_edge)
{
	vector<vector<Point>> contours;
	vector<Vec4i> hierarchy;
	//查找轮廓
	findContours(img_bin, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE);
	//绘制查找到的轮廓
	drawContours(img_edge, contours, -1, Scalar(0, 255, 0),1);//绘制的轮廓为绿色,宽度为1
	cout << "edge has been extracted successfully" << endl;

	imshow("win_edge", img_edge);
	waitKey(0);

	//cout << contours[0][13].x << endl;//获得边界点的信息


	return 0;
}
double pointTolineTodirect(Mat img_bin,vector<double>array_direct)
{
	//储存结果初始化
	array_direct.clear();

	//为了保证处理的边缘为画出的边缘,我们对binary图进行处理
	vector<vector<Point>> contours;//the format is the "array of arrays", because here may exsist more than 1 contour
	vector<Vec4i> hierarchy;
	//1、查找轮廓
	findContours(img_bin, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE);

	Mat win_test(img_bin.size(), CV_8UC3);

	vector<Point> contours_1;

	for (int i = 0; i < contours[0].size(); i++)
	{
		contours_1.push_back(contours[0][i]);//经输出检验,储存的边缘是转圈储存的,故可以进行间隔直线拟合操作
	}

	//2、间隔区域直线拟合
	vector<Point> win_point_in;
	for (int j = 2; j < contours_1.size() - 2; j++)
	{
		win_point_in.clear();
		for (int k = 0; k < 5; k++)
		{
			win_point_in.push_back(contours_1[j+k-2]);
		}

		Vec4f line_out;
		fitLine(win_point_in, line_out, DIST_L2, 0, 1e-2, 1e-2);

		double angle;//方位角
		angle = acos(line_out[0])*(180.f/CV_PI);
		array_direct.push_back(angle);
	
		//cout << angle << endl;

	}
	cout << "//--------------------------------------------------//" << endl;
	//3、将角度归化
	vector<int> direction_scale;//储存最后的方向们,且储存的内容是四舍五入为整的归化方向角
	for (int ct = 0; ct < array_direct.size(); ct++)
	{
		//为后续的直方图绘制方便,对求取的方向角做四舍五入取整
		if ((adjust_scale(array_direct[ct]) - int(adjust_scale(array_direct[ct]))) >= 0.5)
		{
			direction_scale.push_back(int(int(adjust_scale(array_direct[ct]))+1));
		}
		else
			direction_scale.push_back(int(adjust_scale(array_direct[ct])));
	}

	


	//4、准备绘制直方图
	//C++好像不带图形库,但是opencv是有的,opencv真的香
	//数据存在vector:direction_scale中,对其进行排序并绘出直方图,于是利用opencv中的函数进行绘图吧
	Mat hist(1, direction_scale.size(), CV_8UC1);
	for (int ct2 = 0; ct2 < direction_scale.size(); ct2++)
	{
		hist.at<uchar>(0, ct2) = direction_scale[ct2];
	}
	const int channels[1] = { 0 };
	int histSize[] = { 256 };
	float midRanges[] = { -10, 90 };
	const float *ranges[] = { midRanges };
	MatND dstHist;
	calcHist(&hist, 1, channels, Mat(), dstHist, 1, histSize, ranges, true, false);
	Mat drawImage = Mat::zeros(Size(256, 256), CV_8UC3);
	double g_dHistMaxValue;
	minMaxLoc(dstHist, 0, &g_dHistMaxValue, 0, 0);
	for (int i = 0; i < 256; i++)
	{
		int value = cvRound(256 * 0.9 *(dstHist.at<float>(i) / g_dHistMaxValue));

		line(drawImage, Point(i, drawImage.rows - 1), Point(i, drawImage.rows - 1 - value), Scalar(255, 0, 0));
	}

	imshow("win_hist", drawImage);
	waitKey(0);
	return 0;

}
double adjust_scale(double angle)
{
	double angle_out;
	//由于acos转换后只会返回0-180
	if (angle >= 90)
	{
		angle_out = angle - 90;
		return angle_out;
	}

	angle_out = angle;
	return angle_out;
}






double getAngle(Mat img_edge)
{
	double *angelhist_return;//储存像素点法向量方向的数组,一维即可

	//对边缘图遍历
	for(int i=1;i<img_edge.rows-1;i++)//注意边界范围
		for (int j = 1; j < img_edge.cols-1; j++)
		{
			int in_counter = 0;
			int out_counter = 0;

			//可以不用设计类,直接用元素位置计算就可
			//-----------------------------------------------------------------------------------------
			//初筛,如果目标点不是边缘点,则直接跳到下一次循环
			if (img_edge.at<Vec3b>(i, j)[1] == 0)
				continue;
			//-----------------------------------------------------------------------------------------
			//设计3x3窗口(每次在循环中声明)
			double win_array[9] = { img_edge.at<Vec3b>(i - 1, j - 1)[1],img_edge.at<Vec3b>(i-1 , j)[1],img_edge.at<Vec3b>(i - 1, j + 1)[1],img_edge.at<Vec3b>(i, j-1)[1],img_edge.at<Vec3b>(i , j)[1],img_edge.at<Vec3b>(i , j+1)[1],img_edge.at<Vec3b>(i + 1, j - 1)[1],img_edge.at<Vec3b>(i+1 , j)[1],img_edge.at<Vec3b>(i + 1, j + 1)[1] };
			//-----------------------------------------------------------------------------------------
			//检测,并储存识别出的点的位置(以点号储存)
			int point_num[2] = {};//暂时感觉是只会有两个点(这个要反复论证)
			int flag_1 = 0;//记录已经获得的点的数量
			//-----------------------------------------------------------------------------------------
			//1、首先检测内部元素(内部元素至多会有两个)
			for (int k = 0; k < 4; k++)
			{
				if (in_counter == 2)
				{
					cout << "内部连接点计数器初始化错误" << endl;
					break;
				}
					if (win_array[2 * k + 2] == 255)
				{
					in_counter++;
					point_num[flag_1] = 2 * k + 2;
					flag_1++;
				}
			}
			//-----------------------------------------------------------------------------------------
			//2、检测外部元素
			for (int g = 0; g <= 4; g++)
			{
				if (g == 2)//注意不要把自己算进去
					continue;
				if (out_counter == 2)
					break;
				if (in_counter == 2)
					break;
				if (win_array[2 * g + 1] == 255)
				{
					if (in_counter == 1)
					{
						switch (point_num[0])//对已检测的
						{
						case 2:
							if ((2 * g + 1) == 1 || (2 * g + 1) == 3)
								continue;
							break;
						case 4:
							if ((2 * g + 1) == 1 || (2 * g + 1) == 7)
								continue;
							break;
						case 6:
							if ((2 * g + 1) == 3 || (2 * g + 1) == 9)
								continue;
							break;
						case 8:
							if ((2 * g + 1) == 7 || (2 * g + 1) == 9)
								continue;
							break;
						}
					}

					out_counter++;
					point_num[flag_1] = 2 * g + 1;
					flag_1++;//这个counter可以不用单独设置if,用counter就可以判断了

				}

			}
			//-----------------------------------------------------------------------------------------
			//经检测后,发现由绘制函数绘制的边缘不会与相邻边缘产生渐变效果
			//此时得到了想要的两个点的位置,故下一步就是求取目标点的切向量
			int point_vec[2] = {};//存储该点的切向量
			getvec(point_num[0], point_num[1], point_vec);//得到该点的切向量
			//下一步是将切向量转化为方向角



			//此处就需要把法向量储存到一个数组里了,数组使用动态数组


			
		}



	return 0;

}
int getvec(int point_1, int point_2,int vec_return[2])//测试一下能不能返回指针
{
	int vec_1[2], vec_2[2];
	//对两个点进行分析
	switch (point_1)//统一以中心点为出发点
	{
	case 1:
		vec_1[0] = -1; vec_1[1] = 1;
		break;
	case 2:
		vec_1[0] = 0; vec_1[1] = 1;
		break;
	case 3:
		vec_1[0] = 1; vec_1[1] = 1;
		break;
	case 4:
		vec_1[0] = -1; vec_1[1] = 0;
		break;
	case 6:
		vec_1[0] = 1; vec_1[1] = 0;
		break;
	case 7:
		vec_1[0] = -1; vec_1[1] = -1;
		break;
	case 8:
		vec_1[0] = 0; vec_1[1] = -1;
		break;
	case 9:
		vec_1[0] = 1; vec_1[1] = -1;
		break;

	}
	switch (point_2)//统一以中心点为出发点
	{
	case 1:
		vec_2[0] = -1; vec_2[1] = 1;
		break;
	case 2:
		vec_2[0] = 0; vec_2[1] = 1;
		break;
	case 3:
		vec_2[0] = 1; vec_2[1] = 1;
		break;
	case 4:
		vec_2[0] = -1; vec_2[1] = 0;
		break;
	case 6:
		vec_2[0] = 1; vec_2[1] = 0;
		break;
	case 7:
		vec_2[0] = -1; vec_2[1] = -1;
		break;
	case 8:
		vec_2[0] = 0; vec_2[1] = -1;
		break;
	case 9:
		vec_2[0] = 1; vec_2[1] = -1;
		break;
	}

	vec_return[0] = vec_1[0] - vec_2[0];
	vec_return[1] = vec_1[1] - vec_2[1];

	return 0;
}



实现效果

初始图
去除矩形框
二值化图
边缘
直方图
直方图为了把 0 突出,x轴范围从(-10,90)

其他测试结果
1、不同建筑物提取评价(前后两像素拟合)



2、同一建筑物不同长度拟合
下图从左至右依次为:2,3,4,5,6个两端相邻像素
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值