裁剪算法

Cohen-SutherLand算法

算法理论

算法总体思想是,对于每条线段 P1P2 P 1 P 2 ,(1)若 P1P2 P 1 P 2 全在窗口内,取之;(2)若 P1P2 P 1 P 2 明显在窗口外,弃之;(3)若无法满足上述两条件,则把线段分为两段,窗口外的可弃之,剩下的重复处理。
为方便处理,将图形区按下表分类:

100110001010
000100000010
010101000110

于是每个点可以根据上图获得一个区域码。对于某个线段,若:
code1=code2=0,则线段在窗口内
(code1&code2)!=0,则线段在窗口的同侧,线段在窗口外
否则即为第三种情况。则将窗口外的端点与边界求交点,替换原有交点,继续计算。

中点分割算法的理论与上述算法基本类似,就是求线段端点的方法不同,使用二分法寻找边界点,而非直接计算。

算法实现

#include<iostream>
#include<algorithm>
#include<opencv2\core\core.hpp>
#include<opencv2\highgui\highgui.hpp>
#include"MyLine.h"

#define LEFT 1
#define RIGHT 2
#define BOTTOM 4
#define TOP 8
#define WIDTH 1000
#define HEIGHT 500

using namespace std;
using namespace cv;

int xl, xr, yb, yt;

void DDALine(Mat& m, const int x0, const int y0, const int x1, const int y1, const cv::Vec3b& v);
int Encode(int x, int y);
void Cohen_SutherLand(Mat& m, MyLine& L);

int main(){
    vector<MyLine> lv;
    int n,i;
    int x1, y1, x2, y2;
    cout << "please input the number of lines" << endl;
    cin >> n;
    for (i = 1; i <= n; i++){
        cout << "please input the coordinates of the vertexes of the No." << i << " line" << endl;
        cin >> x1 >> y1 >> x2 >> y2;
        lv.push_back(MyLine(MyPoint(x1, y1), MyPoint(x2, y2)));
    }
    cout << "please input the border of the screen, xl, xr, yb, yt" << endl;
    cin >> xl >> xr >> yb >> yt;
    Mat imageROI = Mat(HEIGHT, WIDTH, CV_8UC3, Scalar(255, 255, 255));
    for (i = 0; i < HEIGHT; i++){
        imageROI.at<Vec3b>(i, xl) = Vec3b(0, 0, 0);
        imageROI.at<Vec3b>(i, xr) = Vec3b(0, 0, 0);
    }
    for (i = 0; i < WIDTH; i++){
        imageROI.at<Vec3b>(yb, i) = Vec3b(0, 0, 0);
        imageROI.at<Vec3b>(yt, i) = Vec3b(0, 0, 0);
    }//draw border
    //Cohen-SutherLand
    vector<MyLine>::iterator iter;
    for (iter = lv.begin(); iter != lv.end(); iter++){
        Cohen_SutherLand(imageROI, *iter);
    }
    namedWindow("显示结果");
    imshow("显示结果", imageROI);
    waitKey();
}

int Encode(int x, int y){
    int code=0;
    if (x < xl){
        code = code | LEFT;
    }
    else if (x>xr){
        code = code | RIGHT;
    }
    if (y < yb){
        code = code | BOTTOM;
    }
    else if (y>yt){
        code = code | TOP;
    }
    return code;
}

void Cohen_SutherLand(Mat& m, MyLine& L){
    int code1, code2, code;
    float x, y, x1, y1, x2, y2;
    x1 = L.getP1().GetX();
    y1 = L.getP1().GetY();
    x2 = L.getP2().GetX();
    y2 = L.getP2().GetY();
    code1 = Encode(x1, y1);
    code2 = Encode(x2, y2);
    while (code1 != 0 || code2 != 0){
        if ((code1&code2) != 0){
            return;
        }
        code = code1;
        if (code == 0){
            code = code2;
        }
        if ((LEFT&code) != 0){
            x = xl;
            y = y1 + (y2 - y1)*(xl - x1) / (x2 - x1);
        }
        else if ((RIGHT&code) != 0){
            x = xr;
            y = y1 + (y2 - y1)*(xr - x1) / (x2 - x1);
        }
        if ((BOTTOM&code) != 0){
            y = yb;
            x = x1 + (x2 - x1)*(yb - y1) / (y2 - y1);
        }
        else if ((TOP&code) != 0){
            y = yt;
            x = x1 + (x2 - x1)*(yt - y1) / (y2 - y1);
        }
        if (code == code1){
            x1 = x;
            y1 = y; 
            code1 = Encode(x, y);
        }
        else{
            x2 = x;
            y2 = y;
            code2 = Encode(x, y);
        }
    }
    DDALine(m, x1, y1, x2, y2, Vec3b(0, 0, 0));
    return;
}

结果:这里写图片描述
算法总体比较简单,就是!=与&的优先级需要注意一下。

Liang-Barskey算法

算法理论

首先需要介绍Cyrus-Beck算法,可以对多边形区域进行裁剪。对于某线段,用参数方程表示为:

