参考:多边形或轮廓等距离外扩或收缩_hjk61314的博客-CSDN博客_多边形外扩

1. 需求

已知:给定一个简单多边形,多边形按照顺时针或者逆时针的数组排列,

求:内部等距离缩小或者外部放大的多边形,该多边形实际上是由距离一系列平行已知多边形的边,并且距离为L(已知)的线段所构成的。如图所示,外围的是原多边形,内侧红色是新的多边形

2. 原理

数学描述:多边形的相邻两条边,L1和L2,交于Pi点,做平行于L1和L2,平行线间距是L的,并且位于多边形内部的两条边,交于Qi点,我们要计算出Qi的坐标,如图,

PiQi向量,显然是等于平行四边形的两个相邻边的向量v1和v2的和。

而v1和v2向量的方向,就是组成多边形的边的方向,可以用顶点差来表示。

v1和v2向量的长度是相同的,等于平行线间距L与两个线段夹角的sin值的除法。

即: Qi = Pi + (v1 + v2)

Qi = Pi + L / sinθ * ( Normalize(v2) + Normalize(v1))

Sin θ = |v1 × v2 | /|v1|/|v2| (叉乘的性质

其中,L / sinθ为平行四边形的边长(绿色线段,向量v1 v2对应的长度)

知识点:

1.向量a=(x,y)的模长等于\sqrt[2]{x^{2}+y^{2}}

2.向量积或者叫叉乘:

其运算结果仍是一个向量,我们记之为向量c,它的模定义为:

3.二维向量的叉积:

定义:叉乘是向量间的一种运算,设两个向量分别为(x_{1},y_{1}),(x_{2},y_{2}),那么它们的叉乘就为(x_{1}*y_{2}-x_{2}*y_{1})

计算步骤:

  • ⑴、获取多边形顶点数组PList;
  • ⑵、计算DPList[Vi+1-Vi];
  • ⑶、单位化NormalizeDPList,得到NDP[DPi];(用同一个数组存储)
  • ⑷、Sinα = Dp(i+1) X DP(i);
  • ⑸、Qi = Pi + d/sinα (NDPi+1-NDPi)
  • ⑹、这样一次性可以把所有顶点计算完。
    注意,交换Qi表达式当中NDPi+1-NDPi的顺序就可以得到外部多边形顶点数组。

2. 实现

#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm> 
#include <string>

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc.hpp>

#include <eigen3/Eigen/Eigen>

using namespace cv;
using namespace std;

void expand_polygon(vector<Point> &pList, vector<Point> &out){// already ordered by anticlockwise

    // 1. vertex set
    // pList

    // 2. edge set and normalize it
    vector<Point2f> dpList, ndpList;
    int count = pList.size();
    for(int i = 0; i < count; i++){
        int next = (i==(count-1) ? 0: (i+1));
        dpList.push_back(pList.at(next)-pList.at(i));
        float unitLen = 1.0f/sqrt(dpList.at(i).dot(dpList.at(i)));
        ndpList.push_back(dpList.at(i) * unitLen);
        cout<<"i="<<i<<",pList:"<<pList.at(next)<<","<<pList.at(i)<<",dpList:"<<dpList.at(i)<<",ndpList:"<<ndpList.at(i)<<endl;
    }

    // 3. compute Line
    float SAFELINE = 20.0f;//负数为内缩, 正数为外扩。 需要注意算法本身并没有检测内缩多少后折线会自相交,那不是本代码的示范意图
    for(int i = 0; i < count; i++){
        int startIndex = (i==0 ? (count-1):(i-1));
        int endIndex = i;
        float sinTheta = ndpList.at(startIndex).cross(ndpList.at(endIndex));
        Point2f orientVector = ndpList.at(endIndex) - ndpList.at(startIndex);//i.e. PV2-V1P=PV2+PV1
        Point2f temp_out;
        temp_out.x = pList.at(i).x + SAFELINE/sinTheta * orientVector.x;
        temp_out.y = pList.at(i).y + SAFELINE/sinTheta * orientVector.y;
        out.push_back(temp_out);
    }
    reverse(out.begin(),out.end());
    //cout<<endl<<"out:"<<out<<endl;

}


int TEST_expand_polygon() {

    // input a polygon clockwise
    vector<Point> points_src;
    points_src.push_back(Point(550,300)); //1
    points_src.push_back(Point(500,500)); //2

    points_src.push_back(Point(600,520)); //3
    points_src.push_back(Point(610,300)); //4


    // output a extened polygon clockwise
    vector<Point> points_expand;
    expand_polygon(points_src,points_expand);

    cv::Mat smoothed( 1000, 1000, CV_8UC3, Scalar(0) );

    line(smoothed,points_src[0],points_src[1],Scalar(255,0,0),1);
    line(smoothed,points_src[1],points_src[2],Scalar(255,0,0),1);
    line(smoothed,points_src[2],points_src[3],Scalar(255,0,0),1);
    line(smoothed,points_src[3],points_src[0],Scalar(255,0,0),1);
    imshow("smoothed",smoothed);

    Point vertex;
    for(int i=0; i<points_expand.size();i++){
        vertex.x = points_expand[i].x;
        vertex.y = points_expand[i].y;
        circle(smoothed,vertex,2,Scalar(0,255,0),1);
        ostringstream indexText;// or std::to_string()
        indexText << i;
        putText(smoothed,indexText.str(),vertex,cv::FONT_HERSHEY_DUPLEX, 0.5, cv::Scalar(0, 0,255 ), 1);
        int next = (i==(points_expand.size()-1) ? 0: (i+1));
        line(smoothed,points_expand[i],points_expand[next],Scalar(0,255,0),1);
    }

    imshow("smoothed",smoothed);

    waitKey();
    std::cout << "Hello, worldss!" << std::endl;
    return 0;
}


int main(int argc, char **argv) {

    TEST_expand_polygon();
    return 0;
}

3. 效果