复杂动背景下的“低小慢”目标检测技术

复杂动背景下的“低小慢”目标检测技术,论文阅读和复现代码

本文的目的是要检测低空中的慢速目标,我复现了本文算法(修正了一些模块),下面给出复现的细节。

  1. 视觉显著性模型

视觉显著性反应了人眼感知系统对视觉信号灯响应。原文给出的模型描述存在问题
正确的模型如下:
S ( x , y ) = ∑ i = 0 255 f i C ( I ( x , y ) , i ) S(x, y) = \sum_{i=0}^{255} f_i C(I(x, y), i) S(x,y)=i=0255fiC(I(x,y),i)

其中, I ( x , y ) I(x, y) I(x,y)表示点x, y处的像素点值, f f f表示灰度直方图, f i f_i fi表示灰度等级i点频数。C(.,.)表示距离度量,一般地
C ( a , b ) = ∣ a − b ∣ C(a, b) = |a - b| C(a,b)=ab

S ( x , y ) S(x, y) S(x,y)的取值范围超出了255,因此要将其极差归一化到[0, 255]区间。

函数原型:cv::Mat computeSaliencyMap(cv::Mat& image);

  1. 形态学梯度

形态学梯度可以获取目标的边缘轮廓信息,本质上是膨胀结果减去腐蚀结果,函数原型:cv::Mat computeGrad(cv::Mat& image);

  1. 三帧差分

论文里面三帧差分是差分后的二值图,原理可参考原文。函数原型:cv::Mat computeDiff3(cv::Mat& image0, cv::Mat& image1, cv::Mat& image2);

  1. 区域生长(没有用到)

论文里面对区域生长这块的描述不清晰,特别是种子如何选取的问题,原文是没有描述的。我复现了经典的区域生长算法。发现效果不理想,分析原因是:三帧差分的值会在目标的边缘外部是255。
意味着如果将这些点当做是种子点,那么目标边缘外部会向四周生长,如果背景比较类似的话,背景全部会生长;另外,由于三帧差分的值在物体内部也极有可能有255。所以,大概率区域生长的结果是全图变白。

尝试改进方案1:腐蚀+膨胀+连通结构计算,得出目标质心,将质心当成种子迭代生长。
这种方法存在偶然性,因为种子较少,如果种子所在的像素点恰好和周围的像素点差别很大,那么将完全无法生长。

所以我的原始实现没有采用区域生长算法,而是做了一些改进。不搞那些虚的,直接针对三帧差分的结果腐蚀+膨胀+计算连通结构,连通结构即为检测到的目标。

函数原型:vetor<cv::Rect> getObjRects(cv::Mat srcImage, cv::Mat mask);

区域生长算法的函数原型:cv::Mat alg_utils::regionGrow(cv::Mat srcImage, cv::Point pt, int ch1Thres,int ch2Thres, int ch3Thres);

  1. 合并距离比较近近的矩形框

在前面生成的结果中,可能存在着一些零散的小矩形框挨的比较近,一个合理的办法是将这些矩形框合并。

函数原型: vector<cv::Rect> combineNearRects();

下面给出完整代码

// LSSDetector1.h
/**
 * 
 * 复杂动背景下的“低小慢”目标检测技术,吴言枫, 王延杰, 孙海江, 刘培勋。
 * 该算法实现完成后,效果并不好,分析原因是:三帧差分的值会在目标的边缘外部是255。
 * 意味着如果将这些点当做是种子点,那么目标边缘外部会向四周生长,如果背景比较类似的话,背景全部会生长;
 * 另外,由于三帧差分的值在物体内部也极有可能有255。所以,大概率区域生长的结果是全图变白。
 * 改进:直接将三帧差分的二值图做腐蚀膨胀操作,然后计算连通图区域,即为目标的bbox。
 */
#ifndef LSSDETECTOR1_H_
#define LSSDETECTOR1_H_

#include "detectors/detector.h"