P(t)=(P2P1)t+P1 P ( t ) = ( P 2 − P 1 ) t + P 1
对于凸多边形上的任意一点有:
N(P(t)A)>0 N ∙ ( P ( t ) − A ) > 0
则线段必定通过多边形的内侧。其中N是该点法向量。
故可见线段的参数区间为:
{N(P(t)A)>00t1 { N ∙ ( P ( t ) − A ) > 0 0 ⩽ t ⩽ 1
实际计算时,只需要求出 tmin t m i n tmax t m a x ,即可求出线段的两端点。上式可变为:
{N((P2P1)t+P1A)>00t1 { N ∙ ( ( P 2 − P 1 ) t + P 1 − A ) > 0 0 ⩽ t ⩽ 1
{N(P1A)+N(P2P1)>00t1 { N ∙ ( P 1 − A ) + N ∙ ( P 2 − P 1 ) > 0 0 ⩽ t ⩽ 1
N(P2P1)=0 N ∙ ( P 2 − P 1 ) = 0 时,线段与对应边平行,当 N(P1A)>0 N ∙ ( P 1 − A ) > 0 时线段与多边形相交。若与线段在多边形外,直接跳出循环;若线段在多边形内,则继续判断下一条边。
N(P2P1)0 N ∙ ( P 2 − P 1 ) ≠ 0 时,令 ti=Ni(P1Ai)Ni(P2P1) t i = N i ∙ ( P 1 − A i ) N i ∙ ( P 2 − P 1 ) ,判定条件为:
tti,Ni(P2P1)>0tti,Ni(P2P1)<00t1 { t ⩾ t i , 当 N i ∙ ( P 2 − P 1 ) > 0 t ⩽ t i , 当 N i ∙ ( P 2 − P 1 ) < 0 0 ⩽ t ⩽ 1

由于上述方法需要指定法向量的方向,与边上的任意点,实现起来比较麻烦,直接实现Liang-Barskey算法。Liang-Barskey将上述流程中的多边形特定为矩形,过程便简单了许多,如下表所示:

内法向量边上一点 P1A P 1 − A t=Ni(P1Ai)Ni(P2P1) t = N i ∙ ( P 1 − A i ) N i ∙ ( P 2 − P 1 )
x=XL(1,0)(XL,y)(x1-XL,y1-y) x1XL(x2x1) x 1 − X L − ( x 2 − x 1 )
x=XR(-1,0)(XR,y)(x1-XR,y1-y) (x1XR)x2x1 − ( x 1 − X R ) x 2 − x 1
y=YB(0,1)(x,YB)(x1-x,y1-YT) y1YT(y2y1) y 1 − Y T − ( y 2 − y 1 )
y=YT(0,-1)(x,YT)(x1-x,y1-YB) (y1YB)y2y1 − ( y 1 − Y B ) y 2 − y 1

算法实现

#include<iostream>
#include<algorithm>
#include<opencv2\core\core.hpp>
#include<opencv2\highgui\highgui.hpp>
#include"MyLine.h"
#include"MyVector.h"

#define WIDTH 1000
#define HEIGHT 500
#define XL 100
#define XR 800
#define YB 50
#define YT 400

using namespace std;
using namespace cv;

void DDALine(Mat& m, int x0, int y0, int x1, int y1, const cv::Vec3b& v);
void Liang_Barskey(Mat& m, MyPoint& P1, MyPoint& P2);
bool NoReject(double denom, double nom, double& tl, double& tu);

int main(){
    vector<MyLine> lv;
    int n, i, j;
    int x1, y1, x2, y2;
    cout << "please input the number of lines" << endl;
    cin >> n;
    for (i = 1; i <= n; i++){
        cout << "please input the coordinates of the vertexes of the No." << i << " line" << endl;
        cin >> x1 >> y1 >> x2 >> y2;
        lv.push_back(MyLine(MyPoint(x1, y1), MyPoint(x2, y2)));
    }
    Mat imageROI = Mat(HEIGHT, WIDTH, CV_8UC3, Scalar(255, 255, 255));
    for (i = 0; i < HEIGHT; i++){
        imageROI.at<Vec3b>(i, XL) = Vec3b(0, 0, 0);
        imageROI.at<Vec3b>(i, XR) = Vec3b(0, 0, 0);
    }
    for (i = 0; i < WIDTH; i++){
        imageROI.at<Vec3b>(YB, i) = Vec3b(0, 0, 0);
        imageROI.at<Vec3b>(YT, i) = Vec3b(0, 0, 0);
    }//draw border
    vector<MyLine>::iterator iter;
    for (iter = lv.begin(); iter != lv.end(); iter++){
        Liang_Barskey(imageROI, (*iter).getP1(), (*iter).getP2());
    }
    namedWindow("显示结果");
    imshow("显示结果", imageROI);
    waitKey();
}

void Liang_Barskey(Mat& m, MyPoint& P1, MyPoint& P2){
    double tl = 0, tu = 1;
    double tmin, tmax;
    MyVector v = MyVector(P1, P2);
    double dx = v.GetX();
    double dy = v.GetY();
    MyPoint Pmin, Pmax;
    if (NoReject(-dx, P1.GetX() - XL, tl, tu) && NoReject(dx, XR - P1.GetX(), tl, tu) && NoReject(-dy, P1.GetY() - YB, tl, tu) && NoReject(dy, YT - P1.GetY(), tl, tu)){
        tmin = tl;
        tmax = tu;
        Pmin = P1 + (v*tmin);
        Pmax = P1 + (v*tmax);
        DDALine(m, Pmin.GetX(), Pmin.GetY(), Pmax.GetX(), Pmax.GetY(),Vec3b(0,0,0));
    }
    return;
}

bool NoReject(double denom, double nom, double& tl, double& tu){
    float t;
    if (denom < 0){
        t = nom / denom;
        if (t>tu)
            return false;
        else if (t > tl)
            tl = t;
    }
    else if (denom > 0){
        t = nom / denom;
        if (t < tl)
            return false;
        else if (t < tu)
            tu = t;
    }
    else if (nom < 0)
        return false;
    else
        return true;
}

其中运算符重载部分省略。
结果:这里写图片描述
这次感觉C++很多坑还是要注意。一些地方为了利用引用避免拷贝构造,却没注意变量已经析构了,还有头文件互相引用等问题。感觉写起来还是很乱,不知道是不是C++的特性使然。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值