OpenCV之图像特征提取与检测(十一) LBP特征

LBP(Local Binary Patterns) 局部二值模式特征,应用于很多领域,比如人脸识别,纹理检测,对象检测

假如有一个3x3的灰度图
这里写图片描述

首先对这张图做二值化,阈值选中心像素值—5,大于等于5的转化为1,小于的为0,得到一个二进制值( (0,0)位置最高位开始顺时针 ) 00010011,转换为十进制为19,此3x3的灰度图的LBP就为19,然后以此值替换中心像素值。

然后以卷积的方式对整张图进行这样的处理,最后就得到了整张图的LBP特征。

  • LBP表达
    这里写图片描述
    若取逆时针( (0,0)位置最低位 ) :
    Pattern = 11110001 (十进制为241)
    LBP = 1 + 16 + 32 + 64 + 128 = 241
    C = (6+7+8+9+7)/5 + (5+2+1)/3 = 4.7 (局部对比度,对比度小于2的可以认为是平坦区域,角点或边缘的对比度都会很高)

  • LBP扩展与多尺度表达
    这里写图片描述
    还可以取圆形像素点计算。
    LBP可以在不同尺度空间进行计算,验证是否有尺度不变性,同时可以用局部对比度验证光照不变性。

  • LBP统一模式
    这里写图片描述
    U表示pattern值由0到1,由1到0的转折次数。
    所有U=0,或U=2的都称为统一模式,其他情形的称为非统一模式。

  • LBP统一与非统一模式的直方图
    这里写图片描述
    直方图的bins :统一模式的情形有58种,所有非统一模式都放到1个bins中,所以 ULBP = 59 。
    用于纹理的匹配。仔细观察统一模式的情形,会发现各统一模式之间可以通过旋转来转换,此为LBP的旋转不变性。

至此,LBP特征也具备先前讲过的SURF,SIFT相同的关键特性,而且计算量比它们都要小很多,常用作于人脸检测,比Haar特征(因为有浮点运算)要快几倍。

