基于OpenCV的火焰检测(三)——HSI颜色判据

上文向大家介绍了如何用最简单的RGB判据来初步提取火焰区域,现在我要给大家分享的是一种更加直观的判据——HSI判据。
    为什么说HSI判据是更加直观的判据呢?老规矩,先介绍一下HSI色彩模型:

    HSI颜色模型用H、S、I三参数描述颜色特性,其中:
    H表示颜色的色调,它表示人的感官对不同颜色的感受,如红色、绿色、蓝色等,它也可表示一定范围的颜色,如暖色、冷色等。
    H的单位是°,代表与红轴的角度。

这里写图片描述

    S表示颜色的饱和度,纯光谱色是完全饱和的,加入白光会稀释饱和度。饱和度越大,颜色看起来就会越鲜艳。
    I对应成像亮度和图像灰度。

    HSI模型的建立基于两个重要的事实: ① I分量与图像的彩色信息无关;② H和S分量与人感受颜色的方式是紧密相联的。
    这些特点使得HSI模型非常适合彩色特性检测与分析。

    对比一下RGB和HSI模型:

这里写图片描述

    得出由RGB模型转化为HSI模型的公式:

这里写图片描述

    假设R、G、B分量已经归一化到[0,1],那么求出来的S分量和I分量的值也会被归一化到[0,1]。当S分量为0的时候,对应的H分量也应该为0。

    用OpenCV1.0可以根据上面的公式和条件写出模型转化函数的代码:
int cvBGR2HSI(IplImage*img_bgr, IplImage*img_hsi){
    if (img_bgr == NULL || img_hsi == NULL){
        printf("func cvBGR2HSI Error:\n");
        printf("img_bgr == NULL || img_hsi == NULL\n");
        return -1;
    }

    if (img_bgr->nChannels != 3 || img_hsi->nChannels != 3){
        printf("func cvBGR2HSI Error:\n");
        printf("img_bgr->nChannels != 3 || img_hsi->nChannels != 3\n");
        return -1;
    }

    double eps = 1.111e-016;
    double pi = 3.1416;
    double num = 0;
    double den = 0;
    double theta = 0;
    double R, G, B, H, S, I;
    for (int i = 0; i < img_bgr->height; i++){
        uchar*ptr1 = (uchar*)(img_bgr->imageData + i*img_bgr->widthStep);
        uchar*ptr2 = (uchar*)(img_hsi->imageData + i*img_hsi->widthStep);
        for (int j = 0; j < img_bgr->width; j++){
            R = double(ptr1[3 * j + 2]) / 255.0;
            G = double(ptr1[3 * j + 1]) / 255.0;
            B = double(ptr1[3 * j + 0]) / 255.0;
            num = 0.5*((R - G) + (R - B));
            den = sqrt((R - G)*(R - G) + (R - B)*(G - B));
            theta = acos(num / (den + eps));

            H = theta;
            if (B>G){
                H = 2 * pi - H;
            }
            H = H / (2 * pi);
            num = min(min(R, G), B);
            den = R + G + B;
            if (den < eps){
                den = eps;
            }
            S = 1 - 3 * num / den;
            if (S < eps){
                H = 0;
            }
            I = (R + G + B) / 3.0;

            ptr2[3 * j + 0] = H * 360;
            ptr2[3 * j + 1] = S * 255;
            ptr2[3 * j + 2] = I * 255;
        }
    }
    return 0;
}
   在代码中要注意的是在OpenCV中RGB三通道的排列顺序:先存放B分量,再存放G分量,最后才是R分量,这与MATLAB里面的规则是不同的。

    有了模型转化函数之后,我们就可以在HSI色彩模型内使用HSI判据了。HSI判据很简单,也很直观,它的规则是每个分量设定两个阈值,满足
阈值条件的置1,不满足的置0。具体表达式如下:
                        H_min < H < H_max  AND
                        S_min < S < S_max  AND
                        I_min < I < I_max

    在OpenCV1.0中可以轻松写出上面的代码:
int cvHSI_CHK(IplImage*img_hsi, IplImage*hsi_chk, int H_min, int H_max, int S_min, int S_max, int I_min, int I_max)
{
    if (img_hsi == NULL || hsi_chk == NULL){
        printf("func cvHSI_CHK Error:\n");
        printf("img_hsi == NULL || hsi_chk == NULL)\n");
        return -1;
    }

    if (img_hsi->nChannels != 3 || hsi_chk->nChannels != 1){
        printf("func cvHSI_CHK Error:\n");
        printf("img_hsi->nChannels != 3 || hsi_chk->nChannels != 1)\n");
        return -1;
    }

    CvSize size = cvGetSize(img_hsi);

    IplImage*H = cvCreateImage(size, 8, 1);
    IplImage*S = cvCreateImage(size, 8, 1);
    IplImage*I = cvCreateImage(size, 8, 1);
    IplImage*mask = cvCreateImage(size, 8, 1);

    cvSplit(img_hsi, H, S, I, NULL);

    cvCmpS(H, H_min, mask, CV_CMP_GE);

    cvCmpS(H, H_max, H, CV_CMP_LE);
    cvMul(H, mask, H);

    cvCmpS(S, S_min, mask, CV_CMP_GE);
    cvCmpS(S, S_max, S, CV_CMP_LE);
    cvMul(S, mask, S);

    cvCmpS(I, I_min, mask, CV_CMP_GE);
    cvCmpS(I, I_max, I, CV_CMP_LE);
    cvMul(I, mask, I);

    cvMul(H, S, H);
    cvMul(H, I, hsi_chk);
    cvConvertScale(hsi_chk, hsi_chk, 1.0 / 255);    

    cvReleaseImage(&H);
    cvReleaseImage(&S);
    cvReleaseImage(&I);
    cvReleaseImage(&mask);

    return 0;
}
 根据实验经验,博主给出一组参考阈值:
    H_min = 0    H_max = 60          
    S_min = 20   S_max = 100
    I_min = 100  I_max = 255

    参考上面给出的色相环可以轻松设定H分量的两个阈值,如果是想要检测其他颜色的火焰,可以根据需要做修改。
    S分量的阈值较小,这与火焰所处的环境有关,若是在露天较明亮的地方检验,则需要调低一点;若在室内较暗的地方检验,则需要调高一点。
    I分量的阈值较大,依据这么一个先验知识:火焰是会发光的。

    最后,我们代入参考阈值检验一下图片看看效果如何:

这里写图片描述

这里写图片描述

    我们可以看出,火焰的基本轮廓都提取出来了。若大家想要把整个区域都提取出来,可以自行尝试其它阈值。

posted @ 2016-05-30 12:20  隔壁王叔叔a  阅读(1351)  评论(0编辑  收藏  举报