zoukankan      html  css  js  c++  java
  • otsu自适应阈值分割的算法描述和opencv实现,及其在肤色检测中的应用[转]

    otsu算法选择使类间方差最大的灰度值为阈值,具有很好的效果
    算法具体描述见otsu论文,或冈萨雷斯著名的数字图像处理那本书
    这里给出程序流程:
    1、计算直方图并归一化histogram
    2、计算图像灰度均值avgValue.
    3、计算直方图的零阶w[i]和一级矩u[i]
    4、计算并找到最大的类间方差(between-class variance)
    variance[i]=(avgValue*w[i]-u[i])*(avgValue*w[i]-u[i])/(w[i]*(1-w[i]))
    对应此最大方差的灰度值即为要找的阈值
    5、用找到的阈值二值化图像

    我在代码中做了一些优化,所以算法描述的某些地方跟程序并不一致

    otsu代码,先找阈值,继而二值化

    // implementation of otsu algorithm
    // author: onezeros(@yahoo.cn)
    // reference: Rafael C. Gonzalez. Digital Image Processing Using MATLAB
    void cvThresholdOtsu(IplImage* src, IplImage* dst)
    {
        int height=src->height;
        int width=src->width;   
        //histogram
        float histogram[256]={0};
        for(int i=0;i<height;i++) {
            unsigned char* p=(unsigned char*)src->imageData+src->widthStep*i;
            for(int j=0;j<width;j++) {
                histogram[*p++]++;
            }
        }
        //normalize histogram
        int size=height*width;
        for(int i=0;i<256;i++) {
            histogram[i]=histogram[i]/size;
        }
        //average pixel value
        float avgValue=0;
        for(int i=0;i<256;i++) {
            avgValue+=i*histogram[i];
        }

        int threshold;   
        float maxVariance=0;
        float w=0,u=0;
        for(int i=0;i<256;i++) {
            w+=histogram[i];
            u+=i*histogram[i];

            float t=avgValue*w-u;
            float variance=t*t/(w*(1-w));
            if(variance>maxVariance) {
                maxVariance=variance;
                threshold=i;
            }
        }

        cvThreshold(src,dst,threshold,255,CV_THRESH_BINARY);
    }

    更多情况下我们并不需要对每一帧都是用otsu寻找阈值,于是可以先找到阈值,然后用找到的阈值处理后面的图像。下面这个函数重载了上面的,返回值就是阈值。只做了一点改变

    // implementation of otsu algorithm
    // author: onezeros(@yahoo.cn)
    // reference: Rafael C. Gonzalez. Digital Image Processing Using MATLAB
    int cvThresholdOtsu(IplImage* src)
    {
        int height=src->height;
        int width=src->width;   

        //histogram
        float histogram[256]={0};
        for(int i=0;i<height;i++) {
            unsigned char* p=(unsigned char*)src->imageData+src->widthStep*i;
            for(int j=0;j<width;j++) {
                histogram[*p++]++;
            }
        }
        //normalize histogram
        int size=height*width;
        for(int i=0;i<256;i++) {
            histogram[i]=histogram[i]/size;
        }

        //average pixel value
        float avgValue=0;
        for(int i=0;i<256;i++) {
            avgValue+=i*histogram[i];
        }

        int threshold;   
        float maxVariance=0;
        float w=0,u=0;
        for(int i=0;i<256;i++) {
            w+=histogram[i];
            u+=i*histogram[i];

            float t=avgValue*w-u;
            float variance=t*t/(w*(1-w));
            if(variance>maxVariance) {
                maxVariance=variance;
                threshold=i;
            }


        }

        return threshold;
    }

    我在手的自动检测中使用这个方法,效果很好。

    下面是使用上述两个函数的简单的主程序,可以试运行一下,如果处理视频,要保证第一帧时,手要在图像中。

    #include <cv.h>
    #include <cxcore.h>
    #include <highgui.h>
    #pragma comment(lib,"cv210d.lib")
    #pragma comment(lib,"cxcore210d.lib")
    #pragma comment(lib,"highgui210d.lib")

    #include <iostream>
    using namespace std;

    int main(int argc, char** argv)
    {   
    #ifdef VIDEO //video process
        CvCapture* capture=cvCreateCameraCapture(-1);
        if (!capture){
            cout<<"failed to open camera"<<endl;
            exit(0);
        }

        int threshold=-1;
        IplImage* img;   
        while (img=cvQueryFrame(capture)){
            cvShowImage("video",img);
            cvCvtColor(img,img,CV_RGB2YCrCb);

            IplImage* imgCb=cvCreateImage(cvGetSize(img),8,1);
            cvSplit(img,NULL,NULL,imgCb,NULL);
            if (threshold<0){
                threshold=cvThresholdOtsu(imgCb);
            }
            //cvThresholdOtsu(imgCb,imgCb);
            cvThreshold(imgCb,imgCb,threshold,255,CV_THRESH_BINARY);
            cvErode(imgCb,imgCb);
            cvDilate(imgCb,imgCb);
            cvShowImage("object",imgCb);
            cvReleaseImage(&imgCb);

            if (cvWaitKey(3)==27){//esc
                break;
            }
        }   

        cvReleaseCapture(&capture);
    #else //single image process
        const char* filename=(argc>=2?argv[1]:"cr.jpg");
        IplImage* img=cvLoadImage(filename,CV_LOAD_IMAGE_GRAYSCALE);

        cvThresholdOtsu(img,img);
        cvShowImage( "src", img );
        char buf[256];
        sprintf_s(buf,256,"%s.otsu.jpg",filename);
        cvSaveImage(buf,img);

        cvErode(img,img);
        cvDilate(img,img);
        cvShowImage( "dst", img );
        sprintf_s(buf,256,"%s.otsu.processed.jpg",filename);
        cvSaveImage(buf,img);

        cvWaitKey(0);
    #endif
        return 0;
    }

    效果图:

    1、肤色cb分量

    2、otsu自适应阈值分割效果

    3、开运算后效果

  • 相关阅读:
    error和exception有什么区别?
    运行时异常与一般异常有何异同?
    由c++循环中局部变量地址不变而引发的思考
    Navicat连接Mysql数据库报错,但是命令行可以连接上
    git reset --hard HEAD^后显示more?的解决方案
    java基础易错、难理解、易混淆知识点复习
    More than one file was found with OS independent path 'assets/ap1.data'
    UML类图中方法(操作)的表示格式
    正则匹配以xx开头以xx结尾的单词
    运行PL/SQL时只输出anonymous block completed
  • 原文地址:https://www.cnblogs.com/freedesert/p/2675645.html
Copyright © 2011-2022 走看看