using namespace std;
class LSSDetector1 : public Detector
{
public:
    LSSDetector1(bool debug);
    void init(); // 初始化方法
    void detect(cv::Mat& image); // 检测方法
    cv::Mat computeSaliencyMap(cv::Mat& image); //计算显著性图像
    cv::Mat computeGrad(cv::Mat& image); // 获取形态学梯度图
    cv::Mat computeDiff3(cv::Mat& image0, cv::Mat& image1, cv::Mat& image2); //计算三帧差分
    vector<cv::Rect> getObjRects(cv::Mat srcImage, cv::Mat mask);
    vector<cv::Rect> combineNearRects(); // 连接比较近的矩形框
    bool needCombine(cv::Rect r1, cv::Rect r2);
    cv::Rect combine(vector<cv::Rect> rects);

protected:
    cv::Mat image;
    cv::Mat saliencyMap;
    cv::Mat diff3;
    cv::Size_<int> kernel_size;
    uint8_t Q; // 三帧差分阈值
    cv::Mat grad; // 形态学梯度图
    double connectRectsRatio; // 合并矩形框的阈值,基于视频图像长宽的比例, 小于connectRectsRatio * imageWidth或者imageHeight 距离的矩形框会被合并
    vector<cv::Rect> objRects;
    vector<cv::Rect> combinedRects;
};

#endif
// LSSDetector1.cpp
#include "LSSDetector1.h"

LSSDetector1::LSSDetector1(bool debug)
{
    DEBUG = debug;
}

void LSSDetector1::init()
{
    kernel_size=cv::Size_<int>(5, 5);
    Q = (uint8_t)30;
    connectRectsRatio = 0.1;
}

void LSSDetector1::detect(cv::Mat& image){}

cv::Mat LSSDetector1::computeSaliencyMap(cv::Mat& image)
{
    this->image = image;
    assert(image.channels() == 1 && image.type()== CV_8U); // 灰度图

    cv::Mat r_hist;
    float range[] = { 0, 255 } ;
    const float* histRange = { range };
    cv::calcHist( &image, 1, new int[1]{0}, cv::Mat(), r_hist, 1, new int[1]{255}, &histRange);
    
    std::vector<int32_t> r_hist_arr;
    for (int i = 0; i < 255; i++)
    {
        r_hist_arr.push_back((int32_t)r_hist.at<float>(i));
    }
    // 计算显著性值
    cv::Mat sals = cv::Mat(255, 1, CV_32SC1, cv::Scalar_<int32_t>(0));
    for (int i = 0; i < 255; i++)
    {
        for (int j = 0; j < 255; j++)
        {
            if (j == i) continue;
            int dist = abs(i - j); // 可以有其他的距离度量方法
            sals.ptr<int32_t>(i)[0] += r_hist_arr[j] * dist;
        }
    }
    cv::Mat norm_sals; // int32 type
    cv::normalize(sals, norm_sals, 0, 255, cv::NORM_MINMAX, CV_8U);
    // mapping to each point
    int width = image.cols;
    int height = image.rows;
    cv::Mat saliencyMap = cv::Mat(cv::Size_<int>(width, height), CV_8UC1);
    for (int i = 0; i < width; i++)
    {
        for (int j = 0; j < height; j++)
        {
            // uint8_t pix = image.at<uint8_t>(j, i);
            // 获得当前坐标地像素值
            uint8_t pix = image.ptr<uint8_t>(j)[i];
            // 获得当前坐标像素值的显著性
            saliencyMap.ptr<uint8_t>(j)[i] = norm_sals.ptr<uint8_t>(pix)[0];
        }
    }
    if(DEBUG)
    {
        cv::imshow("SaliencyMap", saliencyMap);
        cv::waitKey(0);
    }
    this->saliencyMap = saliencyMap;
    return saliencyMap;
}


