darknet中的图像缩放算法(双线性内插值法)

一:双线性内插值法

设原图像的height,width,channels分别为h,w,c

目标图像(resize结果)的height,width,channels分别为h_r,w_r,c_r

则长宽的调整比例分别为:scale_{h}=h/h_rscale_{w}=w/w_r

那么目标图像中的像素点(a, b, c),对应原图像的像素点(a*scale_{h},b*scale_{w},c),要取整。

这是k近邻内插值的方法。

而双线性内插值,对于缩放后的像素点不再是简单的取整。

(a*scale_{h}=2.4,b*scale_{w}=3.6,c),即对应原图像的像素点为(2.4,3.6,c),因为通道数不缩放,所以下文都不再把通道数写出来,(2.4,3.6)该像素点其实对应在点(2,3),(2,4),(3,3),(3,4)之间,所以缩放后的点(2.4,3.6)应该由这四个点共同决定,见下图:

Pixel(E)=0.4*0.4*Pixel(A)+0.4*0.6*Pixel(B)+0.4*0.6*Pixel(C)+0.6*0.6*Pixel(D)

由上图可以清晰的看出双线性内插值法将对应原图像的像素点的小数部分视为一种占比关系,我们再梳理一下:

设缩放比例为scale_h(height的缩放比例),scale_w(width的缩放比例),缩放后的图像中像素点(x,y),对应的原始图像中的像素点为(x*scale_width,y*scale_height),取他的小数部分为(a,b),取他的整数为(c,d),则其像素值受到这四个像素点的像素值决定A(c,d),B(c+1,d),C(c,d+1),D(c+1,d+1)

则像素点Pixel(x,y)=Pixel(A)*a*(1-b)+Pixel(B)*(1-a)*(1-b)+Pixel(C)*a*b+Pixel(D)*(1-a)*b

这就是双线性内插值法的计算过程。

 

二:darknet框架中的实现

相关算法在src/image.c中,resize_image函数,代码如下:

image resize_image(image im, int w, int h)
{
    //目标大小608*608
    image resized = make_image(w, h, im.c);
    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);
            }
            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);
            }
        }
    }

    free_image(part);
    return resized;
}

我现将darknet中的实现思想表述以下,再来分析代码。

再借用这附图:

 上述在讲解双线性内插值时,是宽高同时进行缩放的,可以现进行宽缩放或高缩放。在计算过程中,就可以先求中间点H,I的像素值或点G,F的像素值

即Pixel(H)=Pixel(A)*0.4+Pixel(B)*0.6,Pixel(I)=Pixel(C)*0.4+Pixel(D)*0.6,Pixel(E)=Pixel(H)*0.4+Pixel(I)*0.6

或Pixel(G)=Pixel(A)*0.4+Pixel(C)*0.6,Pixel(F)=Pixel(B)*0.4+Pixel(D)*0.6,Pixel(E)=Pixel(G)*0.4+Pixel(F)*0.6

而darknet采用的是先对宽(width)进行缩放,再对高(height)进行缩放

现在来看代码:

首先来看缩放比例的计算:

float w_scale = (float)(im.w - 1) / (w - 1);
float h_scale = (float)(im.h - 1) / (h - 1);

看到这里,小朋友你是不是有很多❓,为什么要减1啊?

在寻找对应原图像的像素点时,缩放后的图像中像素点(x,y),对应的原始图像中的像素点为(x*scale_width,y*scale_height),这个(x*scale_width,y*scale_height)是两个小数,我们对他向下和向上取整,获得那四个像素点,其中向上取整时,得到的那俩个像素点可能超出了图像的大小,那么对于这种点采取像素值取0的方式。那么对于缩放后的图像的最外侧的像素点,在计算其像素值时,一定会超出图像大小,那么就要取0值进行计算,这无端的在外圈像素引入了误差,从视觉效果来说,缩放后的图像会有一圈颜色偏暗(黑)的像素圈圈。

所以darknet不对最下端和最右端的像素进行缩放,所以实际进行缩放的图像大小为(im.w-1,im.h-1),那么对于最后一行和最后一列的像素值直接从原图像的对应位置直接取值,对应代码为:

if(c == w-1 || im.w == 1){
    val = get_pixel(im, im.w-1, r, k);
}

搞清楚这些后,整个过程就很明朗了,先对width进行缩放:

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);
            }
        }
    }

part用于存储width缩放的中间值

再对height进行缩放:

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);
            }
            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);
            }
        }
    }

由于darknet中图像保存为一维数组,所以要特别注意图像像素的索引,darknet中的图像是以行(height),列(width),通道(channel)进行展开为一维数组的。可以通过get_pixel函数来理解像素的索引。

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
darknet图像分类任务,我们将使用自己的数据集进行猫狗分类。首先,我们需要准备一个包含猫狗图像的数据集。这个数据集应该包含猫和狗的图像,每个图像都要有相应的标签,表示它是一只猫还是一只狗。 接下来,我们需要使用darknet框架进行图像分类。darknet是一个轻量级的深度学习框架,适用于图像分类、目标检测和语义分割等任务。我们可以使用darknet的一个已经训练好的模型进行迁移学习,也可以自己从头开始训练一个新模型。 训练模型的第一步是将数据集分为训练集和验证集。通常,我们将数据集的80%作为训练集,20%作为验证集。训练集用来训练模型的参数,验证集用来评估模型的性能。 接下来,我们需要准备配置文件。配置文件包含了训练模型的相关参数,比如网络结构、学习率、批次大小等。我们可以根据自己的需求进行调整。 然后,我们可以使用darknet命令行工具来训练模型。在训练过程,模型会不断地根据训练集的图像和标签进行参数更新,以提高对猫狗图像的分类性能。训练过程需要一定的时间,取决于数据集的大小和计算资源的性能。 训练完成后,我们可以使用验证集对模型进行评估。评估结果会告诉我们模型的准确率和召回率,以及其他性能指标。如果模型的性能达到了我们的要求,我们就可以使用它对新的猫狗图像进行分类了。 总之,通过使用自己的数据集和darknet框架,我们可以实现猫狗图像分类任务,得到一个准确率高的模型,用于对新的猫狗图像进行分类。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猫猫虫(——)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值