在利用传感器获取房间地图信息后由于多次测量存在误差,导致房间地图轮廓歪歪曲曲影响观感,我这里采用水平方向和垂直方向分别执行邻近线段合并的方法对这一问题做了一些处理。以下图为例,黑色表示地图轮廓,比如在垂直方向执行邻近线段合并时先获取图像3列数据作为一个计算单元,如下图的计算单元a、b、c,再将这些计算单元里邻近的线段合并成一条直线,至于计算单元c,由于其近似于曲线不进行合并,避免矫枉过正。

计算单元处理前后

完整代码如下所示:

#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <vector>using namespace std;
using namespace cv;//函数功能:排序(从小到大)
template<typename T>
void quick_sort(vector<T>& arr, int left, int right, vector<int>& arr_index)
{int i, j;int t1,temp1;T temp,t;if (left>right)return;temp = arr[left];temp1 = arr_index[left];i = left;j = right;while (i != j){while (arr[j] >= temp && i<j)j--;while (arr[i] <= temp && i<j)i++;if (i<j){t = arr[i];arr[i] = arr[j];arr[j] = t;t1 = arr_index[i];arr_index[i] = arr_index[j];arr_index[j] = t1;}}arr[left] = arr[i];arr[i] = temp;arr_index[left] = arr_index[i];arr_index[i] = temp1;quick_sort(arr,left, i - 1,arr_index);quick_sort(arr,i + 1, right,arr_index);
}//函数功能:根据给定索引进行排序
template<typename T>
void resort_arr(vector<T>& arr, vector<int> indexs)
{int len = arr.size();T* backup = new T[len];for(int i=0; i<len; i++){backup[i] = arr[i];}for(int i=0; i<len; i++){arr[i] = backup[indexs[i]];}
}/*
函数功能:在二维数组中绘制指定位置的线段
arr:二维数组,可理解为二值化后的灰度图像
cols:图像的宽
ltop:线段左上位置的端点
rbottom:线段的右下位置的端点
val:绘制直线线段的值
*/
void set_array(uchar* arr, int cols, Point ltop, Point rbottom, int val)
{// 直线绘制if(ltop.x == rbottom.x || ltop.y == rbottom.y){for(int i=ltop.y; i<=rbottom.y; i++){for(int j=ltop.x; j<=rbottom.x; j++){*(arr+i*cols+j) = val;}}}// 斜线绘制,绘制斜线时须在X和Y两个方向进行遍历,否则对于近乎水平或竖直的斜线绘制会有问题else{float x_slope = float(rbottom.y - ltop.y) / (rbottom.x - ltop.x);for(int i=ltop.x; i<=rbottom.x; i++){int yoffset = (i-ltop.x)*x_slope + ltop.y;*(arr+yoffset*cols+i) = val;}float y_slope = float(rbottom.x - ltop.x) / (rbottom.y - ltop.y);for(int j=ltop.y; j<=rbottom.y; j++){int xoffset = (j-ltop.y)*y_slope + ltop.x;*(arr+j*cols+xoffset) = val;}}
}/*
函数功能:在竖直方向和水平方向合并邻近的直线线段(对斜线不作处理)
image_data:二维数组,可以认为是二值化后的灰度图像
img_w:图像宽
img_h:图像高
merge_step:每merge_step行或列作为一个线段合并的计算单元
merge_max_gap:前后线段之间的首尾距离小于merge_max_gap才满足合并条件
merge_len_thresh:合并的两条线段的长度和大于merge_len_thresh才满足合并条件(避免斜线被合并)
*/
void merge_lines(uchar* image_data,int img_w,int img_h,int merge_step = 3,int merge_max_gap = 3,int merge_len_thresh = 30)
{// 竖直方向合并线段for(int n=0; n<img_w-merge_step; n++){int line_num = 0; //检测到的线段数量vector<vector<int>> vec_lines; //存储计算单元内每列的线段首尾偏移数据for(int x=n; x<n+merge_step; x++){vector<int> vec_line;int last_val = 0;int head_off = 0;int tail_off = 0;for(int y=0; y<img_h; y++){int gray_scale = *(image_data+y*img_w+x);if(gray_scale==255 && last_val==0){head_off = y;}if((gray_scale==0 && last_val==255)){tail_off = y;// 长度为1的线段忽略,避免处理后直角处出现断裂if(tail_off - head_off == 1){last_val = 0;continue;}vec_line.push_back(head_off);vec_line.push_back(tail_off);line_num++;}last_val = gray_scale;}vec_lines.push_back(vec_line);}// 不少于2条线段执行合并操作if(line_num >= 2){vector<int> heads; //保存线段的头部偏移vector<int> tails; //保存线段的尾部偏移vector<int> indexs;for(int i=0; i<vec_lines.size(); i++){for(int j=0; j<vec_lines[i].size(); j+=2){heads.push_back(vec_lines[i][j]);tails.push_back(vec_lines[i][j+1]);indexs.push_back(indexs.size());}}// 对计算单元内所有的线段头部偏移进行从小到大的排序quick_sort(heads, 0, line_num-1, indexs);// 所有的尾部偏移根据头部偏移的排序索引重新排序resort_arr(tails, indexs);vector<int> merge_heads; //线段合并后的头部偏移数组,同一列需要合并的线段可能不止一条vector<int> merge_tails; //线段合并后的尾部偏移数组int current_merge_head = heads[0]; //当前合并线段的头部偏移int current_merge_tail = tails[0]; //当前合并线段的尾部偏移for(int i=1; i<line_num; i++){int merge_len = current_merge_tail - current_merge_head;int line_len = tails[i] - heads[i];// 合并条件:当前合并线段的尾部偏移和下一条线段的头部偏移最大的距离不超过阈值,// 且二者长度和需大于阈值,防止邻近的两条斜线被误合并if(current_merge_tail-heads[i] > -merge_max_gap&& merge_len+line_len > merge_len_thresh){current_merge_tail = max(current_merge_tail, tails[i]);}else{if(merge_len > merge_len_thresh){merge_heads.push_back(current_merge_head);merge_tails.push_back(current_merge_tail);}current_merge_head = heads[i];current_merge_tail = tails[i];}}// 若最后一条线段也需要合并,则需要手动把合并线段加进来(此处可能会添加重复,不过不影响)if(current_merge_tail - current_merge_head > merge_len_thresh){merge_heads.push_back(current_merge_head);merge_tails.push_back(current_merge_tail);}// 遍历计算单元里所有线段,将组成合并线段的各个子线段清除for(int k=0; k<merge_heads.size(); k++){Point merge_head_pt(-1);Point merge_tail_pt(-1);for(int i=0; i<vec_lines.size(); i++){for(int j=0; j<vec_lines[i].size(); j+=2){if(vec_lines[i][j] >= merge_heads[k] && vec_lines[i][j+1] <= merge_tails[k]){// 记录合并线段的头坐标点if(vec_lines[i][j] == merge_heads[k] && merge_head_pt.x == -1){merge_head_pt.x = n+i;merge_head_pt.y = merge_heads[k];}// 记录合并线段的尾坐标点if(vec_lines[i][j+1] == merge_tails[k] && merge_tail_pt.x == -1){merge_tail_pt.x = n+i;merge_tail_pt.y = merge_tails[k]-1;}// 将子线段的像素值置0set_array(image_data, img_w, Point(n+i,vec_lines[i][j]), Point(n+i, vec_lines[i][j+1]-1), 0);}}}// 将合并线段的像素值置255set_array(image_data, img_w, merge_head_pt, merge_tail_pt, 255);}}}// 水平方向合并线段(同竖直方向合并线段同理)for(int n=0; n<img_h-merge_step; n++){int line_num = 0;vector<vector<int>> vec_lines;for(int y=n; y<n+merge_step; y++){vector<int> vec_line;int last_val = 0;int head_off = 0;int tail_off = 0;for(int x=0; x<img_w; x++){int gray_scale = *(image_data+y*img_w+x);if(gray_scale==255 && last_val==0){head_off = x;}if((gray_scale==0 && last_val==255)){tail_off = x;if(tail_off - head_off == 1){last_val = 0;continue;}vec_line.push_back(head_off);vec_line.push_back(tail_off);line_num++;}last_val = gray_scale;}vec_lines.push_back(vec_line);}if(line_num >= 2){vector<int> heads;vector<int> tails;vector<int> indexs;for(int i=0; i<vec_lines.size(); i++){for(int j=0; j<vec_lines[i].size(); j+=2){heads.push_back(vec_lines[i][j]);tails.push_back(vec_lines[i][j+1]);indexs.push_back(indexs.size());}}quick_sort(heads, 0, line_num-1, indexs);resort_arr(tails, indexs);vector<int> merge_heads;vector<int> merge_tails;int current_merge_head = heads[0];int current_merge_tail = tails[0];for(int i=1; i<line_num; i++){int merge_len = current_merge_tail - current_merge_head;int line_len = tails[i] - heads[i];if(current_merge_tail-heads[i] > -merge_max_gap&& merge_len+line_len > merge_len_thresh){current_merge_tail = max(current_merge_tail, tails[i]);}else{if(merge_len > merge_len_thresh){merge_heads.push_back(current_merge_head);merge_tails.push_back(current_merge_tail);}current_merge_head = heads[i];current_merge_tail = tails[i];}}if(current_merge_tail - current_merge_head > merge_len_thresh){merge_heads.push_back(current_merge_head);merge_tails.push_back(current_merge_tail);}for(int k=0; k<merge_heads.size(); k++){Point merge_head_pt(-1,-1);Point merge_tail_pt(-1,-1);for(int i=0; i<vec_lines.size(); i++){for(int j=0; j<vec_lines[i].size(); j+=2){if(vec_lines[i][j] >= merge_heads[k] && vec_lines[i][j+1] <= merge_tails[k]){if(vec_lines[i][j] == merge_heads[k] && merge_head_pt.x == -1){merge_head_pt.x = merge_heads[k];merge_head_pt.y = n+i;}if(vec_lines[i][j+1] == merge_tails[k] && merge_tail_pt.x == -1){merge_tail_pt.x = merge_tails[k]-1;merge_tail_pt.y = n+i;}set_array(image_data, img_w, Point(vec_lines[i][j], n+i), Point(vec_lines[i][j+1]-1, n+i), 0);}}}set_array(image_data, img_w, merge_head_pt, merge_tail_pt, 255);}}}
}int main()
{  Mat src_img = imread("../mergeLines/test.png", IMREAD_GRAYSCALE);imshow("合并线段前", src_img);// 合并邻近的直线merge_lines(src_img.data, src_img.cols, src_img.rows, 3, 3, 30);imshow("合并线段后", src_img);waitKey();return 0;
}

