darknet网络检测结果和opencv检测结果不一样原因分析一

经darknet训练后的网络,在利用opencv接口进行检测时,和官网检测结果不一致,故分析下网络实际加载图像方法。

在和他人员交流时,其反馈说利用AlexeyAB版darknet训练后利用opencv检测时不会出现检测结果不一致问题(经测试确实可行),后期将转到分析AlexeyAB版程序,以下分析是基于官网代码。AB版参考:https://github.com/AlexeyAB/darknet#how-to-compile-on-linux-using-cmake   主要在darknet的基础上做了很多有用的扩充。更多AB版了解可参考https://blog.csdn.net/just_sort/article/details/104109070    后期自己也将对AB版代码进行分析。

1、darknet如何加载图像?

说明:darknet中的image数据最终转换为RGB格式的按比例缩放的float型数据。空余部分填充为0.5或127

首先darknet在include/darknet.h中定义了image结构体,用于存放图片数据及图片相关信息。

typedef struct {
    int w;
    int h;
    int c;  //c: 图像的通道数
    float *data;//data指针:指向图像第一个像素位置的指针
} image;

在darknet中,image结构体存储图像数据顺序是按照以下规则:1)按通道顺序存储,即先存1通道,再2通道,最后3通道。

 在opencv中,每个像素点直接存储三个通道的值,且为BGR格式。在利用opencv读取图像时,将图像转为了RGB格式。具体测试结果不一样应该还是数据转换问题,目前还在分析该原因。下面主要分析darknet中数据转换部分。

darknet网络检测结果和opencv检测结果不一样原因分析一_第1张图片

检测命令1:./darknet detect cfg/yolov3.cfg yolov3.weights data/dog.jpg。这个命令结合代码非常好理解,首先可以看examples/darknet.c的主函数main中。程序是根据给定的argv来决定程序的走向,如本例argv[1]为detect,也就是说程序会转到detector.c中的test_dector函数中。test_detector()函数中包含了我们熟悉的一个端到端的过程。即,初始化模型、读取模型权重、读取图片、图片resize、将图片喂给模型进行前向传播、根据模型输出得到目标位置及类别、将目标位置及类别标注在图片上。load_image_color(input,0,0)根据图片路径加载input,在include/darknet.h和image.c中进行定义,通过调用load_image()函数(定义于include/darknet.h和image.c中)读取图片。

image im = load_image_color(input,0,0);
image sized = letterbox_image(im, net->w, net->h);//等比例缩放图像至网络大小

在image.cpp中定义函数定义load_image_color()等函数,在image_opencv.cpp中定义了load_image_cv()函数。下述代码说明了不同函数间的调用关系。

image load_image_color(char *filename, int w, int h)
{
    return load_image(filename, w, h, 3);
}

image load_image(char *filename, int w, int h, int c)
{
#ifdef OPENCV
    image out = load_image_cv(filename, c);//若定义opencv,调用
#else
    image out = load_image_stb(filename, c);//若没有定义opencv,调用
#endif

    if((h && w) && (h != out.h || w != out.w)){//参数h w均为0,正常加载图像后不会d调用iaoyong resize_image()函数,而是调用letterbox_image()进行等比例缩放
        image resized = resize_image(out, w, h);
        free_image(out);
        out = resized;
    }
    return out;
}
void rgbgr_image(image im)//转换BRG到RBG
{
    int i;
    for(i = 0; i < im.w*im.h; ++i){
        float swap = im.data[i];
        im.data[i] = im.data[i+im.w*im.h*2];
        im.data[i+im.w*im.h*2] = swap;
    }
}

image load_image_stb(char *filename, int channels)
{
    int w, h, c;
    unsigned char *data = stbi_load(filename, &w, &h, &c, channels);
    if (!data) {
        fprintf(stderr, "Cannot load image \"%s\"\nSTB Reason: %s\n", filename, stbi_failure_reason());
        exit(0);
    }
    if(channels) c = channels;
    int i,j,k;
    image im = make_image(w, h, c);//image结构中的像素数据是按通道组合在一起的,且进行了均一化处理
    for(k = 0; k < c; ++k){
        for(j = 0; j < h; ++j){
            for(i = 0; i < w; ++i){
                int dst_index = i + w*j + w*h*k;
                int src_index = k + c*i + c*w*j;
                im.data[dst_index] = (float)data[src_index]/255.;
            }
        }
    }
    free(data);
    return im;
}
//在image_opencv.cpp中定义了load_image_cv()函数
image load_image_cv(char *filename, int channels)
{
    int flag = -1;
    if (channels == 0) flag = -1;
    else if (channels == 1) flag = 0;
    else if (channels == 3) flag = 1;
    else {
        fprintf(stderr, "OpenCV can't force load with %d channels\n", channels);
    }
    Mat m;
    m = imread(filename, flag);//加载图像,若加载失败,返回空的矩阵,stored in B G R order.
    if(!m.data){
        fprintf(stderr, "Cannot load image \"%s\"\n", filename);
        char buff[256];
        sprintf(buff, "echo %s >> bad.list", filename);
        system(buff);
        return make_image(10,10,3);
        //exit(0);
    }
    image im = mat_to_image(m);转换为image结构体图像数据
    return im;
}
image mat_to_image(Mat m)
{
    IplImage ipl = m;//IplImage是OpenCV中C语言的图像类型;cv::Mat是OpenCV中C++语言的图像类型;
    image im = ipl_to_image(&ipl);//转换为image结构图像,并将图像数据转为0~1
    rgbgr_image(im);//convert BGR to RGB
    return im;
}
image ipl_to_image(IplImage* src)
{
    int h = src->height;
    int w = src->width;
    int c = src->nChannels;
    image im = make_image(w, h, c);//在image.c中定义,生成
    unsigned char *data = (unsigned char *)src->imageData;
    int step = src->widthStep;
    int i, j, k;

    for(i = 0; i < h; ++i){
        for(k= 0; k < c; ++k){
            for(j = 0; j < w; ++j){
                im.data[k*w*h + i*w + j] = data[i*step + j*c + k]/255.;//归一化0~1
            }
        }
    }
    return im;
}
image make_empty_image(int w, int h, int c)//构建空的image结构
{
    image out;
    out.data = 0;
    out.h = h;
    out.w = w;
    out.c = c;
    return out;
}

