摘要

最近由于公司业务需求,需要实现对一个多边形进行等距收缩一定距离,其实现效果图大致如下:

虽然单点距离貌似收缩情况不太正确,不过看起来还是蛮顺眼的。反正领导看到一眼就说他要这样的,好嘞,小二马上给你搞。由于OpenCv针对于图形处理具有较多优秀的算法,并且当时找到一篇收缩算法正是用这个玩意做的,于是参考该作者的C++代码,地址:https://blog.csdn.net/dcrmg/article/details/52517991,实现了一版Java对应的版本。由于OpenCv是C++函数库,并且对于Java的Api并没有太多的中文资料,所以本就菜鸟的作者也是花费不少时间才搞出来,所以在介绍收缩之前需安装OpenCv相关环境,可以参考本人博客:安装OpenCv环境。

注意事项:其实安装OpenCv不管是在windows、MacOs还是Linux环境下都并不难,只需要两部分即可:1、OpenCv本身的C++函数库。2、OpenCv对于Java支持的Jar。

七夕大家都回家玩游戏陪女朋友,我特码还在公司看代码,还写个鸡毛博客,眼泪不争气地流了下来,算了没心情了,待更吧……


续更……
哈哈哈,经过几天的恢复,我胡汉三又回来啦,先把收缩相关代码贴出来给大伙尝尝鲜。

