我们在用一个算法对一幅图像进行分割之后,总会面临这样一个问题,分割的结果到底好不好。用眼睛可以看出好坏,但这只是主观的好坏,如何量化的对分割的结果进行评价呢,这是这篇文章我要讨论的主题。
我查阅过很多方法,包括ROC曲线,Dice重合率等等,要么是我理解不好,要么是难以实现。下面的代码,将基于GT(ground truth)图像计算分割图像的分割精度、过分割率、欠分割等指标来评估算法的分割结果。
首先简要介绍一下相关概念
GT(ground truth)图像:我理解的就是含有理论分割结果的图像,用来和结果图像进行比较的参照图像。那么GT图像怎么来,一般通过专家手工勾画出来,得到理论值。当然为避免偶然性,你可以选择多个专家,得到多个GT分割结果,取各项评估参数的平均值。
分割精度:个人理解就是分割准确的面积占GT图像中真实面积的百分比。找了半天找了这么个公式(如下)。
不难理解,其中Rs表示专家手工勾画出的分割图像的参考面积,Ts表示算法分割得到的图像的真实面积,|Rs-Ts|表示错误分割的像素点个数。
过分割率:即分割在GT图像参考面积之外的像素点的比率,计算公式如下:
含义同上,Os表示本不应该包含在分割结果中的像素点个数,实际却在分割结果中的像素点个数。换句话讲,Os中的像素点出现在实际分割图像中,但不出现在理论分割图像Rs中。
欠分割率:即分割在GT图像参考面积之中欠缺的像素点的比率,计算的公式如下:
含义同上,Us表示本应该包含在分割结果中的像素点个数,实际却不在分割结果中的像素点个数。换句话讲,Us中的像素点出现在理论分割图像中,但不出现在实际分割图像中。
好了,理论介绍完了,下面看一下效果和代码吧,我把我做头发分割的结果拿来做一下测试。
测试结果如下:
计算的各类参数如下:
如果大家对我头发分割的算法有疑问或者感兴趣,欢迎给我留言!
下面奉上我的代码:
/*************************************************************************
//为图像分割的结果提供评价标准
//预先人工绘制GT图像作为参考,与分割图像进行过分割率和欠分割率的计算
************************************************************************/
#include <opencv2/opencv.hpp>
#include <iostream>
#include <stdlib.h>
void getCounterImage(IplImage* src,IplImage* test,IplImage* dst);
using namespace std;
FILE *fp;
int main()
{
IplImage* testImg; // 测试图像;
IplImage* gtImg; //参照图像;
IplImage* counterImg; //轮廓图像;
fp = fopen("meanshift 方法.txt","w+");
fprintf(fp,"%s","图像编号 ");
fprintf(fp,"%s","分割准确率 ");
fprintf(fp,"%s","过分割率 ");
fprintf(fp,"%s","欠分割率 ");
int i = 1;
while(i<=5){
fprintf(fp,"%s","\n");
fprintf(fp,"%d",i);
char fileName[100] = "E:\\testdata\\Judge\\om_hair_dilate";
char gtFileName[100] = "E:\\testdata\\Judge\\gtHair";
char NumStr[10];
char JpgStr[10]=".jpg";
char ResStr[100]="res";
itoa(i,NumStr,10);
strcat(fileName,NumStr);
strcat(fileName,JpgStr);
strcat(gtFileName,NumStr);
strcat(gtFileName,JpgStr);
testImg = cvLoadImage(fileName);
gtImg = cvLoadImage(gtFileName);
counterImg = cvCreateImage(cvGetSize(gtImg),8,1); //单通道图像
cvZero(counterImg);
getCounterImage(gtImg,testImg,counterImg);
cvShowImage("testImage",testImg);
cvShowImage("gtImage",gtImg);
cvMoveWindow("gtImage",0,0);
cvReleaseImage(&counterImg);
char c=cvWaitKey();
if(32==c)
i++;
}
fclose(fp);
cvWaitKey(0);
}
void getCounterImage(IplImage* src,IplImage* test,IplImage* dst)
{
double SegRate; //分割准确率;
double OverSegRate; //过分割率;
double LessSegRate; //欠分割率;
CvScalar BLACK = CV_RGB(0,0,0);
CvScalar WHITE = CV_RGB(255,255,255);
for (int i = 0; i <src->height;i++)
for (int j = 0; j <src->width;j++)
{
CvScalar pixel = cvGet2D(src,i,j);
if(pixel.val[2]>200 && pixel.val[0]<50 && pixel.val[1]<50)
{
cvSet2D(dst,i,j,BLACK);
}
else
cvSet2D(dst,i,j,WHITE);
}
//cvShowImage("test",dst);
cvThreshold(dst,dst,150,255,CV_THRESH_BINARY_INV);
//查找轮廓;
CvSeq *pContour = NULL;
CvSeq *pConInner = NULL;
CvMemStorage *pStorage = NULL;
if(dst)
{
pStorage = cvCreateMemStorage(0);
cvFindContours(dst,pStorage,&pContour,sizeof(CvContour), CV_RETR_EXTERNAL , CV_CHAIN_APPROX_SIMPLE);
cvDrawContours(dst, pContour, CV_RGB(255, 255, 255), CV_RGB(255,255,255), -1, CV_FILLED, 8, cvPoint(0, 0));
}
cvNamedWindow("counter",0);
cvThreshold(dst,dst,150,255,CV_THRESH_BINARY_INV);
cvShowImage("counter",dst);
//cvShowImage("judge",test);
//cvSaveImage("gt1.jpg",dst);
/**开始计算过分割率和欠分割率**/
int x,y;
int SegNum=0; //分割准确的像素点个数;
int OverSegNum=0; //过分割的像素点个数;
int LessSegNum=0; //欠分割的像素点个数;
int GTNum=0; //真实像素点个数;
for (int i = 0; i <dst->height;i++)
for (int j = 0; j <dst->width;j++)
{
x = cvGet2D(test,i,j).val[0]; //分割图像
y = cvGet2D(dst,i,j).val[0]; //参照图像
if(x==0)
SegNum++;
if(y==0)
{
GTNum++;
if(x!=0)
LessSegNum++;
}
else if(x==0)
OverSegNum++;
}
SegRate = 1-(double)(abs(GTNum-SegNum))/GTNum; //分割准确率;
OverSegRate = (double)OverSegNum/(GTNum+OverSegNum); //过分割率;
LessSegRate = (double)LessSegNum/(GTNum+OverSegNum); //欠分割率;
cout<<"分割准确率:"<<SegRate<<endl;
cout<<"过分割率:"<<OverSegRate<<endl;
cout<<"欠分割率:"<<LessSegRate<<endl;
fprintf(fp,"%s"," ");
fprintf(fp,"%f",SegRate);
fprintf(fp,"%s"," ");
fprintf(fp,"%f",OverSegRate);
fprintf(fp,"%s"," ");
fprintf(fp,"%f",LessSegRate);
}
Junzzy所写,欢迎大家指正!