cv::Mat LSSDetector1::computeGrad(cv::Mat& image)
{
    assert(image.channels() == 1 && image.type() == CV_8U);
    // 设置结构元素
    cv::Mat grad;
    cv::Mat kernel = cv::getStructuringElement(cv::MORPH_CROSS, this->kernel_size);
    cv::morphologyEx(image, grad, cv::MORPH_GRADIENT, kernel);
    if (DEBUG)
    {
        cv::imshow("Grad", grad);
        cv::waitKey();
    }
    this->grad = grad;
    return grad;
}

cv::Mat LSSDetector1::computeDiff3(cv::Mat& image0, cv::Mat& image1, cv::Mat& image2) //计算三帧差分
{
    assert(image0.channels() == 1 && image0.type() == CV_8U);
    assert(image1.channels() == 1 && image1.type() == CV_8U);
    assert(image2.channels() == 1 && image2.type() == CV_8U);

    cv::Mat diff0, diff1;
    cv::absdiff(image1, image0, diff0);
    cv::absdiff(image2, image1, diff1);
    
    cv::threshold(diff0, diff0, Q-1/*Q*/, 255, cv::THRESH_BINARY);
    cv::threshold(diff1, diff1, Q-1/*Q*/, 255, cv::THRESH_BINARY);
    // Logical and 
    cv::Mat diff3;
    cv::bitwise_and(diff0, diff1, diff3, cv::Mat());
    if (DEBUG)
    {
        cv::imshow("Diff3", diff3);
        cv::waitKey(0);
    }
    this->diff3 = diff3;
    return diff3;
}

vector<cv::Rect> LSSDetector1::getObjRects(cv::Mat srcImage, cv::Mat mask)
{
    // 腐蚀膨胀操作
    cv::Mat struct_ele = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3), cv::Point(-1, -1)); 
    cv::erode(mask, mask, struct_ele);  
    cv::dilate(mask, mask, struct_ele, cv::Point(-1, -1), 10);
    // 获得连通域的质心
    cv::Mat labels, stats, centroids;
    
    cv::connectedComponentsWithStats(mask, labels, stats, centroids); //通过连通域获得不同目标的Rect
    vector<cv::Rect> rects;
    for (int i = 1; i < stats.rows; i++)
    {
        cv::Rect c_rect;
        c_rect.x = stats.at<int>(i, 0);
        c_rect.y = stats.at<int>(i, 1);
        c_rect.width = stats.at<int>(i, 2);
        c_rect.height = stats.at<int>(i, 3);
        rects.push_back(c_rect);
    }
    this->objRects = rects;
    return rects;
}


vector<cv::Rect> LSSDetector1::combineNearRects() //这些矩形框肯定是不交叉的,因为他们不连通
{
    vector<cv::Rect> ret_rects;
    vector<cv::Rect> alt_rects;
    for (int i = 0; i < objRects.size(); i++)
    {
        alt_rects.push_back(objRects[i]);
    }
    cv::Rect t_rect;
    while (!alt_rects.empty())
    {
        vector<cv::Rect> t_rects; //需要合并的rects组
        t_rects.push_back(alt_rects.back());
        alt_rects.pop_back();

        vector<int> indices;
        for (int i = 0; i < alt_rects.size(); i++) // 记录哪些rect需要合并
        {
            cv::Rect cand_rect = alt_rects[i];
            for (int j = 0; j < t_rects.size(); j++)
            {
                // 判断是否需要合并
                if (needCombine(cand_rect, t_rects[j]))
                {
                    t_rects.push_back(cand_rect);
                    indices.push_back(i);
                    break;
                }
            }
        }
        // 去除掉alt_rects中的需要合并的样本
        vector<cv::Rect> filter_alt_rects;
        for (int i = 0; i < alt_rects.size(); i++)
        {
            auto it = std::find(indices.begin(), indices.end(), i);
            if (it==indices.end()) //没有找到
            {
                filter_alt_rects.push_back(alt_rects[i]);
            }
        }
        alt_rects = filter_alt_rects;
        t_rect = combine(t_rects);
        ret_rects.push_back(t_rect);
    }
    this->combinedRects = ret_rects;
    return ret_rects;
}

