OpenCV学习之图像分割

一、边缘提取常用算子

1、sobel算子边缘检测

//Sobel梯度算子
void imageSobel(){
	const char* name = "lena.tif";
	IplImage* image = cvLoadImage(name, CV_LOAD_IMAGE_GRAYSCALE);
	if (image == NULL){
		printf("image load failed.\n");
		return;
	}
	IplImage* image3 = cvCreateImage(cvGetSize(image), image->depth, image->nChannels);
	IplImage* image5 = cvCreateImage(cvGetSize(image), image->depth, image->nChannels);
	int gx = 1, gy = 1;
	//gx x方向上的导数阶数  gy  y方向上的导数阶数 ,sobel一阶微分算子
	//小核对噪声更敏感
	cvSobel(image, image3, gx, gy, 3);//3*3的卷积核
	cvSobel(image, image5, gx, gy, 5);//5*5的卷积核

	cvNamedWindow("Origin", CV_WINDOW_AUTOSIZE);
	cvNamedWindow("Sobel3", CV_WINDOW_AUTOSIZE);
	cvNamedWindow("Sobel5", CV_WINDOW_AUTOSIZE);
	cvShowImage("Origin", image);
	cvShowImage("Sobel3", image3);
	cvShowImage("Sobel5", image5);
	cvWaitKey(); 

	cvDestroyWindow("Origin");
	cvDestroyWindow("Sobel5");
	cvDestroyWindow("Sobel3");
	cvReleaseImage(&image);
	cvReleaseImage(&image3);
	cvReleaseImage(&image5);
}

结果:默然核的大小是3(左图),右图是5*5的核,边缘要粗些。




2、拉普拉斯变换

//拉普拉斯算子
void imageLaplasi(){
	const char* name = "lena.tif";//house.tif
	IplImage* image = cvLoadImage(name, CV_LOAD_IMAGE_GRAYSCALE);
	if (image == NULL){
		printf("image load failed.\n");
		return;
	}
	IplImage* imageG = cvCreateImage(cvGetSize(image), image->depth, image->nChannels);
	IplImage* imageLOG = cvCreateImage(cvGetSize(image), image->depth, image->nChannels);
	
	
	//拉普拉斯算子,二阶微分算子,对噪声比较敏感,一般不直接用于边的检测,常使用高斯拉普拉斯LOG算子
	cvLaplace(image, imageG, 3);//核的大小为3

	cvSmooth(image, imageLOG, CV_GAUSSIAN, 3, 3);
	cvLaplace(imageLOG, imageLOG, 3);//核的大小为3
	
	
	cvNamedWindow("Origin", CV_WINDOW_AUTOSIZE);
	cvNamedWindow("Laplasi", CV_WINDOW_AUTOSIZE);
	cvNamedWindow("LOG", CV_WINDOW_AUTOSIZE);
	cvShowImage("Origin", image);
	cvShowImage("Laplasi", imageG);
	cvShowImage("LOG", imageLOG);
	
	cvWaitKey();

	cvDestroyWindow("Origin");
	cvDestroyWindow("Laplasi");
	cvDestroyWindow("LOG");
	
	cvReleaseImage(&image);
	cvReleaseImage(&imageG);
	cvReleaseImage(&imageLOG);
}


结果:结果显示先对图像进行高斯平滑,对于边缘提取的效果更好



3、Canny边缘检测

IplImage* g_img=NULL;
IplImage* g_imgDst = NULL;
const char* g_windowName = "Canny";

//通过进度条调整阈值
void CannyTrackBar(int threshold){
	/*void cvCanny( const CvArr* image,CvArr* edges,double threshold1,double threshold2, int aperture_size=3 )
	threshold1:阈值1,   threshold2;阈值2     aperture_size:Sobel 算子大小,默认为3即表示一个3*3的矩阵
	双阈值中小的用来控制边缘连接,大的用来控制强边缘的初始分割.小于下限阈值,则被抛弃
	即阈值1越大,则被抛弃的像素越多。假设阈值1小于阈值2
	*/
	cvCanny(g_img, g_imgDst, threshold, threshold * 3, 3);
	cvShowImage(g_windowName, g_imgDst);
}