最后效果如下:

图像处理之地图轮廓美化相关推荐

  1. 激光SLAM-地图边界噪点的处理(地图的美化)--图像处理的方法

    激光SLAM-地图边界噪点的处理(地图的美化)--图像处理的方法 问题介绍 实现步骤 代码实现 效果 参考文章 问题介绍 本问是实际扫地机器人对于2D激光SLAM采用cartographer所建的地图 ...

  2. vue-echarts绘制地图轮廓

    记录Echarts + Vue 踩坑 使用Echarts 来 绘制地图轮廓图 第一步 要引入 Echarts 引入Echarts/map/ 下的 地区.js 文件 第二步 在视图中, 放置地图容器 第 ...

  3. Dreamwaver地图链接美化页面链接

    Dreamwaver地图链接美化页面链接 1.双击图片,显示工具 看到了吗? 2.用它在图片上 画 它是不是比切割图片漂亮方便! 注意: 代码变化:  <div class="kffw ...

  4. 使用Echars实现水滴状、环形图、分割图、堆叠、组织架构图、地图轮廓等图表

    百度Echarts 水滴状图表 横向柱形图 分割块柱形图 曲线面积图 横向堆叠柱形图 环形进度图 饼状图 饼状图多个标题 组织架构图 省市轮廓地图 新疆省地图 全国地图 折线图阴影效果 柱形折线混合图 ...

  5. 圆 最小外包矩形_【OpenCV3图像处理】提取轮廓的凸包、外包矩形、最小外包矩形、最小外包圆...

    1.提取轮廓的凸包 convexhull()函数(点我看OpenCV3.2帮助文档) 函数调用形式: void convexhul(InputArray points,OutputArray hull ...

  6. 图像处理21-计算轮廓周长或曲线长度

    double cv::arcLength(InputArray curve, bool closed ) 计算轮廓周长或曲线长度. 该函数计算曲线长度或闭合轮廓周长 curve:二维点的输入向量,存储 ...

  7. 基于百度地图API在AI Studio上的卫星地图块图像处理与分类

    基于百度地图API在AI Stduio上的瓦片地图块图像处理与分类 项目介绍 本项目基于百度地图API获取了不同的瓦片地图并进行合并等处理,可用于遥感和抽象地图的地图块的图像分类.分割.检测等数据的制 ...

  8. 一个让echarts中国地图包含省市轮廓的技巧

    背景知识及应用简介 本文主要介绍一个使用ECharts地图组件的取巧方法,该技巧源于实际需求中遇到的问题,一般没有该需求的话这个技巧也是用不到的.有前端基础和以及对ECharts有了解的人基本可以读懂 ...

  9. 《数字图像处理与分析》结课作业去雾报告

    原创作品,出自 "晓风残月xj" 博客,欢迎转载,转载时请务必注明出处(http://blog.csdn.net/xiaofengcanyuexj). 由于各种原因,可能存在诸多不 ...

最新文章

  1. Django模拟新浪微博的@功能
  2. Spring boot的@Conditional派生注解
  3. 企业使用开源软件的风险
  4. LeetCode题——最长无重复子串
  5. 第七节:实战前必须掌握的10个指令(上)
  6. IE 7.0抛弃Win2000用户?(zz)
  7. 通过手机物理返回键实现弹出层的隐藏
  8. 贪心算法——找纸币问题
  9. Java 进阶:集合框架2
  10. tortoiseGit 解决冲突
  11. python tcl smb_Python——操作smb文件服务器(上传和下载)
  12. linux内核寻址,深入理解Linux内核-内存寻址
  13. 032-OpenCV模板匹配单个对象、多个对象
  14. spyder使用笔记
  15. 牛客网Verilog快速入门题目收获——异步复位的串联T触发器(VL2)
  16. 多功能雨伞项目计划书_多功能雨伞项目计划书_雨伞策划书范文
  17. Vue+TS Echarts股票图
  18. Excel使用right函数截断数据,并覆盖原来的数据
  19. 动物派对怎么修改服务器,《动物派对》联机方法教程攻略 如何联机
  20. 老王学java8周第一天

热门文章

  1. 哈哈,原来老酷比我的更新还要慢~~ 嘿嘿
  2. 生物碱基c语言编程,新的CRISPR C-G DNA碱基编辑器
  3. 在家网上娱乐,网上飙歌渐成流行,炎热夏季都市白领的轻松减压十种方法,...
  4. coreldraw x4怎么会蓝屏_Win10 1903系统电脑蓝屏怎么一键恢复
  5. 【技巧】绕过微信新设备登录时要求的好友辅助/扫码验证
  6. 淘宝找不到撤销退款申请_如何处理“纠纷退款率”,这些知识点你必须知道
  7. abaqus 6.14有限元分析高级培训视频教程-接触 热力 疲劳分析
  8. SVN commit E155010问题
  9. 为什么我不建议你给领导回复“收到”?
  10. VSCode + xUnit 编写 C# 单元测试