bool LSSDetector1::needCombine(cv::Rect r1, cv::Rect r2)
{
    int height = image.rows;
    int width = image.cols;

    cv::Point c1 = cv::Point(r1.x + r1.width / 2, r1.y + r1.height / 2);
    cv::Point c2 = cv::Point(r2.x + r2.width / 2, r2.y + r2.height / 2);

    if (abs(c1.x - c2.x) < this->connectRectsRatio * width && 
        abs(c1.y- c2.y) < this->connectRectsRatio * height)
    {
        return true;
    }
    return false;
}

cv::Rect LSSDetector1::combine(vector<cv::Rect> rects)
{
    assert(rects.size() >= 1);
    
    int xmin = rects[0].x, ymin=rects[0].y, xmax=rects[0].x+rects[0].width, ymax=rects[0].y+rects[0].height;
    
    for (int i = 1; i < rects.size(); i++)
    {
        if (rects[i].x < xmin) xmin = rects[i].x;
        if (rects[i].y < ymin) ymin = rects[i].y;
        if (rects[i].x + rects[i].width > xmax) xmax = rects[i].x + rects[i].width;
        if (rects[i].y + rects[i].height > ymax) ymax = rects[i].y + rects[i].height;
    }
    return cv::Rect(xmin, ymin, xmax-xmin, ymax-ymin);
}
//run_detector.cpp
/**
 * 

g++-7 -o \
detectors/lssdetectors/run_detector \
detectors/lssdetectors/run_detector.o \
detectors/lssdetectors/LSSDetector1.o \
utils/common_utils.o \
-I /usr/local/include \
-I /usr/local/include/opencv \
-I /usr/local/include/opencv2 \
-I /home/wyx/software/opencv_contrib-3.4.9/modules/tracking/include \
-l opencv_world

run shell: 
detectors/lssdetectors/run_detector `pwd`/dataset/靶机5
 */
#include "LSSDetector1.h"

#include "utils/common_utils.h"
int main(int argc, char** argv)
{
    string img_dir = argv[1];
    vector<string> files;
    common_utils::getFiles(img_dir, files);
    std::sort(files.begin(), files.end());
    LSSDetector1 det(false);
    det.init();
    for (int i = 0; i < files.size() - 3; i++)
    {
        cv::Mat img = cv::imread(img_dir + "/" + files[i]);
        cv::Mat img1 = cv::imread(img_dir + "/" + files[i+1]);
        cv::Mat img2 = cv::imread(img_dir + "/" + files[i+2]);
        
        cv::cvtColor(img, img, cv::COLOR_BGR2GRAY);
        cv::cvtColor(img1, img1, cv::COLOR_BGR2GRAY);
        cv::cvtColor(img2, img2, cv::COLOR_BGR2GRAY);
        

        cv::Mat map = det.computeSaliencyMap(img);
        cv::Mat map1 = det.computeSaliencyMap(img1);
        cv::Mat map2 = det.computeSaliencyMap(img2);

        cv::Mat grad = det.computeGrad(map);
        cv::Mat grad1 = det.computeGrad(map1);
        cv::Mat grad2 = det.computeGrad(map2);

        cv::Mat mask = det.computeDiff3(grad, grad1, grad2);

        // 对mask取连通域

        img2 = cv::imread(img_dir + "/" + files[i+2]);
        vector<cv::Rect> rects = det.getObjRects(img2, mask);
        vector<cv::Rect> combinedRects = det.combineNearRects();
        for (size_t i = 0; i < rects.size(); i++)
        {
            cv::rectangle(img2, rects[i], cv::Scalar(0, 255, 0),2);
            cv::imshow("Image", img2);
            cv::waitKey(0);
        }
        for (auto rect:combinedRects)
        {
            cv::rectangle(img2, rect, cv::Scalar(0, 0, 255),2);
            cv::imshow("Image", img2);
            cv::waitKey(0);
        }
    }
    return EXIT_SUCCESS;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值