以下按照项目的流程总结:图像的基础知识—灰度图像—图像的二值化—图像噪声—图像分割
1.图像的基础知识
用计算机处理图像首先要明白图像在计算机中是如何存储的。图像是由一个个的像素点组成的,每个像素点又包含该点的RGB值(对三通道来说),因此在计算机中图像就是一幅二维数组组成的矩阵矩阵的每个位置代表着该点的像素点。三通道图像占3个字节,单通道图像占一个字节。
图像处理和计算机视觉方面实现的一个重要的视觉库就是opencv,它是一个开源的计算机视觉库,opencv是用C++语言编写,主要接口也是C++语言。Opencv中常见的与图像操作有关的结构有Mat,cvMat和iplimage。其中Mat结构是opencv封装的一种读取和存储图片的矩阵结构的类,其中常用的属性和方法是:
• int rows;//图像的行数
• int cols;//图像的列数
• uchar *data;//指向图像存储空间的指针
• 复制“=”的重载;//不开辟新空间的复制
• void clone();//开辟新空间的复制
Mat类型较CvMat与IplImage类型来说,有更强的矩阵运算能力,支持常见的矩阵运算。这些都是编写程序必须用到的。
2. 灰度图像
灰度图像是指只含亮度信息,不含色彩信息的图像。即每个像素的R=G=B=Y。其中Ymax =255 为白色,Ymin =0 为黑色。彩色图像可恶意由下面这个公式变为灰度图像: Y=0.299R+0.587G+0.114B
在项目中,需要根据灰度值将图像二值化处理,因此要先将三通道图像变成灰度图像:Mat img= imread("图像名称",CV_LOAD_IMAGE_GRAYSCALE );其中,Mat类就是用来处理图像的,将一个图像读进来,CV_LOAD_IMAGE_GRAYSCALE就是进行灰度化处理,可以看出灰度化图像只需一个语句即可完成。
3. 图像的二值化
得到灰度图像后,就变成了单通道图像,通过设置阈值来将灰度值不一样的区域区分开来,项目中设置的阈值为30,将强度值大于30的,其灰度值全变成255,强度值小于30的,其灰度值全部变成黑色,这样图像中只有黑色和白色。
常用的边缘检测的算子主要有sober算子、Roberts算子、Prewitt算子、Canny算子等等。但是现在只学会了Roberts算子,项目中也是用的Roberts算子。Roberts算子是利用局部差分算子寻找边缘,通过计算对角线像素值差的平方来计算强度。从图像处理的实际效果来看,边缘定位较准,对噪声敏感。适用于边缘明显且噪声较少的图像分割。项目中,定义一个result数组,并根据row*cols动态的给数组分配存储空间,将每个像素点计算出的强度值相应的存入result数组中。强度值是由卷积公式得出的。这部分容易错的部分是经常以为是灰度值和阈值比较,其实应该是梯度幅值(强度值[c1] )。
项目中和阈值相比较的这部分的代码为:
for(j=0;j<(rows-1);j++) //使用两个for循环依次对每个像素点进行比较
{
for(i=0;i<(cols-1);i++)
{
if(result[j*cols+i]<30) //每个灰度值和阈值比较
{
img.data[j*cols+i] =0;
}
else
{
img.data[j*cols+i]=255;
}
}
}
项目中阈值的确定是根据结果不断改出来的,科学的获得阈值的方法是使用直方图,取直方图的波谷作为阈值的方法称为模态法,其他方法还有p参数法、判别分析法等。
3. 图像噪声
下面直接分析项目中去除图像噪声的方法:
自定义一个去噪声的函数denoise,该函数有两个参数,一个是用来传入图像,一个是用来传入阈值(八连通区域白色像素点块数的阈值),当然也可以只有一个图像的参数,而阈值在后面直接写即可。项目中还定义了一个ImgPoint类,类中的数据成员有每个像素点灰度值的指针G和用来标记该点是否被遍历过的标记flag。ImgPoint *point;语句声明了一个类的指针point,point是用来存放类的对象的。再用两个for循环依次将图像的灰度值的地址赋给类中定义的G,并将每个点的标记都设为false,语句:
point[j*cols+i].G=dataout+j*cols+i;
point[j*cols+i].flag=false;
point是ImagePoint类的一个对象,它包含的东西:
由于图像的四个边缘无法进行八连通区域白色像素的查询,并且边缘附近的点进行八连通区域白色点判断的时候一定会将边缘也判断,所以不必再判断边缘部分的点,所以项目中用四个for循环将四个边缘部分的flag设置为true。之后声明两个数组q和q1。q用来存放对象的指针(地址),q1用来存放一个连通区域中所有的点。首先用for循环来寻找第一个白点(噪声),根据该点的灰度值是否为255,并且要求该点没有被访问过,如果找到,就将该点访问标志设置为true,并压入q队列:
while(*(point[j*cols+i].G)==255&&!point[j*cols+i].flag){
point[j*cols+i].flag=true;
q.push(point+j*cols+i);
}
由于point中存放类的对象,所有G 和 flag 都在point中。(point[j*cols+i].G)指该点灰度值的指针,加上*表示指针所指的值。由于q是用来存放对象的地址,所以要用q.push(point+j*cols+i)将地址压入队列。之后开始找该点的八连通区域中的白点,找到之后将原来的点复制压入q1中,再将它连通区域的白点压入q队列,再将该点从q队列中弹出来,这是q队列的第二个对象就成为了对首,再这样递归的进行下去。直到一片区域的白点全部入到q1队列,这时q1队列中对象的个数即为一片区域中白点的数量,如果小于设定的阈值,则认为是噪声,将它们灰度值变为0,最后再一一从q1队列中弹出。由于point是用New动态的分配数组,因此程序结束要将空间释放。函数最后返回imgout,传给主函数中denoise(img,1)中的img。
主函数中调用denoise函数,在调用之前要记得先声明;在程序中引用类名的格式为:#include "ImgPoint.h"。
4. 图像分割
未完待续[c2] 。。。
[c1]这里不叫强度值,术语称 梯度幅值。梯度是向量,有方向代表边缘增长的方向,数量代表边缘的陡峭程度。阈值的作用就是挑选出梯度幅值较高的也就是边缘很明显的地方。边缘方向信息在个别的算子中用处较大,如canny算子。其他的如sobel和robert只用梯度幅值来找边缘。
[c2]
完整代码如下:
#include<opencv2/opencv.hpp>
#include<math.h>
using namespace cv;
using namespace std;
int rows,cols;
void domain(Mat img1) ;
void round(int xstart ,int ystart,int * num,bool * flag) ;
void first(int * num,bool * flag,Mat img2) ;
int number;
int a(int T);
int get_value(uchar* ptr,int rows,int cols);
int main()
{
int T;//阈值
Mat img= imread("1.jpg",CV_LOAD_IMAGE_GRAYSCALE );
rows=img.rows;
cols=img.cols;
//GaussianBlur(img,img,Size(5,5),2,2);
uchar *result; //数组和地址的关系
result=new uchar[rows*cols];
uchar *result1;//动态分配一个数组result,存每个点的强度值,new分配的数组最后要用delete释放空间
int b;
for(int j=0;j<(rows-1);j++)
{
for(int i=0;i<(cols-1);i++)
{
int x=img.data[j*cols+i]-img.data[(j+1)*cols+i+1];
int y=img.data[(j+1)*cols+i]-img.data[j*cols+i+1];
result[j*cols+i]=(uchar)sqrt(x*x+y*y);
}
}
//printf("%d ",result[10]);
T=get_value(result,rows,cols);
printf("%d ",T);
for(int j=0;j<(rows-1);j++)
{
for(int i=0;i<(cols-1);i++)
{
if(result[j*cols+i]<T)
{
img.data[j*cols+i] =0;
}
else
{
img.data[j*cols+i]=255;
}
}
}
delete [] result ;
domain (img );
namedWindow("image0", CV_WINDOW_AUTOSIZE);
imshow("image0",img);
waitKey();
return 0;
}
int get_value(uchar* ptr,int rows,int cols)
{
int T;//阈值
double max_variance=0.0 ;//大的方差
int max_T=0;//大的方差对应的阈值
for(int m=0;m<=255;m++)
{
T=m;
int foreground_gray=0;//前景总灰度值
int background_gray=0;//背景总灰度值
int fore_num=0,back_num=0;//计算个数
double fore_aver=0;
double back_aver=0;
for(int j=1;j<rows-1;j++)
{
for(int i=1;i<cols-1;i++)
{
if(ptr[j*cols+i]>T)
{
foreground_gray=ptr[j*cols+i]+foreground_gray;
fore_num++;
}
else
{
background_gray+=ptr[j*cols+i];
back_num++;
}
}
}
if(fore_num!=0)
{
fore_aver=((double)foreground_gray)/((double)fore_num);
}
if(back_num!=0)
{
back_aver=(double)background_gray/(double)back_num;
}
double fore_proportion=(double)fore_num/(double)(rows*cols);
double back_proportion=(double)back_num/(double)(rows*cols);
double total_aver=fore_proportion*fore_aver+back_proportion*back_aver;
double variance=(fore_aver-total_aver)*(fore_aver-total_aver)*fore_proportion+(back_aver-total_aver)*(back_aver-total_aver)*back_proportion;
if(variance>max_variance)
{
max_variance=variance;
max_T=T;
}
}
return max_T;
}
void domain(Mat img1) //找出一片区域的白点个数
{
//int n; //记录连通域白点个数
Mat img2=img1.clone();
// img2.data[j*cols+i]=img1.data[j*cols+i];
int * num;
num=new int[rows*cols];
bool * flag;
flag=new bool[rows*cols];
for(int j=0;j<rows;j++){
for(int i=0;i<cols;i++){
if(img2.data[j*cols+i]==255)
{
num[j*cols+i]=1;
flag[j*cols+i]=false;
}
else
{
num[j*cols+i]=0;
flag[j*cols+i]=false;
}
}
}
for(int i=0;i<cols;i++) //四个边,第一行
{
flag[i]=true;
}
for(int i=0,j=rows-1;i<cols;i++) //四个边,最后一行
{
flag[j*cols+i]=true;
}
for(int j=0;j<rows;j++) //四个边,左边一列
{
flag[j]=true;
}
for(int i=cols-1,j=0;j<rows;j++) //四个边,右边一列
{
flag[j*cols+i]=true;
}
first(num,flag,img2);
namedWindow("image1", CV_WINDOW_AUTOSIZE);
imshow("image1",img2);
delete [] num ;
delete [] flag ;
}
void first(int * num,bool * flag,Mat img2) //寻找连通域的第一个白点
{
for(int j=0;j<rows;j++){
for(int i=0;i<cols;i++){
if(num[j*cols+i]==1&&!flag[j*cols+i])
{
//round(i,j,num,flag);
num[j*cols+i]=2;
number=0;
flag[j*cols+i]=true;
round(i,j,num,flag);
if(number<80)
{
for(int a=0;a<rows;a++){
for(int b=0;b<cols;b++){
if(num[a*cols+b]==2)
{
img2.data[a*cols+b]=0;
}
}
}
}
else
{
for(int a=0;a<rows;a++){
for(int b=0;b<cols;b++){
if(num[a*cols+b]==2)
{
num[a*cols+b]=3;
}
}
}
}
}
}
}
}
void round(int xstart ,int ystart,int * num,bool * flag) //寻找连通域的白点数
{
if(num[(ystart-1)*cols+xstart-1]==1&&!flag[(ystart-1)*cols+xstart-1]) //左上
{
number++;
flag[(ystart-1)*cols+xstart-1]=true;
num[(ystart-1)*cols+xstart-1]=2;
round(xstart-1,ystart-1,num,flag);
}
if(num[(ystart-1)*cols+xstart]==1&&!flag[(ystart-1)*cols+xstart]) //上
{
number++;
flag[(ystart-1)*cols+xstart]=true;
num[(ystart-1)*cols+xstart]=2;
round(xstart,ystart-1,num,flag);
}
if(num[(ystart-1)*cols+xstart+1]==1&&!flag[(ystart-1)*cols+xstart+1]) //右上
{
number++;
flag[(ystart-1)*cols+xstart+1]=true;
num[(ystart-1)*cols+xstart+1]=2;
round(xstart+1,ystart-1,num,flag);
}
if(num[ystart*cols+xstart-1]==1&&!flag[ystart*cols+xstart-1]) //左
{
number++;
flag[ystart*cols+xstart-1]=true;
num[ystart*cols+xstart-1]=2;
round(xstart-1,ystart,num,flag);
}
if(num[ystart*cols+xstart+1]==1&&!flag[ystart*cols+xstart+1]) //右
{
number++;
flag[ystart*cols+xstart+1]=true;
num[ystart*cols+xstart+1]=2;
round(xstart+1,ystart,num,flag);
}
if(num[(ystart+1)*cols+xstart-1]==1&&!flag[(ystart+1)*cols+xstart-1]) //左下
{
number++;
flag[(ystart+1)*cols+xstart-1]=true;
num[(ystart+1)*cols+xstart-1]=2;
round(xstart-1,ystart+1,num,flag);
}
if(num[(ystart+1)*cols+xstart]==1&&!flag[(ystart+1)*cols+xstart]) //下
{
number++;
flag[(ystart+1)*cols+xstart]=true;
num[(ystart+1)*cols+xstart]=2;
round(xstart,ystart+1,num,flag);
}
if(num[(ystart+1)*cols+xstart+1]==1&&!flag[(ystart+1)*cols+xstart+1]) //右下
{
number++;
flag[(ystart+1)*cols+xstart+1]=true;
num[(ystart+1)*cols+xstart+1]=2;
round(xstart+1,ystart+1,num,flag);
}
}