区域填充算法

由于做调研报告和过年,耽搁了一段时间,赶紧补上。惭愧惭愧。

多边形域的填充

算法理论

对于一般多边形,对一条扫描线,可以分为四个步骤:
1. 求交。求扫描线与多边形各边的交点。
2. 排序。交点按递增的顺序排序。
3. 交点配对。将各个交点配对,每对交点代表一个相交区间。
4. 区间填色。
对于扫描线与顶点相交的情况,当共享交点的两条边在扫描线的两边时,交点只算一个。当共享交点的两条边在扫描线的同一边时,交点作为0个或2个。在此规定,两顶点均高于扫描线时,取2个交点,顶点需要填充;两顶点均低于扫描线时,取0个交点,顶点不填充。
对于边界像素,规定落在右/上边界的像素不予填充,落在左下边界的像素予以填充。
即保证下闭上开,左闭右开。
再对上述四个步骤进行讨论。
求交点,活性边表的效率较高,可以利用上一条扫描线的活性边表构造下一条扫描线的活性边表。设边的直线方程为:

ax+by+c=0 a x + b y + c = 0
对于 i+1 i + 1 点
xi+1=(byi+1C)/a=xib/a x i + 1 = ( − b y i + 1 − C ) / a = x i − b / a
Δx=b/a Δ x = − b / a 为常量。
故活性边中的结点包括:
x:线 x : 当 前 扫 描 线 与 边 的 交 点
Δx:x Δ x : x 的 增 量
ymax:线 y m a x : 边 所 交 的 最 高 扫 描 线 号

由当前扫描线到下一条扫描线,只有个别结点需要调整,故采用冒泡排序;下一条扫描线新交的边,采用插入排序最适宜。如果有不再相交的边,需要删除出去。
为了方便活性边表的建立与更新,为每个扫描线建立一个新边表,存放在该扫描线第一次出现的边,即存放在 ymin y m i n 的新边表中。新边表每个结点存放对应边的初始信息,包括 x,Δx,ymax x , Δ x , y m a x 等。
填色只需要令b在多边形内时取真,在多边形外时取假,每访问一个节点把b取反一次。一开始置为假,若b为真则进行填色。

算法实现

main.cpp

#include
#include
#include
#include"Polygon.h"
#include
#include"EdgeTable.h"

#define width 1000
#define height 500

using namespace cv;
using namespace std;

void BubbleSort(vector& row);
void InsertionSort(vector& row, vector& newrow);
void DrawRow(Mat& m, vector& row, int y);
void RefreshRow(vector<vector>& table, int y);

int main(){
    Mat imageROI = Mat(height, width, CV_8UC3, Scalar(255, 255, 255));
    int n,i,j;
    cout << "Please input the number of edges" << endl;
    cin >> n;
    Polygon Poly(n);
    cout << "Please input the coordinate of the vertexes" << endl;
    Poly.Initialize();//definition of the polygon

    EdgeTable NET(height+1);//initialization of new edge table
    for (i = 0; i < Poly.PointTable.size(); i++){
        if (i + 1 < Poly.PointTable.size())
            j = i + 1;
        else
            j = 0;//to get the index of the points, and form a line
        double x0 = Poly.PointTable[i].GetX();
        double x1 = Poly.PointTable[j].GetX();
        int y0 = Poly.PointTable[i].GetY();
        int y1 = Poly.PointTable[j].GetY();
        int ymin = min(y0, y1);
        int ymax = max(y0, y1);
        double x;//x is the x coordinate of the ymin!
        if (ymin == y0){
            x = x0;
        }
        else{
            x = x1;
        }
        double deltax = double(-(x1 - x0)) / (y0 - y1);
        NET.table[ymin].push_back({ x, deltax, ymax });
    }//put edges of ymin=i into new edge table

    int y = 0;//y=min scanner
    EdgeTable AET(height+1);//initialization of active edge table
    for (i = 0; i < height; i++){
        BubbleSort(AET.table[i]);//sort the active table
        InsertionSort(AET.table[i], NET.table[i]);//insert new edge into the active table, and sort
        DrawRow(imageROI,AET.table[i],i);//draw the line
        RefreshRow(AET.table,i);//erase nodes that y=ymax, and insert the row into next line
    }

    namedWindow("显示结果");
    imshow("显示结果", imageROI);
    waitKey();
}

void BubbleSort(vector& row){
    EdgeNode tmp;
    int i,j;
    for (i = row.size() - 1; i >= 0; i--){
        for (j = 0; j < i; j++){
            if (row[j].x > row[j+1].x){
                tmp = row[j];
                row[j] = row[j+1];
                row[j+1] = tmp;
            }
        }
    }
    return;
}

void InsertionSort(vector& row, vector& newrow){
    int i, j;
    EdgeNode tmp;
    i = row.size();
    row.insert(row.end(), newrow.begin(), newrow.end());
    for (; i for (j = i; j > 0&&tmp.x1].x; j--){
            row[j] = row[j-1];
        }
        row[j] = tmp;
    }
}

void DrawRow(Mat& m,vector& row,int y){
    bool NeedFill = false;
    int x1 = 0;
    int x2 = 0;
    vector::iterator iter;
    for (iter = row.begin(); iter != row.end(); iter++){
        x1 = x2;
        x2 = (*iter).x;
        if (NeedFill == true){
            for (int x = x1; x <= x2; x++){
                m.at(y, x) = Vec3b(0, 0, 0);
            }
        }
        NeedFill = !NeedFill;
    }
}

void RefreshRow(vector<vector>& table, int y){
    vector::iterator iter;
    vector row;
    EdgeNode tmp;
    if (y <= height){
        row = table[y];
        for (iter = row.begin(); iter != row.end();){
            if ((*iter).ymax == y){
                iter=row.erase(iter);
            }
            else{
                (*iter).x += (*iter).deltax;
                iter++;//the travesal method of vector when erase() used
            }
        }
        table[y + 1] = row;
        return;
    }
}

EdgeTable.h

#pragma once
#include

struct EdgeNode{
    double x;
    double deltax;
    int ymax;
};

class EdgeTable
{
public:
    EdgeTable(int n);
    ~EdgeTable();
    std::vector<std::vector> table;
};

EdgeTable.cpp

#include "EdgeTable.h"

EdgeTable::EdgeTable(int n)
{
    table = std::vector<std::vector>(n);
}
EdgeTable::~EdgeTable()
{
}

Polygon.h

#pragma once
#include
#include
#include
class MyPoint{
public:
    MyPoint() :x(0), y(0){};
    MyPoint(int a, int b) :x(a), y(b){};
    void SetX(int X){ x = X; };
    void SetY(int Y){ y = Y; };
    int GetX(){ return x; };
    int GetY(){ return y; };
private:
    int x;
    int y;
};

class Polygon
{
public:
    Polygon(int n) :n(n){};
    ~Polygon(){};
    void Initialize();
    std::vector PointTable;
private:
    int n;
};

Polygon.cpp

#include "Polygon.h"
#include
void Polygon::Initialize(){
    int i, x, y;
    for (i = 0; i < n; i++){
        std::cin >> x >> y;
        PointTable.push_back(MyPoint(x, y));
    }
}

结果:
区域填充算法_第1张图片
写得还是有点费劲。主要问题在于vector的使用还不熟悉,一开始甚至想自己写链表。其他如传引用还是拷贝构造,遍历时有erase怎么办,都花了一些时间。算法中新边表要加入ymin的x值也疏忽了。加油加油。

边填充算法

算法理论

边填充算法的理论比较简单,没有使用特殊的数据结构,但在实现时出了一些不好解决的问题。
边标志算法分为两步,第一步对多边形的每条边进行直线扫描转换,即对多边形的每条边打上边标志。
第二步则进行填充。对每条与多边形相交的扫描线,从左往右进行扫描,若点在多边形内,置inside为ture,若在多边形外则置false。每次遇到边标志时,令inside=!inside。最后,当inside为真时,则将像素置为多边形色。

算法实现

#include
#include
#include
#include
#include"Polygon.h"

#define width 1000
#define height 500

using namespace std;
using namespace cv;

void DDAline(Mat& m, const int x0, const int y0, const int x1, const int y1, const cv::Vec3b& v);

int main(){
    Mat imageROI = Mat(height, width, CV_8UC3, Scalar(255, 255, 255));
    int n, i, j;
    cout << "Please input the number of edges" << endl;
    cin >> n;
    Polygon Poly(n);
    cout << "Please input the coordinate of the vertexes" << endl;
    Poly.Initialize();//definition of the polygon
    int x0, x1, y0, y1;
    for (i = 0; i < Poly.PointTable.size(); i++){
        if (i + 1 < Poly.PointTable.size()){
            j = i + 1;
        }
        else{
            j = 0;
        }
        x0 = Poly.PointTable[i].GetX();
        y0 = Poly.PointTable[i].GetY();
        x1 = Poly.PointTable[j].GetX();
        y1 = Poly.PointTable[j].GetY();
        if (x0 > x1){
            swap(x0, x1);
            swap(y0, y1);
        }
        DDAline(imageROI, x0, y0, x1, y1, Vec3b(0, 0, 0));
    }
    bool inside;
    int x,y;
    for (y = 0; y < height; y++){
        int count = 0;
        inside = false;
        for (x = 0; x < width-1; x++){
            if (imageROI.at(y, x) == Vec3b(0, 0, 0) && imageROI.at(y, x+1) != Vec3b(0, 0, 0)){
                inside = !inside;
            }
            if (inside != false){
                imageROI.at(y, x) = Vec3b(0, 0, 0);
            }
        }
    }
    namedWindow("显示结果");
    imshow("显示结果", imageROI);
    waitKey();
}

其中直线扫描算法、多边形的定义见前述。
结果如图
区域填充算法_第2张图片

总结