package com.lanxuewei.utils.opencv;import static org.opencv.core.CvType.CV_32FC1;
import static org.opencv.core.CvType.CV_8UC1;
import static org.opencv.imgproc.Imgproc.*;import java.awt.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;import com.lanxuewei.utils.json.AiResultJsonModel;
import org.opencv.core.*;
import org.opencv.core.Point;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** @author lanxuewei Create in 2018/7/30 17:29* Description: 多边形等距收缩(需使用openCv,所以需要在支持openCv环境下使用) */
public class PolygonScaleHelper {static {System.loadLibrary(Core.NATIVE_LIBRARY_NAME);               // 加载opencv的库}private static final Logger logger = LoggerFactory.getLogger(PolygonScaleHelper.class);/** x、y坐标最大最小值常量字符串 */private static final String XMax = "xMax";private static final String XMin = "xMin";private static final String YMax = "yMax";private static final String YMin = "yMin";/** List<Double>坐标中xy对应下标志 */private static final int X = 0;private static final int Y = 1;/** 平移时距离边框留白距离 */private static int PADDING = 10;/*** 更新多边形(收缩)** @param points 多边形坐标集* @param scale 收缩比*/public static List<List<Double>> updatePolygonByScale(List<List<Double>> points, float scale) {/*** 1.获取xy最大最小值* 2.平移坐标* 3.矩阵变换* 4.平移坐标*/List<List<Double>> resultPoints = new ArrayList<>();if (points != null && points.size() > 0) {                  // 坐标不为空才进行处理List<MatOfPoint> matOfPoints = new ArrayList<>();       // 将List<Point>转化为List<MatOfPoint>MatOfPoint matOfPoint = new MatOfPoint();/*** 1.获取xy最大最小值* 2.平移坐标* 3.矩阵变换* 4.平移坐标*/Map<String, Double> maxAndMinValue = getMaxAndMinValueByPoints(points);  // 获得xy坐标中最小xy以及最大xytranslation(points, maxAndMinValue, true);   // 靠近原点坐标平移——用于减少计算过程中矩阵大小,减少内存开销,但变换之后坐标需要平移回来int size = points.size();    // 获得坐标数量matOfPoint.alloc(size);for (int i = 0; i < size; i++) {List<Double> onePoint = points.get(i);matOfPoint.put(i, 0, onePoint.get(X), onePoint.get(Y));}double width = maxAndMinValue.get(XMax) - maxAndMinValue.get(XMin); // 最大需要矩阵大小double height = maxAndMinValue.get(YMax) - maxAndMinValue.get(YMin);Mat mat = Mat.zeros(new Size(width + PADDING*2, height + PADDING*2),CV_8UC1);   // 初始化细化后的字符轮廓matOfPoints.add(matOfPoint);fillPoly(mat, matOfPoints, new Scalar(255));                        // 将多边形填充为矩阵matList<List<Double>> scaledPoints = scaleNoduleFromMask(mat, scale);  // 对矩阵进行等距收缩if (scaledPoints != null && scaledPoints.size() > 2) {              // 收缩成功translation(scaledPoints, maxAndMinValue, false);               // 远离原点平移——还原之前为了减少内存开销的平移坐标操作resultPoints = scaledPoints;                                    } else {translation(points, maxAndMinValue, false);resultPoints = points;}}return resultPoints;}/*** 多边形等距收缩** @param mask 原矩阵* @param scale 缩放比* @return 缩放后坐标*/private static List<List<Double>> scaleNoduleFromMask(Mat mask, float scale) {if (scale <= 0 || scale >= 1) {     // 收缩比例范围检查logger.error("param scale invalid");return null;}if(mask.empty()) {                  // mask为空检查logger.error("mask file is empty");return null;}double maxValue = 0;Mat imageThin = new Mat(mask.size(), CV_32FC1);     // 定义保存距离变换结果的Mat矩阵distanceTransform(mask, imageThin, CV_DIST_L2,3);   // 矩阵变换Mat distTransf;distTransf = Mat.zeros(mask.size(),CV_8UC1);        // 定义细化后的字符轮廓for(int i = 0; i < imageThin.rows(); i++) {for(int j = 0; j < imageThin.cols(); j++) {double temp = imageThin.get(i, j)[0];if (temp > maxValue) {maxValue = temp;        // 获取距离变换的极大值}}}double maxDistance = maxValue * scale;for(int i = 0; i < imageThin.rows(); i++) {for(int j = 0; j < imageThin.cols(); j++) {if(imageThin.get(i,j)[0] > maxDistance) {distTransf.put(i, j, 255);}}}List<MatOfPoint> matOfPoints = new ArrayList<>();Mat hierarchy = new Mat();final int CV_CHAIN_APPROX_TC89_L1 = 3;findContours(distTransf, matOfPoints, hierarchy, RETR_EXTERNAL, CV_CHAIN_APPROX_TC89_L1);List<List<Double>> scaledPoints = new ArrayList<>();  // 收缩以后多边形的坐标for (int i = 0; !matOfPoints.isEmpty() && i < matOfPoints.size(); i++){List<Point> points = matOfPoints.get(i).toList();for (int j = 0; !points.isEmpty() &&  j < points.size(); j++){List<Double> point = new ArrayList<>();      // 一个坐标point.add(points.get(j).x);point.add(points.get(j).y);scaledPoints.add(point);                     // 加入坐标集}}return scaledPoints;}/*** 获取集合中最小以及最大的x和y** @param edges 坐标集* @return 坐标集中x、y最小值以及最大值*/public static Map<String, Double> getMaxAndMinValueByPoints(List<List<Double>> edges) {if (edges == null || edges.size() == 0) {return null;}Map<String, Double> maxAndMinValue = new HashMap<>();List<Double> firstPoint = edges.get(0); // 获得第一个坐标if (firstPoint != null) {double xMax = firstPoint.get(X);   // 第一个坐标xy作为最大最小初始值double xMin = firstPoint.get(X);double yMax = firstPoint.get(Y);double yMin = firstPoint.get(Y);for (int i = 1; i < edges.size(); i++) {List<Double> point = edges.get(i);  // 当前坐标double xTemp = point.get(X);        // 暂存当前xy变量double yTemp = point.get(Y);// xif (xTemp > xMax) {     // 求所有x最大值xMax = xTemp;}if (xTemp < xMin) {     // 求所有x最小值xMin = xTemp;}// yif (yTemp > yMax) {     // 求所有y最大值yMax = yTemp;}if (yTemp < yMin) {     // 求所有y最小值yMin = yTemp;}}maxAndMinValue.put(XMax, xMax);   // 将最大最小值加入集合maxAndMinValue.put(XMin, xMin);maxAndMinValue.put(YMax, yMax);maxAndMinValue.put(YMin, yMin);}return maxAndMinValue;}/*** 平移坐标点** @param edges 待平移坐标点* @param maxAndMinValMap xMin、yMin 为xy平移距离* @param isCloseToOrigin 是否朝着原点位置平移*/public static void translation(List<List<Double>> edges, Map<String, Double> maxAndMinValMap, boolean isCloseToOrigin) {if (edges != null && maxAndMinValMap != null) {Double xMin = maxAndMinValMap.get(XMin);       // 获取xy平移距离Double yMin = maxAndMinValMap.get(YMin);if (xMin != null && yMin != null) {if (isCloseToOrigin) {    // 靠近原点平移坐标for (int i = 0; i < edges.size(); i++) {List<Double> point = edges.get(i);             // 获取当前坐标point.set(X, point.get(X) - xMin + PADDING);   // 更新坐标,xMin和yMin为移动距离,PADDING为保持与边界距离point.set(Y, point.get(Y) - yMin + PADDING);}} else {                 // 远离原点平移坐标for (int i = 0; i < edges.size(); i++) {List<Double> point = edges.get(i);  // 获取当前坐标point.set(X, point.get(X) + xMin - PADDING);point.set(Y, point.get(Y) + yMin - PADDING);}}}}}/*** 多边形转化为List<List<Double>>坐标** @param polygon 多边形* @return*/public static List<List<Double>> polygonToPoints(Polygon polygon) {List<List<Double>> resultPoints = new ArrayList<>();if (polygon == null) {return resultPoints;}for (int i = 0; i < polygon.npoints; i++) {List<Double> point = new ArrayList<>();point.add((double) polygon.xpoints[i]);point.add((double) polygon.ypoints[i]);resultPoints.add(point);}return resultPoints;}/*** List<List<Double>>坐标转化为多边形** @param points 坐标点集* @return*/public static Polygon PointsToPolygon(List<List<Double>> points) {Polygon polygon = new Polygon();if (points == null) {return polygon;}for (List<Double> point : points) {double x = point.get(0);double y = point.get(1);polygon.addPoint((int) x,(int) y);}return polygon;}}

相关测试代码如下:

package com.lanxuewei.utils.data;import java.awt.*;
import java.util.List;import javax.swing.*;import com.lanxuewei.utils.opencv.PolygonScaleHelper;public class Test{public static void main(String[] args) {EventQueue.invokeLater(() -> {// 创建窗口对象MyFrame frame = new MyFrame();// 显示窗口frame.setVisible(true);});}/*** 窗口*/public static class MyFrame extends JFrame {public static final String TITLE = "Java图形绘制";public static final int WIDTH = 250;public static final int HEIGHT = 300;public MyFrame() {super();initFrame();}private void initFrame() {// 设置 窗口标题 和 窗口大小setTitle(TITLE);setSize(WIDTH, HEIGHT);// 设置窗口关闭按钮的默认操作(点击关闭时退出进程)setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);// 把窗口位置设置到屏幕的中心setLocationRelativeTo(null);// 设置窗口的内容面板MyPanel panel = new MyPanel(this);setContentPane(panel);}}/*** 内容面板*/public static class MyPanel extends JPanel {private MyFrame frame;public MyPanel(MyFrame frame) {super();this.frame = frame;}@Overrideprotected void paintComponent(Graphics g) {super.paintComponent(g);drawRect(g);}/*** 矩形 / 多边形*/private void drawRect(Graphics g) {frame.setTitle("矩形 / 多边形");Graphics2D g2d = (Graphics2D) g.create();g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);g2d.setColor(Color.GRAY);Polygon polygon = new Polygon();    // 设置多边形坐标polygon.addPoint(100, 100);polygon.addPoint(150, 150);polygon.addPoint(200, 230);polygon.addPoint(240, 210);polygon.addPoint(220, 80);g2d.draw(polygon);List<List<Double>> points = PolygonScaleHelper.polygonToPoints(polygon);    // 多边形转化为集合类型坐标集float scale = 0.3f;  // 收缩比0.3List<List<Double>> scalePoints = PolygonScaleHelper.updatePolygonByScale(points, scale);Polygon scalePolygon = PolygonScaleHelper.PointsToPolygon(scalePoints);     // 集合类型坐标集转化为多边形g2d.draw(scalePolygon);g2d.dispose();}}
}

部分内容以及踩坑说明如下:

1、收缩以及整个处理过程中坐标点集采用model为List<List<Double>>类型,而不采用List<Point>是因为业务需求,不过多解释,需要修改的朋友可以自行修改为自己需要的结构类型,本文中采取的List<Double>,下标0为x,1为y处理。

2、 收缩之前会将所有的坐标进行一定的位置平移,收缩后又平移回去,考虑因素是因为之前业务中是对于一张图片上某些坐标点进行收缩,收缩过程中需要借助矩阵,图片大小(一般512*512)直接作为矩阵大小当然可以,但是为了减少矩阵大小,就对所有坐标进行一定的平移处理,减少内存开销。又因为处理过程中需要找到坐标点外一些非0点,所以矩阵大小设置为(maxX-minX)+padding * (maxY-minY)+padding,这里padding设置为10。

3、Mat.zeros函数功能为初始化一个所有元素为0的矩阵,别小看这个函数,当时本人被它坑惨了,因为Opencv是C++实现的一个函数库,而C++语言中,类似于创建数组之后,是需要对其进行初始化的。但是Java习惯却是不需要初始化!!!习惯性思维导致本人一开始并没有调用该函数进行初始化,而是直接构造,导致查错半天无果,一脸懵逼。

4、 该收缩方法大概思想就是:将坐标点采用一个0和255的矩阵进行填充出多边形,计算出收缩距离变换的极大值,然后初始化另一个同样大小所有元素为0的矩阵,比较所有坐标点与极大值大小,将大于极大值部分都设置为255,再将矩阵转化为坐标点即可。

5、 由于可能坐标点间距离非常小,在一个或两个单位之间,所以可能导致收缩过后坐标点只剩一个或者两个重合的情况,遂最后进行判断,收缩失败则还原坐标。

总结:历经多时,总算是把这篇博客写好了,虽说最近因为一些其他事耽搁了,不过还是得改改严重的拖延症,行动起来,go go go……

采用OpenCv——多边形等距收缩实现相关推荐

  1. opencv多边形轮廓等距缩放

    opencv多边形按像素放大或缩小,可用于缩放提取后的轮廓 代码示例如下: #!/usr/bin/env python # -*- encoding: utf-8 -*- ''' @File : 多边 ...

  2. JAVA实现小精度多边形等距外扩

    涉及技术 腾讯地图 jdk1.8 问题描述 开发一款应用,前端在地图上标记经纬度坐标点集合形成一个多边形,现需要在后端将多边形做一个等距外扩或者收缩. 在网上查了好多的算法,经过尝试验证,在小经度多边 ...

  3. 采用OpenCV和深度学习的钢印识别

    采用OpenCV和深度学习的钢印识别 [这个帖子标题党了很久,大概9月初立贴,本来以为比较好做,后来有事情耽搁了,直到现在才有了一些拿得出手的东西.肯定不会太监的.好,转入正题:] 原始需求: 系统将 ...

  4. 相机标定的理解及采用opencv和matlab工具箱的标定方法

    一.相机标定的目的 确定空间物体表面某点的三维几何位置与其在图像中对应点之间的相互关系,建立摄像机成像的几何模型,这些几何模型参数就是摄像机参数. 二.通用摄像机模型 世界坐标系.摄像机坐标系和像平面 ...

  5. matlab相机标定工具箱进行相机标定,相机标定的理解及采用opencv和matlab工具箱的标定方法...

    一.相机标定的目的 确定空间物体表面某点的三维几何位置与其在图像中对应点之间的相互关系,建立摄像机成像的几何模型,这些几何模型参数就是摄像机参数. 二.通用摄像机模型 世界坐标系.摄像机坐标系和像平面 ...

  6. 基于python OpenCV多边形图像识别的实现

    首先说一下我的整体思路: ① 首先定义了一个识别器类型,封装了计算边长,识别形状和展示结果三个函数. ② 主函数先读入图片,然后将图片转化为灰度图片,然后高斯滤波平滑处理,然后将灰度图片转化为黑白两色 ...

  7. 多边形等距放缩原理与python实现

    版权声明:本文为博主原创文章,转载请附上原文出处链接和本声明. 参考:https://blog.csdn.net/happy__888/article/details/315762 在日常图像处理中, ...

  8. 采用 opencv surf 算子进行特征匹配

    opencv docu 源码在git 目录结构 . ├── build ├── CMakeLists.txt ├── main.cpp ├── t1.jpg └── t2.jpg /** @file ...

  9. opencv 多边形近似物体形状

    前面我们学习过最小外接矩和最小外接圆,那么可以用一个最小的多边形包围物体吗?当然可以: 其中 cv.approxPolyDP() 的参数1是源图像的某个轮廓:参数2(epsilon)是一个距离值,表示 ...

最新文章

  1. 今晚8点直播 | 详解聊天机器人落地及进阶实战
  2. ubuntu 16.04 更新后搜狗输入法无法输入中文的问题
  3. 算法---字符串去重
  4. 汇编 加法减法指令 inc dec add sub neg 标志寄存器测试
  5. 2016二级c语言笔试内容,2016年计算机二级c语言笔试试题「最新」
  6. 与 SENet 互补提升,华为提出自注意力新机制:Weight Excitation
  7. matlab 计算误码率,关于误码率的问题 急!!!!!
  8. 简单解决“ORA-27100: shared memory realm already exists”的问题
  9. 百度云:centos7.0+ 安装宝塔与ShopXO开源商城(从0搭建到部署上线) - 教程篇
  10. C++:定义头文件/定义命名空间
  11. 学习MySQL:使用SELECT语句从MySQL服务器查询数据
  12. Ps 初学者教程,如何在产品照片中改变对象颜色?
  13. cs5460a c语言程序,CS5463程序,有图有程序,大虾来看看,欢迎拍砖!
  14. CSS中强大的EM(转)
  15. 今天有空,不如来找找“双鸭山大学”的由来吧~
  16. 浅谈润乾报表与QlikView对比
  17. 第二次去苹果店维修MacBook
  18. 计算机毕业设计Java短视频交流点播系统(源码+系统+mysql数据库+lw文档)
  19. 美国零售业发展强劲:2019愿景向好?
  20. NP-Completeness(NP完全问题)

热门文章

  1. 推荐一个快速证件照换底色的工具超级好用
  2. 内存泄漏分析valgrind
  3. 修改input默认样式
  4. java查询枚举_Java枚举的反向查找
  5. 京东亚马孙遭遇售后囧途吗
  6. PDF编辑技巧1:添加页码和添加背景图片
  7. 数据结构系列:稀松数组分析,及代码实现
  8. 关键字volatile有什么含意?并给出三个不同的例子
  9. 网站优化的几个关键词?pv uv pr
  10. 2021年施工员-装饰方向-通用基础(施工员)考试及施工员-装饰方向-通用基础(施工员)免费试题