【openCV】二值图像基础操作

实现了二值图像的Thin,Thicken和提取骨架的操作。

#include "opencv2/opencv.hpp"

#define HIT 1
#define MISS 0

using namespace cv;
using namespace std;

const int dir[9][2] = {{-1,-1},{-1,0},{-1,1},{0,-1},{0,0},{0,1},{1,-1},{1,0},{1,1}};

//定义了skeleton和convex hull操作用到的各八个structuring element
//structuring element可根据不同需要改变,这里只是一种
#define ELE_STR_SIZE 3

#define SKELETON_STR_ELE_NUM 8
const unsigned char skeleton_str_ele[SKELETON_STR_ELE_NUM][ELE_STR_SIZE][ELE_STR_SIZE] = {
	{
		{0,0,0},
		{2,1,2},
		{1,1,1}
	},

	{
		{2,0,0},
		{1,1,0},
		{2,1,2}
	},

	{
		{1,2,0},
		{1,1,0},
		{1,2,0}
	},

	{
		{2,1,2},
		{1,1,0},
		{2,0,0}
	},

	{
		{1,1,1},
		{2,1,2},
		{0,0,0}
	},

	{
		{2,1,2},
		{0,1,1},
		{0,0,2}
	},

	{
		{0,2,1},
		{0,1,1},
		{0,2,1}
	},

	{
		{0,0,2},
		{0,1,1},
		{2,1,2}
	}
};

#define CONVEX_HULL_STR_ELE_NUM 8
const unsigned char convex_hull_str_ele[CONVEX_HULL_STR_ELE_NUM][ELE_STR_SIZE][ELE_STR_SIZE] = {
	{
		{1,1,2},
		{1,0,2},
		{1,2,0}
	},

	{
		{2,1,1},
		{2,0,1},
		{0,2,1}
	},

	{
		{1,1,1},
		{2,0,1},
		{0,2,2}
	},

	{
		{0,2,2},
		{2,0,1},
		{1,1,1}
	},

	{
		{0,2,1},
		{2,0,1},
		{2,1,1}
	},

	{
		{1,2,0},
		{1,0,2},
		{1,1,2}
	},

	{
		{2,2,0},
		{1,0,2},
		{1,1,1}
	},

	{
		{1,1,1},
		{1,0,2},
		{2,2,0}
	}
};


unsigned char hit_and_miss(unsigned char src[][ELE_STR_SIZE],const unsigned char str_ele[][ELE_STR_SIZE]){
	//对一块区域的hit_and_miss判定
	for (int i = 0;i < ELE_STR_SIZE;i++){
		for (int j = 0;j < ELE_STR_SIZE;j++){
			if (str_ele[i][j] == 0 || str_ele[i][j] == 1){
				if (str_ele[i][j] != src[i][j]) return MISS;
			}
		}
	}
	return HIT;
}

void hit_and_miss(IplImage *src,IplImage **dst,const unsigned char str_ele[][ELE_STR_SIZE])
{
	//对一个二值图像的hit_and_miss操作
	unsigned char matrix[ELE_STR_SIZE][ELE_STR_SIZE];
	int i1,j1,nx,ny;
	CvSize size = cvGetSize(src);
	if ((*dst) != NULL) cvReleaseImage(dst); 
	(*dst) = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
	cvZero(*dst);

	for (int i = 0;i<size.height;i++){
		for (int j = 0;j<size.width;j++){
			
			i1 = 0;
			j1 = 0;
			for (int k = 0;k < 9;k++){
				//把自身及相邻共九个格子放入一个矩阵中
				nx = i + dir[k][0];
				ny = j + dir[k][1];
				if (nx<0 || nx>=size.height || ny<0 || ny>=size.width) break;//超出边界,无需判断
				matrix[i1][j1] = CV_IMAGE_ELEM(src,uchar,nx,ny);
				++j1;
				if (j1 == ELE_STR_SIZE){
					++i1;
					j1 = 0;
				}
			}

			if (i1 != ELE_STR_SIZE){
				CV_IMAGE_ELEM(*dst,uchar,i,j) = 0;
			}
			else{
				CV_IMAGE_ELEM(*dst,uchar,i,j) = hit_and_miss(matrix,str_ele);
			}
		
		}
	} 
}

void biImageSubstract(IplImage *src1,IplImage *src2,IplImage **dst){
	//二值图像减法操作
	CvSize size1 = cvGetSize(src1);
	CvSize size2 = cvGetSize(src2);
	if ((*dst) != NULL) cvReleaseImage(dst);
	if ((size1.height != size2.height) || (size1.width != size2.width)){
		(*dst) = NULL;//大小不同则直接退出
		return;
	}

	//产生一张用于保存结果的空图
	(*dst) = cvCreateImage(cvGetSize(src1), IPL_DEPTH_8U, 1);
	cvZero(*dst);

	//按减法即为交补集的定义计算
	for (int i = 0;i<size1.height;i++){
		for (int j = 0;j<size1.width;j++){
			CV_IMAGE_ELEM(*dst,uchar,i,j) = CV_IMAGE_ELEM(src1,uchar,i,j) & (CV_IMAGE_ELEM(src2,uchar,i,j)^1);
		}
	} 
}