image make_image(int w, int h, int c)//在构建数据为image结构
{
    image out = make_empty_image(w,h,c);
    out.data = calloc(h*w*c, sizeof(float));//在动态存储区中长度为n*“size”字节的连续区域,并初始化为0
    return out;
}

加载完函数后进行按比例缩放,不足部分按平均灰度值0.5或127进行填充。

//将原图进行一定比例的缩放,返回的图片尺寸为网络尺寸(w,h)
image letterbox_image(image im, int w, int h)
{
    int new_w = im.w;
    int new_h = im.h;
    if (((float)w/im.w) < ((float)h/im.h)) {//这个说明高度比例大于宽度比例,所以new_h要重新设置
        new_w = w;
        new_h = (im.h * w)/im.w;
    } else {
        new_h = h;
        new_w = (im.w * h)/im.h;
    }
    image resized = resize_image(im, new_w, new_h);//缩放图像
    image boxed = make_image(w, h, im.c);
    fill_image(boxed, .5);//将初始化后的boxed图像像素点置为0.5,即,平均灰度
    //int i;
    //for(i = 0; i < boxed.w*boxed.h*boxed.c; ++i) boxed.data[i] = 0;
    embed_image(resized, boxed, (w-new_w)/2, (h-new_h)/2); //将放缩后的图片复制入boxed图片正中央
    free_image(resized);
    return boxed;
}

//w,h为按原图等比例缩放的照片,即,w=im.w*scale,h=im.h*scale
image resize_image(image im, int w, int h)
{
    image resized = make_image(w, h, im.c); //构建大小为w,h的image图像,且初始化为0  
    image part = make_image(w, im.h, im.c);//先缩放列
    int r, c, k;
    float w_scale = (float)(im.w - 1) / (w - 1);
    float h_scale = (float)(im.h - 1) / (h - 1);
    for(k = 0; k < im.c; ++k){
        for(r = 0; r < im.h; ++r){
            for(c = 0; c < w; ++c){
                float val = 0;
                if(c == w-1 || im.w == 1){
                    val = get_pixel(im, im.w-1, r, k);
                } else {
                    float sx = c*w_scale;//映射到原图的列
                    int ix = (int) sx;//列取整
                    float dx = sx - ix;//求出小数部分
                    val = (1 - dx) * get_pixel(im, ix, r, k) + dx * get_pixel(im, ix+1, r, k);//利用插值公式,在列方向上插值
                }
                set_pixel(part, c, r, k, val);
            }
        }
    }
    for(k = 0; k < im.c; ++k){
        for(r = 0; r < h; ++r){
            float sy = r*h_scale;
            int iy = (int) sy;
            float dy = sy - iy;
            for(c = 0; c < w; ++c){
                float val = (1-dy) * get_pixel(part, c, iy, k);
                set_pixel(resized, c, r, k, val);//首先获取上一行的象素值*(1-dy)
            }
            if(r == h-1 || im.h == 1) continue;
            for(c = 0; c < w; ++c){
                float val = dy * get_pixel(part, c, iy+1, k);
                add_pixel(resized, c, r, k, val);//然后获取下一行的象素值*dy
            }
        }
    }

    free_image(part);
    return resized;
}
static float get_pixel(image m, int x, int y, int c)
{
    assert(x < m.w && y < m.h && c < m.c);
    return m.data[c*m.h*m.w + y*m.w + x];
}
static void set_pixel(image m, int x, int y, int c, float val)
{
    if (x < 0 || y < 0 || c < 0 || x >= m.w || y >= m.h || c >= m.c) return;
    assert(x < m.w && y < m.h && c < m.c);
    m.data[c*m.h*m.w + y*m.w + x] = val;
}
static void add_pixel(image m, int x, int y, int c, float val)//在原像素灰度值基础上加入某值
{
    assert(x < m.w && y < m.h && c < m.c);
    m.data[c*m.h*m.w + y*m.w + x] += val;
}
void fill_image(image m, float s)//将image图像置为0.5的灰度值
{
    int i;
    for(i = 0; i < m.h*m.w*m.c; ++i) m.data[i] = s;
}
//move image from source to dest according to the offset (dx,dy)
void embed_image(image source, image dest, int dx, int dy)//
{
    int x,y,k;
    for(k = 0; k < source.c; ++k){
        for(y = 0; y < source.h; ++y){
            for(x = 0; x < source.w; ++x){
                float val = get_pixel(source, x,y,k);
                set_pixel(dest, dx+x, dy+y, k, val);
            }
        }
    }
}

总结:darknet中的image数据最终转换为RBG格式的按比例缩放的float型数据。

你可能感兴趣的:(darknet网络检测结果和opencv检测结果不一样原因分析一)