//Canny边缘检测
void imageCanny(){
	const char* name = "lena.tif";
	IplImage* image = cvLoadImage(name, CV_LOAD_IMAGE_GRAYSCALE);
	if (image == NULL){
		printf("image load failed.\n");
		return;
	}
	g_img = image;
	g_imgDst = cvCreateImage(cvGetSize(image), image->depth, image->nChannels);

	cvNamedWindow("Source", CV_WINDOW_AUTOSIZE);
	cvShowImage("Source", image);

	//在窗口中创建进度条
	cvNamedWindow(g_windowName, CV_WINDOW_AUTOSIZE);
	int start = 10, count = 120;
	const char* barName = "threshold";
	cvCreateTrackbar(barName, g_windowName,  &start, count, CannyTrackBar);
	
	cvWaitKey();
	cvReleaseImage(&image);
	cvDestroyWindow("Source");
	cvDestroyWindow(g_windowName);
}
 结果:从左至右,依次是低阈值为15,30,50的结果,可以看出,阈值越大,被抛弃的细边缘越多。





二、基于阈值的分割

1、全局阈值分割

Step1:设定阈值T,G1由大于T的像素组成,G2由小于T的像素组成

Step2:求取G1的均值m1,G2的均值m2

Step3:计算新的阈值T1 = (m1+m2)/2;

Step4:迭代,直至T1-T <=某个值。

2、OSTU法(最大类间方差法)

通过计算前景和背景的均值、方差,前景和背景的方差越大,说明图像的两部分差别越大,类间方差越大意味着错分概率越小。具体原理可在网上查阅相关资料。
OpenCV提供cvThreshold()进行阈值分割。

Step1:计算归一化的直方图,统计直方图的各个分量

Step2:阈值k,计算小于k的累积概率-P1, 和大于k的累积概率-P2

Step3:计算均值m1,m2,以及全局均值m2.

Step4:根据公式( P1(m1-mg)^2 +P2(m2-mg)^2 )计算类间方差,

Step5:迭代step2,step3,得到可分性度量(最佳阈值)。


实现代码如下:
int myOstuMethod(IplImage* image){
	int width = image->width;
	int height = image->height;
	int pixSum = width * height;
	int kValue = 0;
	uchar* data = NULL;
	data = (uchar*)malloc(width * height * sizeof(uchar));
	data = (uchar*)image->imageData;
	const int Gray = 256;
	int pixCount[Gray];//各个灰度级的统计值
	float pCount[Gray];//各个灰度级的出现概率
	//初始化
	for (int i = 0; i < Gray; i++){
		pixCount[i] = 0;
		pCount[i] = 0;
	}
	//统计各个灰度级出现的次数
	for (int i = 0; i < height; i++){
		for (int j = 0; j < width; j++){
			pixCount[ (int)data[i*width + j] ]++;
		}
	}

	//计算各个灰度级的概率
	for (int i = 0; i < Gray; i++){
		pCount[i] = (float)pixCount[i] / pixSum;
	}

	//计算类1和类2的类间方差和平均灰度
	float m1, m2, mg, P1, P2, u1, u2, segma, segmaMax;
	segmaMax = 0;
	for (int i = 0; i<Gray; i++){
		m1 = 0;//类一的均值
		m2 = 0;//类二的均值
		mg = 0;//全局均值
		P1 = 0;//类一的累积概率
		P2 = 0;//类二的累积概率
		u1 = 0;
		u2 = 0;
		segma = 0;
		for (int j = 0; j < Gray; j++){
			//比如阈值为100,统计小于100和大于100的
			if (j <= i){
				P1 += pCount[j];
				u1 += pCount[j] * j;
			}
			else{
				P2 += pCount[j];
				u2 += pCount[j] * j;
			}
		}
		m1 = u1 / P1;
		m2 = u2 / P2;
		mg = u1 + u2;
		//类间方差
		segma = P1*(m1 - mg)*(m1 - mg) + P2*(m2 - mg)*(m2 - mg);

		if (segma > segmaMax){
			segmaMax = segma;
			kValue = i;
		}
		//printf("segma %d :%f\n", i, segma);
	}
	return kValue;
}
结果:自己写的代码求得的阈值和matlab  [ graythresh(I)]求得的阈值一样