代码

    #include "../common/common.hpp"

    static Mat gray;
    static const char title[] = "LBP features";
    static int current_radius = 3; // LBP拓展的圆形的半径
    static int max_radius = 20;

    static void m_lbp(int, void*);

    void main(int argc, char** argv)
    {
        gray = imread(getCVImagesPath("images/test1_3.png"), IMREAD_GRAYSCALE);
        imshow("src-15", gray);

        // LBP 基本演示
        int width = gray.cols;
        int height = gray.rows;
        Mat lbpImage_cw = Mat::zeros(gray.rows - 2, gray.cols - 2, CV_8UC1); // 顺时针,减2是因为卷积时,上下左右两行像素忽略掉
        Mat lbpImage_ccw = Mat::zeros(gray.rows - 2, gray.cols - 2, CV_8UC1); // 逆时针
        for (int row = 1; row < height - 1; row++) {
            for (int col = 1; col < width - 1; col++) {
                uchar center = gray.at<uchar>(row, col);
                uchar code_cw = 0;
                code_cw |= (gray.at<uchar>(row - 1, col - 1) >= center) << 7; // (0,0)位置最高位, 只判断大于 与 判断大于等于,图像结果差别很大
                code_cw |= (gray.at<uchar>(row - 1, col) >= center) << 6;
                code_cw |= (gray.at<uchar>(row - 1, col + 1) >= center) << 5;
                code_cw |= (gray.at<uchar>(row, col + 1) >= center) << 4;
                code_cw |= (gray.at<uchar>(row + 1, col + 1) >= center) << 3;
                code_cw |= (gray.at<uchar>(row + 1, col) >= center) << 2;
                code_cw |= (gray.at<uchar>(row + 1, col - 1) >= center) << 1;
                code_cw |= (gray.at<uchar>(row, col - 1) >= center) << 0;
                lbpImage_cw.at<uchar>(row - 1, col - 1) = code_cw;

                uchar code_ccw = 0;
                code_ccw |= (gray.at<uchar>(row - 1, col - 1) >= center) << 0; // (0,0)位置最低位
                code_ccw |= (gray.at<uchar>(row - 1, col) >= center) << 1;
                code_ccw |= (gray.at<uchar>(row - 1, col + 1) >= center) << 2;
                code_ccw |= (gray.at<uchar>(row, col + 1) >= center) << 3;
                code_ccw |= (gray.at<uchar>(row + 1, col + 1) >= center) << 4;
                code_ccw |= (gray.at<uchar>(row + 1, col) >= center) << 5;
                code_ccw |= (gray.at<uchar>(row + 1, col - 1) >= center) << 6;
                code_ccw |= (gray.at<uchar>(row, col - 1) >= center) << 7;
                lbpImage_ccw.at<uchar>(row - 1, col - 1) = code_ccw;
            }
        }
        imshow("LBP base cw", lbpImage_cw);
        imshow("LBP base ccw", lbpImage_ccw);

        // LBP扩展与多尺度表达
        namedWindow(title, CV_WINDOW_AUTOSIZE);
        createTrackbar("minHessian:", title, &current_radius, max_radius, m_lbp);
        m_lbp(0, 0);

        waitKey(0);
    }

    void m_lbp(int, void*)
    {
        int offset = current_radius * 2; // 边缘未计算的像素行列数
        Mat elbpImage = Mat::zeros(gray.rows - offset, gray.cols - offset, CV_8UC1); // 减去边缘未计算的像素行列数
        int width = gray.cols;
        int height = gray.rows;

        int numNeighbors = 8; // 为了简化算法,不管LBP圆形半径多大,P值都取8
        for (int n = 0; n < numNeighbors; n++) {
            // 对此有不明白的,去看看特殊角的sin,cos的值的表
            // sin(0-π)=0-1-0, sin(π-2π)=0-(-1)-0, cos(0-π)=1-0-(-1), cos(π-2π)=(-1)-0-1
            // 从x坐标正方向开始,逆时针选取像素坐标点, LBP选哪个点做起始高位点和沿哪个方向选择都无所谓?
            float x = static_cast<float>(current_radius) * cos(2.0 * CV_PI*n / static_cast<float>(numNeighbors)); // c++类型转换不建议用强转或隐式转换
            float y = static_cast<float>(current_radius) * -sin(2.0 * CV_PI*n / static_cast<float>(numNeighbors));

            // 取当前点的周围四个点,做双线性插值
            int fx = static_cast<int>(floor(x)); // 向下取整数,即不大于x的最大整数
            int fy = static_cast<int>(floor(y));
            int cx = static_cast<int>(ceil(x)); // 向上转整数
            int cy = static_cast<int>(ceil(y));

            float ty = y - fy; // 坐标的小数点部分
            float tx = x - fx;

            // 当前点的周围四个点的权重,这个四个点的权重加起来等于1
            float w1 = (1 - tx)*(1 - ty); // (0,0)
            float w2 = tx*(1 - ty); // (1,0)
            float w3 = (1 - tx)* ty; // (0,1)
            float w4 = tx*ty; // (1,1)

            for (int row = current_radius; row < (height - current_radius); row++) {
                for (int col = current_radius; col < (width - current_radius); col++) {
                    float t = w1* gray.at<uchar>(row + fy, col + fx) + w2* gray.at<uchar>(row + fy, col + cx) +
                        w3* gray.at<uchar>(row + cy, col + fx) + w4* gray.at<uchar>(row + cy, col + cx); // 权重计算当前点的像素值
                    uchar center = gray.at<uchar>(row, col); // 中心像素值
                    // numeric_limits C++11 模板类  std::numeric_limits<float>::epsilon()表示的是最小非0浮点数
                    // 三元表达式就是在判断 t >= center , 然后移位
                    // 浮点数间判断等于: == 完全一样才相等, std::numeric_limits<float>::epsilon() 差值若在浮点数的精度范围内表示相等
                    elbpImage.at<uchar>(row - current_radius, col - current_radius) +=
                        ((t > center) && (abs(t - center) > std::numeric_limits<float>::epsilon())) << n; // 移位相加,最终得到二进制转十进制的值
                }
            }
        }
        cout << std::numeric_limits<float>::epsilon() << endl; // 1.19209e-07  这里的e不是自然常数(2.71828),而是10,所以为 0.000000119209
        imshow(title, elbpImage);
    }

效果图

这里写图片描述

猜你喜欢

转载自blog.csdn.net/huanghuangjin/article/details/81275835