轮廓提取算法

目的

        简单4连通/8连通的轮廓提取算法实现,8连通效果等同于opencv的findContours()。

实现

1.二值化 + 连通域提取。(连通域提取通常有两种方法,递归搜索 - depth first search、 two pass 方法,可参考algorithm - Connected Component Labeling - Implementation - Stack Overflow

#define MAX_CONTOURS_NUM (20)
#define CONNECT_WAY  (8)  //必须是4或8, 4-way or 8-way connectivity。 4更快,8对齐cv::findContours
#define REFINE_NET_OUTPUT_H (80)
#define REFINE_NET_OUTPUT_W (80)

short table[REFINE_NET_OUTPUT_H * REFINE_NET_OUTPUT_W];
int label_start_pos[MAX_CONTOURS_NUM+1];
int label_num[MAX_CONTOURS_NUM+1];

//递归标记
bool fdfs(float* data, int w, int h, short* table, int x, int y, int cur_label) {
    if (x < 0 || x == w) return false;
    if (y < 0 || y == h) return false;
    
    int pos = y * w + x;
    if (table[pos] != 0) return false;
    
    //二值化
    if (data[pos] <= 0.5) {
        table[pos] = -1;
        return false;
    }
    
    //打标签
    table[pos] = cur_label;
    
    
    if(cur_label <= MAX_CONTOURS_NUM){
        //标签数量
        label_num[cur_label]++;
        //起始位置
        if(label_start_pos[cur_label] < 0)
            label_start_pos[cur_label] = pos;
    }
    
    //4邻域 or 8邻域
    static const int dx[8] = { 1,-1,0,0,     1,1,-1,-1};
    static const int dy[8] = { 0,0,1,-1,     -1,1,-1,1};
    for (int i = 0; i < CONNECT_WAY; i++)
        fdfs(data, w, h, table, x + dx[i], y + dy[i], cur_label);
    
    return true;
};



int get_contours(float* xnnOutput){
    memset(table, 0, REFINE_NET_OUTPUT_W * REFINE_NET_OUTPUT_H * sizeof(short));
    memset(label_num, 0, (MAX_CONTOURS_NUM+1) * sizeof(int));
    for(int i = 0; i < MAX_CONTOURS_NUM+1; i++)
        label_start_pos[i] = -1;
    
    int label = 1;
    int step = 1;//1、2
    for (int y = 0; y < REFINE_NET_OUTPUT_H; y += step) {
        for (int x = 0; x < REFINE_NET_OUTPUT_W; x += step) {
            if (fdfs(xnnOutput, REFINE_NET_OUTPUT_W, REFINE_NET_OUTPUT_H, table, x, y, label)) {
                label++;
            }
        }
    }
    return label-1;
}

2.轮廓提取。

//边界处理
inline void get_offset(int pos, int out_offset[]){
    
#if CONNECT_WAY==4
    static const int offset[4] = {-1, -REFINE_NET_OUTPUT_W, 1, REFINE_NET_OUTPUT_W};// 左, 上, 右, 下
    {
        memcpy(out_offset, offset, sizeof(offset));
        
        //边界判断
        if(pos % REFINE_NET_OUTPUT_W == 0)//左边界
            out_offset[0] = 0;
        else if((pos+1) % REFINE_NET_OUTPUT_W == 0)//右边界
            out_offset[2] = 0;
        if(pos < REFINE_NET_OUTPUT_W)//上边界
            out_offset[1] = 0;
        else if(pos >= REFINE_NET_OUTPUT_W * (REFINE_NET_OUTPUT_H-1))//下边界
            out_offset[3] = 0;
    }
#else
    static const int offset_8[8] = {-1, -1-REFINE_NET_OUTPUT_W, -REFINE_NET_OUTPUT_W, 1-REFINE_NET_OUTPUT_W,// 左, 左上, 上, 右上
        1, 1+REFINE_NET_OUTPUT_W, REFINE_NET_OUTPUT_W, -1+REFINE_NET_OUTPUT_W};// 右,右下, 下, 左下
    {
        memcpy(out_offset, offset_8, sizeof(offset_8));
        
        //边界判断
        if(pos % REFINE_NET_OUTPUT_W == 0)//左边界
            out_offset[0] = out_offset[1] = out_offset[7] = 0;
        else if((pos+1) % REFINE_NET_OUTPUT_W == 0)//右边界
            out_offset[3] = out_offset[4] = out_offset[5] = 0;
        if(pos < REFINE_NET_OUTPUT_W)//上边界
            out_offset[1] = out_offset[2] = out_offset[3] = 0;
        else if(pos >= REFINE_NET_OUTPUT_W * (REFINE_NET_OUTPUT_H-1))//下边界
            out_offset[5] = out_offset[6] = out_offset[7] = 0;
    }
#endif
}

//通过顺时针遍历方向,提取轮廓 (这里只提取最大轮廓)
std::vector<cv::Point> get_external_points(){
    std::vector<cv::Point> result;
    //找出最大轮廓
    int max_contour_label = -1;
    int max_contour_pts = 0;
    for(int i = 1; i < MAX_CONTOURS_NUM+1; i++){
        if(label_num[i] > max_contour_pts){
            max_contour_pts = label_num[i];
            max_contour_label = i;
        }
    }
    if(max_contour_label < 0)
        return result;
    
    //找出轮廓点, 沿着顺时针方向找
    int start_pos = label_start_pos[max_contour_label];
    int offset[8];//4个方向偏移量
    int search = (CONNECT_WAY>>1);//第一个点,肯定是从左到右找到的, ->
    int last_search = -100;
    int pos = start_pos;
    
    result.push_back(cv::Point(pos%REFINE_NET_OUTPUT_W, pos/REFINE_NET_OUTPUT_W));
    get_offset(pos, offset);
    search = (search + (CONNECT_WAY>>1)) % CONNECT_WAY; //反向。 下一个点起始搜索方向
    do{
        //1.更新方向
        search = (search + 1) % CONNECT_WAY;//顺时针找下一个方向
        if(offset[search] == 0) //边界不找
            continue;
        //2.更新位置
        int next_pos = pos + offset[search];
        if(next_pos == start_pos)//回到原点
            break;
        //3.找到点
        if(table[next_pos] == max_contour_label){
#if CONNECT_WAY == 4
            int diff = search - last_search;
            if(diff == -1 || diff == 3){ //消除相邻内点
                result.back() = cv::Point(next_pos%REFINE_NET_OUTPUT_W, next_pos/REFINE_NET_OUTPUT_W);
                last_search = -100;//下个点不判断
            }
            else{
                result.push_back(cv::Point(next_pos%REFINE_NET_OUTPUT_W, next_pos/REFINE_NET_OUTPUT_W));
                last_search = search;
            }
#else
            result.push_back(cv::Point(next_pos%REFINE_NET_OUTPUT_W, next_pos/REFINE_NET_OUTPUT_W));
#endif
            
            search = (search + (CONNECT_WAY>>1)) % CONNECT_WAY; //反向。 下一个点起始搜索方向
            pos = next_pos; //下个点
            get_offset(pos, offset); //更新offset, 主要处理边界case
            continue;
        }
    }while(1);
    return result;
}

3.调用方式

get_contours(float_img_data);
std::vector<cv::Point> contours = get_external_points();

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值