下面是全局阈值和OStu法的分割实现:

/阈值分割
void imageBinaryzation(){
	const char* name = "finger.tif";//finger
	const char* name1 = "cell.tif";
	IplImage* img = cvLoadImage(name, CV_LOAD_IMAGE_GRAYSCALE);
	IplImage* img1 = cvLoadImage(name1, CV_LOAD_IMAGE_GRAYSCALE);
	if (img == NULL || img1 == NULL){
		printf("image load failed.\n");
		return;
	}

	/* CV_THRESH_BINARY =0,        value = value > threshold ? max_value : 0
	CV_THRESH_BINARY_INV  =1,   value = value > threshold ? 0 : max_value
	CV_THRESH_TOZERO =3,        value = value > threshold ? value : 0
	CV_THRESH_OTSU   =8
	*/
	//全局阈值
	IplImage* imgDst = cvCreateImage(cvGetSize(img), IPL_DEPTH_8U, 1);
	double thresholdValue = 110;
	double maxValue = 255;
	cvThreshold(img, imgDst, thresholdValue, maxValue, CV_THRESH_BINARY);
	cvNamedWindow("Source", 0);
	cvNamedWindow("Binary", 0);
	cvShowImage("Source", img);
	cvShowImage("Binary", imgDst);


	//Ostu
	IplImage* imgOstu = cvCreateImage(cvGetSize(img1), IPL_DEPTH_8U, 1);
	cvThreshold(img1, imgOstu, 0, maxValue, CV_THRESH_OTSU);//使用CV_THRESH_OTSU时,参数中的阈值不再起作用
	cvNamedWindow("Source1", 0);
	cvNamedWindow("Ostu", 0);
	cvShowImage("Source1", img1);
	cvShowImage("Ostu", imgOstu);

	cvWaitKey();
	cvDestroyWindow("Source");
	cvReleaseImage(&img);
	cvDestroyWindow("Binary");
	cvReleaseImage(&imgDst);

}




3、自适应阈值

openCV采用cvAdaptiveThreshold()函数实现,针对有强照明或者反射梯度的图像,需要根据梯度阈值化时,自适应阈值非常有用。

自适应阈值与全局阈值的比较:

//自适应阈值分割
void imageThresholdComp(){
	const char* name = "word.jpg";
	IplImage* img = cvLoadImage(name, CV_LOAD_IMAGE_GRAYSCALE);
	if (img == NULL){
		printf("image load failed.\n");
		return;
	}

	IplImage* imgDst1 = cvCreateImage(cvGetSize(img), IPL_DEPTH_8U, 1);
	IplImage* imgDst2 = cvCreateImage(cvGetSize(img), IPL_DEPTH_8U, 1);
	double thresholdValue = 120;
	double maxValue = 255;
	

	cvThreshold(img, imgDst1, thresholdValue, maxValue, CV_THRESH_BINARY_INV);//CV_THRESH_BINARY_INV 像素值反转

	//测试发现:相比blocksize=3,  blockSize =7时字母线条更加粗些,识别性更高些
	//测试CV_ADAPTIVE_THRESH_MEAN_C 效果好于  CV_ADAPTIVE_THRESH_GAUSSIAN_C,字母更加清晰连贯
	int blockSize = 7;
	cvAdaptiveThreshold(img, imgDst2, maxValue, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY_INV, blockSize, 5);

	cvNamedWindow("Source1", CV_WINDOW_AUTOSIZE);
	cvNamedWindow("Ans1", CV_WINDOW_AUTOSIZE);
	cvNamedWindow("Ans2", CV_WINDOW_AUTOSIZE);
	cvShowImage("Source1", img);
	cvShowImage("Ans1", imgDst1);
	cvShowImage("Ans2", imgDst2);
	cvWaitKey();
}
结果:可以看出自适应阈值对于‘光照下的英文’分割得更好。



  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值