Cohen-SutherLand算法
算法理论
算法总体思想是,对于每条线段P1P2P1P2,(1)若P1P2P1P2全在窗口内,取之;(2)若P1P2P1P2明显在窗口外,弃之;(3)若无法满足上述两条件,则把线段分为两段,窗口外的可弃之,剩下的重复处理。
为方便处理,将图形区按下表分类:
1001 | 1000 | 1010 |
0001 | 0000 | 0010 |
0101 | 0100 | 0110 |
于是每个点可以根据上图获得一个区域码。对于某个线段,若:
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)=(P2−P1)t+P1P(t)=(P2−P1)t+P1
对于凸多边形上的任意一点有: N∙(P(t)−A)>0N∙(P(t)−A)>0
则线段必定通过多边形的内侧。其中N是该点法向量。 故可见线段的参数区间为:
{N∙(P(t)−A)>00⩽t⩽1{N∙(P(t)−A)>00⩽t⩽1
实际计算时,只需要求出tmintmin和tmaxtmax,即可求出线段的两端点。上式可变为: {N∙((P2−P1)t+P1−A)>00⩽t⩽1{N∙((P2−P1)t+P1−A)>00⩽t⩽1
{N∙(P1−A)+N∙(P2−P1)>00⩽t⩽1{N∙(P1−A)+N∙(P2−P1)>00⩽t⩽1
当N∙(P2−P1)=0N∙(P2−P1)=0时,线段与对应边平行,当N∙(P1−A)>0N∙(P1−A)>0时线段与多边形相交。若与线段在多边形外,直接跳出循环;若线段在多边形内,则继续判断下一条边。 当N∙(P2−P1)≠0N∙(P2−P1)≠0时,令ti=Ni∙(P1−Ai)Ni∙(P2−P1)ti=Ni∙(P1−Ai)Ni∙(P2−P1),判定条件为:
⎧⎩⎨t⩾ti,当Ni∙(P2−P1)>0t⩽ti,当Ni∙(P2−P1)<00⩽t⩽1{t⩾ti,当Ni∙(P2−P1)>0t⩽ti,当Ni∙(P2−P1)<00⩽t⩽1
由于上述方法需要指定法向量的方向,与边上的任意点,实现起来比较麻烦,直接实现Liang-Barskey算法。Liang-Barskey将上述流程中的多边形特定为矩形,过程便简单了许多,如下表所示:
边 | 内法向量 | 边上一点 | P1−AP1−A | t=Ni∙(P1−Ai)Ni∙(P2−P1)t=Ni∙(P1−Ai)Ni∙(P2−P1) |
---|---|---|---|---|
x=XL | (1,0) | (XL,y) | (x1-XL,y1-y) | x1−XL−(x2−x1)x1−XL−(x2−x1) |
x=XR | (-1,0) | (XR,y) | (x1-XR,y1-y) | −(x1−XR)x2−x1−(x1−XR)x2−x1 |
y=YB | (0,1) | (x,YB) | (x1-x,y1-YT) | y1−YT−(y2−y1)y1−YT−(y2−y1) |
y=YT | (0,-1) | (x,YT) | (x1-x,y1-YB) | −(y1−YB)y2−y1−(y1−YB)y2−y1 |
算法实现
#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++的特性使然。