由图可见,算法实现的结果存在一定问题。
在顶点处,由于像素点只有一个,inside直接取负了,但还可通过对查找顶点、讨论边的另一个顶点位置等方法解决。
图上的(0,0)至(200,100)的边由直线扫描算法,每行均有两个像素点被置为黑色,故算法中加了一步,相邻的两个黑色像素点视为一个边标志。
但又出现了问题。如在y=1这行,两条边的边标志是相邻的,又被视为了一个边标志,造成整行都是黑的。
故个人认为该算法只是比较适合硬件实现,在这种结果中并不太适用。

种子填充算法

算法理论

有点深度优先算法的意思,但是只有上下两个方向作为分支,左右则直接处理了,一定程度上减少了搜索的深度。
初始任意取一个种子元素,当栈非空时进行以下四步
+ 栈顶像素出栈
+ 对包含该像素的左右区间进行填充
+ 对最左、最右像素标记为xl和xr
+ 对上下两个区间进行扫描,如果存在未填充像素,则将区间的最右端元素入栈

算法实现

#include
#include
#include
#include
#include
#include"Polygon.h"

#define width 1000
#define height 500

using namespace std;
using namespace cv;

void DDAline(Mat& m, const int x0, const int y0, const int x1, const int y1, const cv::Vec3b& v);

int main(){
    Mat imageROI = Mat(height, width, CV_8UC3, Scalar(255, 255, 255));
    int n, i, j;
    cout << "Please input the number of edges" << endl;
    cin >> n;
    Polygon Poly(n);
    cout << "Please input the coordinates of the vertexes" << endl;
    Poly.Initialize();//definition of the polygon
    int x0, x1, y0, y1;
    for (i = 0; i < Poly.PointTable.size(); i++){
        if (i + 1 < Poly.PointTable.size()){
            j = i + 1;
        }
        else{
            j = 0;
        }
        x0 = Poly.PointTable[i].GetX();
        y0 = Poly.PointTable[i].GetY();
        x1 = Poly.PointTable[j].GetX();
        y1 = Poly.PointTable[j].GetY();
        if (x0 > x1){
            swap(x0, x1);
            swap(y0, y1);
        }
        DDAline(imageROI, x0, y0, x1, y1, Vec3b(0, 0, 0));
    }
//种子填充算法
    int px, py;
    cout << "input seed coordinate" << endl;
    cin >> px >> py;
    MyPoint seed = MyPoint(px, py);
    stack pixelStack;
    MyPoint tmp;
    int x,y, xl, xr,xnextspan;
    bool flag;
    pixelStack.push(seed);//push the seed pixel
    while (!pixelStack.empty()){
        tmp = pixelStack.top();
        pixelStack.pop();
        x = tmp.GetX();
        y = tmp.GetY();
        if (imageROI.at(y, x) == Vec3b(0, 0, 0)){
            continue;
        }
        imageROI.at(y, x) = Vec3b(0, 0, 0);
        for (x0 = x + 1; imageROI.at(y, x0) != Vec3b(0, 0, 0); x0++){
            imageROI.at(y, x0) = Vec3b(0, 0, 0);
        }
        xr = x0 - 1;//fill right pixels
        for (x0 = x - 1; imageROI.at(y, x0) != Vec3b(0, 0, 0); x0--){
            imageROI.at(y, x0) = Vec3b(0, 0, 0);
        }
        xl = x0 + 1;//fill left pixels
        x0 = xl;
        y = y + 1;
        while (x0 <= xr){
            flag = false;
            while (imageROI.at(y, x0) != Vec3b(0, 0, 0)&&x0if (flag == false){
                    flag = true;
                }
                x0++;
            }
            if (flag == true){
                if (x0 == xr&&imageROI.at(y, x0) != Vec3b(0, 0, 0)){
                    pixelStack.push(MyPoint(x0, y));
                }
                else{
                    pixelStack.push(MyPoint(x0 - 1, y));
                }
                flag = false;
            }
            xnextspan = x0;
            while (imageROI.at(y, x0) == Vec3b(0, 0, 0) && x0 <= xr){
                x0++;
            }
            if (xnextspan == x0)
                x0++;
        }//fill y=y+1
        x0 = xl;
        y = y - 2;
        while (x0 <= xr){
            flag = false;
            while (imageROI.at(y, x0) != Vec3b(0, 0, 0)&&x0if (flag == false){
                    flag = true;
                }
                x0++;
            }
            if (flag == true){
                if (x0 == xr&&imageROI.at(y, x0) != Vec3b(0, 0, 0)){
                    pixelStack.push(MyPoint(x0, y));
                }
                else{
                    pixelStack.push(MyPoint(x0 - 1, y));
                }
                flag = false;
            }
            xnextspan = x0;
            while (imageROI.at(y, x0) == Vec3b(0, 0, 0) && x0 <= xr){
                x0++;
            }
            if (xnextspan == x0)
                x0++;
        }//fill y=y-1
    }
    namedWindow("显示结果");
    imshow("显示结果", imageROI);
    waitKey();
}

效果如图
区域填充算法_第3张图片
算法实现起来比较有趣。难受的是又把mat的y和x搞反了,查了好久是什么问题。还是要对opencv多熟悉才行。

你可能感兴趣的:(图形学)