学习DIP第60天
转载请标明本文出处:http://blog.csdn.net/tonyshengtan ,出于尊重文章作者的劳动,转载请标明出处!文章代码已托管,欢迎共同开发:https://github.com/Tony-Tan/DIPpro
今天已经是第60篇博客了,这六十篇每一篇平均要两天左右,所以,在过去的四个月学到了这么多知识,想想挺开心,但学的越多就会发现自己不会的越多。从小学到大学,这么多年一直以学习为主要工作但学习又有很多阶段,对于通用知识,比如小学的语文数学此观点不适用,对于一些专业性较强的知识,感觉会有两个很主要的阶段,感觉自己目前处于入门阶段,由于数字图像涉及数学的知识较多,还有信号和信息论的知识,所以,感觉还是比较难学科,不然招聘公司也不会一年几十万的养着图像处理工程师。
下面的图是本人的一点点见解,只是自己总结的,没有实践,也没有科学依据,不喜勿喷:
我感觉图像处理能分成三个阶段,或者更多,第一阶段的人很多,听说这行前景好或者工资高的人,多半会学习点图像的知识,比如彩色空间啊,了解下OpenCV啊等等,还有一些属于纯属上课被逼无奈的,比如我们学校就对电子信息类专业和通信类开数字图像处理这门课,而且我当时考试还挂了。。。。这部分大家会接触一些名词,一些简单算法,有用心的同学可能会实现下代码,这阶段风景不错,而且好多名词可以拿来忽悠HR或者忽悠导师,得到一份不错的工作,或者做点导师的项目。
这个阶段多半使用现成的函数库,了解了基本算法或者听别人说一些算法,自己来跑结果,这个阶段Matlab的用户量较大。
第1阶段后半期也就是平台期,这个阶段是做了一段时间有一定算法使用基础和代码能力的工程师,多半在各企业负责最底层的图像算法编写,在他们面前是一座大山,和一个路口,继续做图像还是转管理。
如果继续选择图像,就会面临一个峭壁,具体是算法底层的数学,说的数学,总是困难的,爬这个峭壁的动力可以是挣更多的钱,或者爱好,因为一旦到达阶段2,就能成为首席图像处理工程师,到任何需要图像处理的公司,都能够独当一面,这些人已经到了不缺钱的地步,而且在行业内一定有一定名气。
第二阶段的一旦到达,可以说是事业的平稳期,或者巅峰,不缺钱,还能独自决定一些技术层面上的事,指挥手下阶段1的员工工作,这个阶段面临的也是一个选择,就是靠这个吃一辈子饭,绝对没问题,再有就是向更高的境界冲击。
第三阶段没有尽头,因为能促使进入阶段三的动力只有爱好,这部分挣的钱可能没有阶段2挣的多,而且难度更大,看不到尽头,所以这部分属于探索阶段,在这个阶段上看到的一些,都能推动未来学科的发展,所以这个阶段的人多半是在实验室和数学中忙碌一生,然后几十年后出现在各大论文和教材中。
以上属于个人猜想或者愚见,想法幼稚,仅供参考。
今天废话太多了,哈哈,其实上面的可以新开一篇博客单独写出来,但觉得,首先自己年轻视野狭窄,第二水平太低,所以当做废话夹在本文中。
今天说分水岭算法,分水岭算法族是一个思想引出的一些列实现方法。
算法内容:首先将二维灰度图像抽象为三维地形,横纵坐标表示经纬度,灰度表示高度,这样就产生了一个地形图,地形中有高山有盆地,我们的任务就是找到盆地并且给盆地划定地盘。
算法过程,将最低点(灰度)作为起始点,开始从下注水,想泉水一样,水位逐渐上升,所有点都是上下透水的,但不会横向流动,每产生一个新的积水盆地时标记这个盆地使之与原有盆地区别开,当两个盆地内的水要汇集的时候,在此点建立一个水坝,将水分开,此水坝无限高,水永远不会溢出,依次建立所有水坝,最后的水坝就是所谓的分水岭或分水线,也就是得到的分割线。
算法步骤:
此算法描述只是分水岭算法之一,冈萨雷斯的书中使用的是形态学的描述方式,但与上述过程原理一致,感兴趣者可自行查阅。
解释下第一步,第一步是本算法的核心,此步骤首先要确定盆地还是非盆地,可以抽象成下面几种模型:
图像中的红色框内为非初始盆地,或叫做盆底,绿色为盆底,对于第二种盆地的确定需要使用图的深度或者广度优先搜索,判断所有候选点,来判断是第二种模型还是第三种。
//
// Watershed
// tony.sheng.tan@gmail.com
// Created by 谭升 on 15/03/03.
// Copyright (c) 2015年 谭升. All rights reserved.
//
/*
typedef struct PriQueueNode_ PriQueueHead;
typedef struct NLevelPriQueueNode_ * NLevelPriQueue;
typedef struct NLevelPriQueueNode_ NLevelPriNode;
typedef struct ExPix_ Pix_Label;
* ___ ____________________
* | P |->| NLevelPriQueue |
* |---| |--------------------|
* | r |->| NLevelPriQueue |
* |---| |--------------------|
* | i |->| NLevelPriQueue |
* |---| |--------------------|
* | Q |->| NLevelPriQueue |
* |---| |--------------------|
* | u |->| NLevelPriQueue |
* |---| |--------------------|
* | e |->| NLevelPriQueue |
* |---| |--------------------|
* | u |->| NLevelPriQueue |
* |---| |--------------------|
* | e |->| NLevelPriQueue |
* |___| |____________________|
struct NLevelPriNode_{
int x;
int y;
NLevelPriQueue next;
};
struct PriQueueNode_{
int nodeNum;
NLevelPriQueue head;
NLevelPriQueue tail;
};
struct ExPix_{
int grayvalue;
int label;
};
*/
#include "watershed.h"
#include <stdio.h>
//将非极小值且与极小值相邻的元素入队
void inQueue(PriQueueHead* priQueue,int gray_level,int x,int y){
priQueue[gray_level].nodeNum++;
///malloc new node
NLevelPriNode* newNode=(NLevelPriNode *)malloc(sizeof(NLevelPriNode));
newNode->x=x;
newNode->y=y;
newNode->next=NULL;
if(priQueue[gray_level].head==NULL){
priQueue[gray_level].head=newNode;
priQueue[gray_level].tail=newNode;
}else{
priQueue[gray_level].tail->next=newNode;
priQueue[gray_level].tail=newNode;
}
}
//判断极小值是平底锅型还是台阶形状,使用图的深度优先搜索
int isPan(int *src,int width,int height,double value,int x,int y){
src[y*width+x]=-value;
for(int j=-1;j<2;j++)
for(int i=-1;i<2;i++)
if(j+y>=0&&i+x>=0&&j+y<height&&i+x<width&&(i!=0||j!=0)){
if(src[(j+y)*width+i+x]<value&&src[(j+y)*width+i+x]>0)
return 0;
}
for(int j=-1;j<2;j++)
for(int i=-1;i<2;i++)
if(j+y>=0&&i+x>=0&&j+y<height&&i+x<width&&(i!=0||j!=0)){
if(src[(j+y)*width+i+x]==value){
return(isPan(src,width, height, value, i+x, j+y));
}
}
return 1;
}
//由于判断平底锅时使部分数据损坏,现进行恢复
void repairPan(int *src ,int width,int height,double value,int x,int y){
src[y*width+x]=-value;
for(int j=-1;j<2;j++)
for(int i=-1;i<2;i++)
if(j+y>=0&&i+x>=0&&j+y<height&&i+x<width&&(i!=0||j!=0)){
if(src[(j+y)*width+i+x]==value){
repairPan(src, width, height, value, x+i, y+j);
}
}
}
//平底锅形状,标记极小值为255
void setMinimal(int *src,double *dst,int width,int height,int value,int x,int y){
dst[y*width+x]=255.0;
for(int j=-1;j<2;j++)
for(int i=-1;i<2;i++)
if(j+y>=0&&i+x>=0&&j+y<height&&i+x<width&&(i!=0||j!=0)){
if(src[(j+y)*width+x+i]==value&&dst[(j+y)*width+x+i]==0.0)
setMinimal(src,dst,width, height, value, x+i,y+j);
}
}
//台阶形状,标记非极小值127
void setUnMinimal(int *src,double *dst,int width,int height,int value,int x,int y){
dst[y*width+x]=127.0;
for(int j=-1;j<2;j++)
for(int i=-1;i<2;i++)
if(j+y>=0&&i+x>=0&&j+y<height&&i+x<width&&(i!=0||j!=0)){
if(src[(j+y)*width+x+i]==value&&dst[(j+y)*width+x+i]==0.0)
setUnMinimal(src,dst,width, height, value, x+i,y+j);
}
}
//数据类型从double到int
void Double2Int(double *src,int* dst,int width,int height){
for(int i=0;i<width*height;i++)
dst[i]=(int)src[i]+1;
}
//寻找极小值,包括单点极小值和平底锅
void findMinimal(double *src,double *dst,int width,int height){
int *temp=(int *)malloc(sizeof(int)*width*height);
double *dsttemp=(double *)malloc(sizeof(double)*width*height);
Zero(dsttemp, width, height);
Double2Int(src, temp, width, height);
int lessthan=0;
int equ=0;
double min=findMatrixMin(src, width, height);
for(int i=0;i<width*height;i++)
if(src[i]==min)
dsttemp[i]=255.0;
for(int j=0;j<height;j++){
for(int i=0;i<width;i++){
lessthan=0;
equ=0;
int pix=temp[j*width+i];
if(dsttemp[j*width+i]==0.0){
for(int m=-1;m<2;m++)
for(int n=-1;n<2;n++)
if(j+m>=0&&i+n>=0&&j+m<height&&i+n<width){
if(m!=0||n!=0){
if(temp[(j+m)*width+i+n]<pix)
lessthan=1;
if(temp[(j+m)*width+i+n]==pix)
equ=1;
}
}
if(equ==1&&lessthan==0){
if(isPan(temp, width, height,pix, i, j)){
//repairPan(temp,width, height, -pix, i,j);
setMinimal(temp,dsttemp, width, height, -pix, i, j);
}else {
repairPan(temp,width, height, -pix, i,j);
setUnMinimal(temp,dsttemp, width, height, pix, i, j);
}
}
if(lessthan==1)
dsttemp[j*width+i]=127.0;
if(0==lessthan&&0==equ)
dsttemp[j*width+i]=255.0;
}
}
}
matrixCopy(dsttemp, dst, width, height);
free(dsttemp);
free(temp);
}
//标记极小值的label,从-1开始向下增长 -2 -3 -4 -5 -6 -7.....
void LabelMinimal(double * src,Pix_Label* dst,int width,int height,int x,int y,int label){
dst[y*width+x].label=label;
for(int i=-1;i<2;i++){
for(int j=-1;j<2;j++)
if(x+i>=0&&x+i<width&&y+j>=0&&y+j<height&&(i!=0||j!=0))
if(src[(y+j)*width+x+i]==255.0&&dst[(y+j)*width+x+i].label==0){
LabelMinimal(src, dst, width, height, x+i, y+j, label);
}
}
}
//初始化label数组,此数组与图像数组多加了label
void InitLabelMat(double *src,Pix_Label* dst,double *mask,int width,int height){
for(int i=0;i<width*height;i++){
dst[i].grayvalue=src[i];
dst[i].label=0;
}
int label_minimal=-1;
for(int j=0;j<height;j++)
for(int i=0;i<width;i++){
if(mask[j*width+i]==255.0&&dst[j*width+i].label==0){
LabelMinimal(mask, dst, width,height,i,j, label_minimal);
label_minimal--;
}
}
}
//初始化队列头数组
void InitPriQueue(PriQueueHead* priQueue,Pix_Label *srclabel,int width,int height){
for(int i=0;i<GRAY_LEVEL;i++){
priQueue[i].head=NULL;
priQueue[i].nodeNum=0;
priQueue[i].tail=NULL;
}
int inqueue=0;
for(int j=0;j<height;j++)
for(int i=0;i<width;i++){
inqueue=0;
if(srclabel[j*width+i].label==0){
for(int m=-1;m<2;m++)
for(int n=-1;n<2;n++){
if(m+j>=0&&m+j<height&&n+i>=0&&n+i<width&&(m!=0||n!=0))
if(srclabel[(m+j)*width+n+i].label<0)
inqueue=1;
}
if(inqueue){
inQueue(priQueue,srclabel[j*width+i].grayvalue,i,j);
srclabel[j*width+i].label=INQUEUE;
}
}
}
//for(int i=0;i<GRAY_LEVEL;i++)
// printf("g:%d NumofNode:%d\n",i,priQueue[i].nodeNum);
}
int PirQueueisEmpty(PriQueueHead* priqueue){
int sum=0;
for(int i=0;i<GRAY_LEVEL;i++)
sum+=priqueue[i].nodeNum;
return !sum;
}
NLevelPriNode* outQueue(PriQueueHead* priqueue){
NLevelPriNode* node=NULL;
if(!PirQueueisEmpty(priqueue))
for(int i=0;i<GRAY_LEVEL;i++)
if(priqueue[i].nodeNum!=0){
node=priqueue[i].head;
priqueue[i].head=node->next;
priqueue[i].nodeNum--;
break;
}
return node;
}
void findWaterShed(Pix_Label * srclabel,PriQueueHead* priqueue,int width,int height){
NLevelPriNode* node=outQueue(priqueue);
while(node!=NULL){
int y=node->y;
int x=node->x;
//printf("x:%d y:%d \n",x,y);
int label=0;
int isWatershed=0;
for(int j=-1;j<2;j++)
for(int i=-1;i<2;i++){
if(j+y>=0&&j+y<height&&i+x>=0&&i+x<width&&(j!=0||i!=0)){
if(srclabel[(j+y)*width+x+i].label<0){
if(label==0)
label=srclabel[(j+y)*width+x+i].label;
else if(label!=srclabel[(j+y)*width+x+i].label){
isWatershed=1;
}
}
}
}
if(isWatershed)
srclabel[y*width+x].label=WATERSHED;
else if(label<0){
srclabel[y*width+x].label=label;
for(int j=-1;j<2;j++)
for(int i=-1;i<2;i++){
if(j+y>=0&&j+y<height&&i+x>=0&&i+x<width&&(j!=0||i!=0)){
if(srclabel[(j+y)*width+x+i].label==0){
inQueue(priqueue, srclabel[(j+y)*width+i+x].grayvalue, i+x, j+y);
srclabel[(j+y)*width+i+x].label=INQUEUE;
}
}
}
}
else if(label==0&&isWatershed==0){
srclabel[y*width+x].label=0;
}
free(node);
node=outQueue(priqueue);
}
}
//meyer分水岭方法
void MeyerWatershed(double *src,double *dst,int width,int height){
double *dst_temp=(double *)malloc(sizeof(double)*width*height);
Zero(dst_temp, width, height);
Pix_Label * srclabel=(Pix_Label*)malloc(sizeof(Pix_Label)*width*height);
PriQueueHead priqueue[GRAY_LEVEL];
double *minimal=(double *)malloc(sizeof(double)*width*height);
Zero(minimal, width, height);
findMinimal(src, minimal, width, height);
InitLabelMat(src, srclabel, minimal, width, height);
//for(int j=0;j<height;j++){
// for(int i=0;i<width;i++)
// printf(" l:%3d|",srclabel[j*width+i].label);
// printf("\n");
//}
InitPriQueue(priqueue, srclabel, width, height);
/*for(int i=0;i<GRAY_LEVEL;i++){
NLevelPriNode *node=priqueue[i].head;
printf("%d:",i);
while (node!=NULL) {
printf("x:%d,y:%d ",node->x,node->y);
node=node->next;
}
printf("\n");
}*/
findWaterShed(srclabel, priqueue, width, height);
for(int i=0;i<width*height;i++)
if(srclabel[i].label==WATERSHED)
dst_temp[i]=255.0;
matrixCopy(dst_temp, dst, width, height);
free(dst_temp);
free(srclabel);
free(minimal);
}
以上实现目前还有点缺陷,就是盆底面积很大的时候,递归计算会crash,初步设想的解决办法是使用迭代替换递归,分割结果相对来讲对于前面的算法来说相对较好,但运算速度较慢,对噪声敏感。
灰度图像的内容简单的介绍至此,下篇开始介绍彩色基础知识和算法,欢迎收看。
待续。。。
原文地址:http://blog.csdn.net/tonyshengtan/article/details/44194985