评价房屋提取
上一步提取出房屋内容后,需要进行提取评价。房屋选择限于无树木遮挡的屋顶,对房屋边缘计算法向量,归化到0-90度之间,根据集中性评价利用性。
系统学习一下vector的用法:https://blog.youkuaiyun.com/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个两端相邻像素