LBP特征提取实现运动目标检测跟踪,克服光照突变
局部二值模式(local binary pattern,LBP),其基本思想是将中心像素点的灰度值设为阈值,其圆形邻域内的像素点与之作比较得到二进制码用来表述局部纹理特征,不易受整幅图像灰度线性变化的影响。即当图像的灰度发生均匀变化时,其LBP纹理特征是不变的。因此LBP纹理特征具有灰度不变性。它计算简单,分类能力强,在描述纹理特征提取方面有着显著的效果。
针对常规算法不能适应环境光照带来图像亮度变化的问题,结合LBP纹理特征对光照的不敏感性,提出自适应背景更新策略,当环境光照突然变化的条件下,LBP纹理特征对维护背景具有鲁棒性,并且可以很好地定位前景位置和轮廓,提出了一个对光照变化鲁棒的运动目标检测算法。
算法步骤:
计算当前帧的LBP特征图像和背景帧的LBP特征图像;然后对两幅特征图像进行异或处理生成前景概率图像,前景概率图像描述了当前像素是前景的概率,根据前景概率图像自适应更新背景;最后用背景减除法或者差分得到运动目标,然后利用形态学处理凸显运动目标。
背景更新公式:
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include<opencv2/opencv.hpp>
#include <iostream>
#include <fstream>
#include <sstream>
using namespace cv;
using namespace std;
void elbp(Mat& src, Mat &dst, int radius, int neighbors)
{
for (int n = 0; n<neighbors; n++)
{
// 采样点的计算
float x = static_cast<float>(-radius * sin(2.0*CV_PI*n / static_cast<float>(neighbors)));
float y = static_cast<float>(radius * cos(2.0*CV_PI*n / static_cast<float>(neighbors)));
// 上取整和下取整的值
int fx = static_cast<int>(floor(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;
// 设置插值权重
float w1 = (1 - tx) * (1 - ty);
float w2 = tx * (1 - ty);
float w3 = (1 - tx) * ty;
float w4 = tx * ty;
// 循环处理图像数据
for (int i = radius; i < src.rows - radius; i++)
{
for (int j = radius; j < src.cols - radius; j++)
{
// 计算插值
float t = static_cast<float>(w1*src.at<uchar>(i + fy, j + fx) + w2*src.at<uchar>(i + fy, j + cx) + w3*src.at<uchar>(i + cy, j + fx) + w4*src.at<uchar>(i + cy, j + cx));
// 进行编码
dst.at<uchar>(i - radius, j - radius) += ((t > src.at<uchar>(i, j)) || (std::abs(t - src.at<uchar>(i, j)) < std::numeric_limits<float>::epsilon())) << n;
}
}
}
}
void elbp1(Mat& src, Mat &dst)
{
// 循环处理图像数据
for (int i = 1; i < src.rows - 1; i++)
{
for (int j = 1; j < src.cols - 1; j++)
{
uchar tt = 0;
int tt1 = 0;
uchar u = src.at<uchar>(i, j);
if (src.at<uchar>(i - 1, j - 1)>u) { tt += 1 << tt1; }
tt1++;
if (src.at<uchar>(i - 1, j)>u) { tt += 1 << tt1; }
tt1++;
if (src.at<uchar>(i - 1, j + 1)>u) { tt += 1 << tt1; }
tt1++;
if (src.at<uchar>(i, j + 1)>u) { tt += 1 << tt1; }
tt1++;
if (src.at<uchar>(i + 1, j + 1)>u) { tt += 1 << tt1; }
tt1++;
if (src.at<uchar>(i + 1, j)>u) { tt += 1 << tt1; }
tt1++;
if (src.at<uchar>(i + 1, j - 1)>u) { tt += 1 << tt1; }
tt1++;
if (src.at<uchar>(i - 1, j)>u) { tt += 1 << tt1; }
tt1++;
dst.at<uchar>(i - 1, j - 1) = tt;
}
}
}
int main()
{
//加载图像
Mat current_frame = cv::imread("D:/right/p4.jpg", 0); //img
Mat background_frame = cv::imread("D:/right/p1.jpg", 0);
//namedWindow("current_frame", 0);
//imshow("current_frame", current_frame);
// ROI处理,减少计算时间
//Rect rect(10, 2000, 4000, 1000);
Rect rect(10, 10, 1800, 800);
Mat current_roi = current_frame(rect);
Mat background_roi = background_frame(rect);
//namedWindow("circle", 0);
//imshow("circle", current_roi);
int radius, neighbors;
radius = 1;
neighbors = 8;
//创建一个LBP
//注意为了溢出,我们行列都在原有图像上减去2个半径
Mat current_dst = Mat(current_roi.rows - 2 * radius, current_roi.cols - 2 * radius, CV_8UC1, Scalar(0));
elbp1(current_roi, current_dst);
namedWindow("current_roi", 0);
imshow("current_roi", current_dst);
Mat background_dst = Mat(background_roi.rows - 2 * radius, background_roi.cols - 2 * radius, CV_8UC1, Scalar(0));
elbp1(background_roi, background_dst);
namedWindow("background_roi", 0);
imshow("background_roi", background_dst);
//异或处理
Mat xor_dst;
bitwise_xor(current_dst, background_dst, xor_dst);
namedWindow("xor_dst", 0);
imshow("xor_dst", xor_dst);
Mat update_background_dst;
/*Mat dst1 = Mat(current_roi.rows - 2 * radius, current_roi.cols - 2 * radius, CV_8UC1, Scalar(0));
elbp(current_roi, dst1, 1, 8);
namedWindow("circle", 0);
imshow("circle", dst1);*/
Mat current_back;
current_back = Mat::zeros(xor_dst.size(), CV_8U);
//计算当前像素为前景的概率;更新背景
//这里只检测两张图片,并没有循环处理更新背景,但是效果还可以,概率公式还需修改。
for (int i = 0; i < xor_dst.rows ; i++)
{
for (int j = 0; j < xor_dst.cols ; j++)
{
int gray_pixel = xor_dst.at<uchar>(i, j);
//统计二进制1的个数
int count_1 = 0;
while (gray_pixel)
{
count_1++;
gray_pixel = gray_pixel & (gray_pixel - 1);
}
float p_ij = count_1 / 8;
current_back.at<uchar>(i, j) = background_roi.at<uchar>(i, j) + (1 - p_ij)*(current_roi.at<uchar>(i, j) - background_roi.at<uchar>(i, j));
}
}
namedWindow("new_background", 0);
imshow("new_background", current_back);
Rect rect1(0, 0, xor_dst.cols, xor_dst.rows);
Mat test = current_roi(rect1);
//差分计算
Mat diff_result=Mat::zeros(xor_dst.size(), CV_8UC1);
absdiff(current_back, test, diff_result);
//形态学处理
Mat Morph_dst;
Mat element = getStructuringElement(MORPH_ELLIPSE, Size(15, 15));
//dilate(diff_result, Morph_dst, element);
morphologyEx(diff_result, Morph_dst, MORPH_CLOSE, element);
namedWindow("Morph_dst", 0);
imshow("Morph_dst", Morph_dst);
cv::waitKey(0);
}
针对光照问题,只要不是光照突变,例如直接从中午突变到夜晚,3号图即是,其他图均是光照强度明显不同,LBP特征提取都具有较好的效果。测试结果如下,大家可以自己用图片测试下: