OpenCV学习之图像分割

来源:互联网 发布:淘宝网购物车已买到的宝贝 编辑:程序博客网 时间:2024/06/05 06:17

一、边缘提取常用算子

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.tifIplImage* 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);//核的大小为3cvSmooth(image, imageLOG, CV_GAUSSIAN, 3, 3);cvLaplace(imageLOG, imageLOG, 3);//核的大小为3cvNamedWindow("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";//fingerconst 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 : 0CV_THRESH_BINARY_INV  =1,   value = value > threshold ? 0 : max_valueCV_THRESH_TOZERO =3,        value = value > threshold ? value : 0CV_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);//OstuIplImage* 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();}
结果:可以看出自适应阈值对于‘光照下的英文’分割得更好。