void biImageUnion(IplImage *src1,IplImage *src2,IplImage **dst){
	//二值图像的并操作
	CvSize size1 = cvGetSize(src1);
	CvSize size2 = cvGetSize(src2);
	if ((*dst) != NULL) cvReleaseImage(dst);
	if ((size1.height != size2.height) || (size1.width != size2.width)){
		(*dst) = NULL;//大小不同则直接退出
		return;
	}
	//产生一张用于保存结果的空图
	(*dst) = cvCreateImage(cvGetSize(src1), IPL_DEPTH_8U, 1);
	cvZero(*dst);
	for (int i = 0;i<size1.height;i++){
		for (int j = 0;j<size1.width;j++){
			CV_IMAGE_ELEM(*dst,uchar,i,j) = CV_IMAGE_ELEM(src1,uchar,i,j) | CV_IMAGE_ELEM(src2,uchar,i,j);
		}
	} 
}

bool equals(IplImage *src1,IplImage *src2){
	//判定两个二值图像是否相同
	CvSize size1 = cvGetSize(src1);
	CvSize size2 = cvGetSize(src2);
	if ((size1.height != size2.height) || (size1.width != size2.width)) return false; //大小不同则直接退出
	for (int i = 0;i<size1.height;i++){
		for (int j = 0;j<size1.width;j++){
			if (CV_IMAGE_ELEM(src1,uchar,i,j)!=CV_IMAGE_ELEM(src2,uchar,i,j)) return false; //判定到不同则直接退出
		}
	} 
	return true;
}

void thicken(IplImage *src,const unsigned char str_ele[][ELE_STR_SIZE],IplImage**dst){
	//二值图像thicken操作
	IplImage *hitnmiss = NULL;

	//产生一张用于保存结果的空图
	if ((*dst) != NULL) cvReleaseImage(dst);
	(*dst) = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
	cvZero(*dst);

	//按定义执行thicken操作
	hit_and_miss(src,&hitnmiss,str_ele);
	biImageUnion(src,hitnmiss,dst);

	cvReleaseImage(&hitnmiss);
}



void thin(IplImage *src,const unsigned char str_ele[][ELE_STR_SIZE],IplImage**dst){
	//二值图像thin操作
	IplImage *hitnmiss = NULL;

	//产生一张用于保存结果的空图
	if ((*dst) != NULL) cvReleaseImage(dst);
	(*dst) = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
	cvZero(*dst);

	//按定义执行thin操作
	hit_and_miss(src,&hitnmiss,str_ele);
	biImageSubstract(src,hitnmiss,dst);

	cvReleaseImage(&hitnmiss);
}

void skeleton(IplImage *src,IplImage **dst){
	//二值图像提取骨架操作
	bool flag = true;
	
	IplImage *cp_src = NULL;
	IplImage *tmp = NULL;

	//拷贝原图
	cp_src = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
	cvCopy(src,cp_src,NULL);
	
	//用给出的八个structuring element对原图不断做thin操作直至图片没有变化
	while (flag){
		flag = false;
		for (int i = 0;i < SKELETON_STR_ELE_NUM;i++){
				thin(cp_src,skeleton_str_ele[i],&tmp);
				if (!flag && !equals(cp_src,tmp)) flag = true;
				
				cvReleaseImage(&cp_src);
				cp_src = tmp;
			    tmp = NULL;
		}
	}

	if ((*dst) != NULL) cvReleaseImage(dst);
	(*dst) = cp_src;
}

void getBinaryImage(IplImage *src,IplImage **dst){
	IplImage *grayImage = NULL; 
	// 转为灰度图 
	grayImage =  cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);  
	cvCvtColor(src, grayImage, CV_BGR2GRAY);
	 
	 //创建二值图
	if ((*dst) != NULL) cvReleaseImage(dst);
	(*dst) = cvCreateImage(cvGetSize(grayImage), IPL_DEPTH_8U, 1);
	cvZero(*dst);//清零 
	cvThreshold(grayImage,*dst,128,1,CV_THRESH_BINARY_INV);
	
	cvReleaseImage(&grayImage);
}

void reverseBinaryImage(IplImage *src){
	//将一个二值图像反转
	CvSize size = cvGetSize(src);
	for (int i = 0;i < size.height;i++){
		for (int j = 0;j < size.width;j++){
			CV_IMAGE_ELEM(src,uchar,i,j) ^= 1;
		}
	}
}





void job1(){
	//提取土豆骨架
	//以二值图片src为输入,以提取到的skeleton以二值图片输出到dst中。
	//做法是在每次迭代中不断用八个Structuring Element去对原图做Thin操作,
	//直到某次迭代中图片不再有变化。
	IplImage *srcImage = cvLoadImage("potota.jpg", CV_LOAD_IMAGE_UNCHANGED);// 从文件中加载原图  
	IplImage *binaryImage = NULL; 
	IplImage *skeletonImage = NULL;
     if(srcImage == NULL)
     {//如果读入图像失败
         fprintf(stderr, "Can not load image\n");
         return;
     }
	 getBinaryImage(srcImage,&binaryImage);
	 reverseBinaryImage(binaryImage);//因为原土豆图黑底白色,将前后反转
	 

	 skeleton(binaryImage,&skeletonImage);//提取骨架
	 
	 cvReleaseImage(&binaryImage);


	 //把提取到的skeleton用绿色标注在原图上
	 CvSize size =cvGetSize(skeletonImage);
	 for (int i = 0;i < size.height;i++){
		 for (int j = 0;j < size.width;j++){
			 if (CV_IMAGE_ELEM(skeletonImage,uchar,i,j) == 1){
				CvScalar s = cvGet2D(srcImage,i,j);
				s = CV_RGB(0,255,0);
				cvSet2D(srcImage,i,j,s);
			 }
		 }
	 }

	 cvReleaseImage(&skeletonImage);

	 cvShowImage("Skeleton",srcImage);
	 cvWaitKey(0);
	 cvDestroyWindow("Skeleton");
	 
	 cvReleaseImage(&srcImage);
}

void job2(){
	//做一次thin操作
	//以二值图片src和结构元素str_ele作为输入,把操作结果以二值图片输出到dst中。
	//做法是先将原图和结果元素做一次Hit-and-Miss操作,再用原图减去操作结果。
	unsigned char str_ele[3][3] = {
									{2,1,2},
									{1,1,1},
									{2,1,2}
									};//操作用到的structuring element
	IplImage *srcImage = cvLoadImage("potota.jpg", CV_LOAD_IMAGE_UNCHANGED);// 从文件中加载原图  
	IplImage *binaryImage = NULL; 
	IplImage *thinImage = NULL;
     if(srcImage == NULL)
     {//如果读入图像失败
         fprintf(stderr, "Can not load image\n");
         return;
     }
	 getBinaryImage(srcImage,&binaryImage);
	 reverseBinaryImage(binaryImage);//因为原土豆图黑底白色,将前后反转
	 

	 thin(binaryImage,str_ele,&thinImage);//thin操作
	 
	 cvReleaseImage(&binaryImage);


	 //把thin操作的结果用绿色标注在原图上
	 CvSize size =cvGetSize(thinImage);
	 for (int i = 0;i < size.height;i++){
		 for (int j = 0;j < size.width;j++){
			 if (CV_IMAGE_ELEM(thinImage,uchar,i,j) == 1){
				CvScalar s = cvGet2D(srcImage,i,j);
				s = CV_RGB(0,255,0);
				cvSet2D(srcImage,i,j,s);
			 }
		 }
	 }

	 cvReleaseImage(&thinImage);

	 cvShowImage("Thin",srcImage);
	 cvWaitKey(0);
	 cvDestroyWindow("Thin");
	 
	 cvReleaseImage(&srcImage);
}

void job3(){
	//做一次thicken操作
	//以二值图片src和结构元素str_ele作为输入,把操作结果以二值图片输出到dst中。
	//做法是先将原图和结果元素做一次Hit-and-Miss操作,再用原图并上操作结果。
	unsigned char str_ele[3][3] = {
									{2,1,2},
									{1,1,1},
									{2,1,2}
									};//操作用到的structuring element
	IplImage *srcImage = cvLoadImage("potota.jpg", CV_LOAD_IMAGE_UNCHANGED);// 从文件中加载原图  
	IplImage *binaryImage = NULL; 
	IplImage *thickenImage = NULL;
     if(srcImage == NULL)
     {//如果读入图像失败
         fprintf(stderr, "Can not load image\n");
         return;
     }
	 getBinaryImage(srcImage,&binaryImage);
	 reverseBinaryImage(binaryImage);//因为原土豆图黑底白色,将前后反转
	 

	 thicken(binaryImage,str_ele,&thickenImage);//thicken操作
	 
	 cvReleaseImage(&binaryImage);


	 //把thicken操作的结果用绿色标注在原图上
	 CvSize size =cvGetSize(thickenImage);
	 for (int i = 0;i < size.height;i++){
		 for (int j = 0;j < size.width;j++){
			 if (CV_IMAGE_ELEM(thickenImage,uchar,i,j) == 1){
				CvScalar s = cvGet2D(srcImage,i,j);
				s = CV_RGB(0,255,0);
				cvSet2D(srcImage,i,j,s);
			 }
		 }
	 }

	 cvReleaseImage(&thickenImage);

	 cvShowImage("Thicken",srcImage);
	 cvWaitKey(0);
	 cvDestroyWindow("Thicken");
	 
	 cvReleaseImage(&srcImage);

}

int main()
{
	job1();
    job2();
	job3();
	return 0;
}


原图:


Skeleton效果:


Thin效果:


Thicken效果:


Thin和Thick使用的element::


提取skeleton使用的elemeng(每四个方向只画一个):



相关操作的概念见:http://homepages.inf.ed.ac.uk/rbf/HIPR2/morops.htm


  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值