用于双目重建中的GPU编程:julia-cuda

用于,双目,重建,gpu,编程,julia,cuda · 浏览次数 : 26

小编点评

**内容摘要** * 了解并进行c++编写的CUDA加速程序。 * 编写c++编写的可视化、调试、最终的编译过程。 * 使用c++编写的可视化、调试、最终的编译过程。 * 理解并进行c++编写的CUDA加速程序。 * 了解并进行c++编写的CUDA加速程序。

正文

作者:京东科技 李大冲

一、Julia是什么

julia是2010年开始面世的语言,作为一个10后,Julia必然有前辈们没有的特点。Julia被期望塑造成原生的有C++的运行速度、python的易交互性以及胶水性。最重要的是,julia也是nVidia-cuda官方选中的接口语言,这点让cuda编程变得稍微容易一些。

二、项目背景

双目立体视觉是比较常规获取深度信息的算法。这是一种模仿人的双眼的方法,根据对同一个点在不同的视角进行观察,并在不同的观察视角上的观察差异推算出深度,这在图1中更清晰一点。被动双目的缺点是需要纹理特征去判断左右相机里是否为同一个点。也就是,当没有问题、纹理不够明显或者纹理重复时,这种方法难以工作。

图 1 被动双目示意图。 观测点在一个相机和在另外一个相机上的成像是有明显可区分性的,二者的观察差异(L1和L2)形成了视差,进而可形成深度信息

被动双目的缺点是某些场景下无效,主动双目便出现了。如果我们投射一些人工设定编码的图案,根据实现约定通过解码可以对视野内是所有点赋予唯一的标识符号,那被动双目的问题不就解决了吗。事实上,人们很快发现可以使用空间上编码和时间上编码两种方式。空间上编码,就像人们观察银河命名星座一样,去投射随机生成的星星点点的光斑,没有纹理的夜空照射进了完全可辨识的图案。如同星座往往是一个空间范围很大的概念,空间编码的方式无法做到非常精细。所以,时间编码的方式应运而生,时间编码是投射一组图案,让被照射的区域,被照射的明暗变化情况是完全不一样的。像图2中的二进制编码(或行业内称为格雷码),再加上灰度变化更加丰富的编码方式以及相机在竖方向上对齐之后是只需要考虑横轴方向上的差异这两点,可以实现全视野的点完全可区分。

图 2 空间编码和时间编码。空间编码如同银河中的星星点点,通过组合星点变为星座,人们可以对深邃夜空不同位置依靠银河区分开。时间编码是投射多组图案,让每一个点是时间上都有唯一的变化情况。就像右图,这16个方格都是完全可区分的。

三、效率问题

使用主动双目的结构去重建,一个很大的影响运行效率的问题是需要对左右图像求算视差(原理如图1所示)。具体求算视差的操作是要对左相机里的每一个点,去找右相机中同一行中与之距离最近的点的横坐标,然后继续插值之后找到距离更近的插值坐标,最后把这两个坐标的差异当作视差进行后续的计算。

使用时间编码的主动双目,按照实现约定的编码进行解码之后,其效果如图3所示,这个环节就是在这对数据上进行处理。以此数据为例,有效区域大概是700850,完全遍历所需要的时间复杂度大概是O(700850*850),直接估算就很耗时。

图 3 时间编码解码后的图像示意图。左右相机拍摄的图案编程了解码值。接下来需要在这对数据上计算视差,而确定相同编码值需要大量的遍历,对相同编码进一步的优化还要对每个点进行小范围的插值处理,时间复杂度很大。

图 4 视差图效果

1. 使用for训练的方式

使用for循环,需要3层。经测试后,在TX2上,使用c++来完成大概需要7s多。

2. 使用numpy的矩阵操作来实现

  1. 代码如下,使用numpy的广播的原理来实现,然后通过一系列的过滤异常值后,再经过插值进一步的处理得到最终想要的结果。

  2. 这段代码没有进一步的去实现插值的过程,耗时情况为1.35s。

def disp(l_, r_, colstartR, colendR, colstartL, colendL, rowstart, rowend):
    # 聚焦在有效值区域
    l = l_[rowstart: rowend, colstartL: colendL]
    r = r_[rowstart: rowend, colstartR: colendR]
    # 算一下阈值。阈值帮助确定判断太远的点就不能当作是目标值
    thres = (np.max(l) - np.min(l)) / (colendL - colstartL) * 10
    h, w = l.shape
    ll = l.T.reshape(w, h, 1)
    # print(l.shape, r.shape)
    # 加快运算,缩小图片,4倍下采样
    quart_r = cv2.resize(r, (w // 4, h))
    # 计算距离
    distance = np.abs(ll - quart_r)
    # 计算点位
    idx_old = np.argmin(distance, axis=-1).T * 4
    # 去除指向同一个点的重复值
    _, d2 = np.unique(idx_old, return_index=True)
    idx = np.zeros_like(idx_old).reshape(-1)
    idx[d2] = idx_old.reshape(-1)[d2]
    idx = idx.reshape(idx_old.shape)
    # 计算最值
    minV = np.min(distance, axis=-1).T
    ori_idx = np.tile(np.arange(w).reshape(w, 1), h).T
    disparity = ori_idx - idx + colstartL - colstartR
    # 去除异常点
    disparity[minV > thres] = 0# 大于阈值的
    disparity[l < 1e-3] = 0 # 左图点无效的
    # 插值
    # 方法:在最值附近的区域去-5:5的值,二次线性插值上采样,采完之后再计算一遍上面的步骤,算完之后取小数进行计算
    # 暂时没有实现这一步
    return disparity, l, r

3. 使用julia-cuda实现

这段主要是cuda核函数的部分,主要包括以下部分:

1)用到了512*700个cuda线程来实现,增加的并行计算抵消了单个点位计算耗时。

2)还用到了共享显存,降低读取数据的耗时,因为右图中的每一行都会被左图中的同行中的每一个元素加载一遍,对于元素for循环是840*840的时间空间复杂度。这儿加载一遍,就可以让这些线程同时看到了。

3)对于上述过程中提到的插值一事,使用cuda的纹理内容轻松搞定,因为纹理内存的一个特性就是插值。即对于一个向量,可以取得到下标不是整数时的值,这些值就是自动插值出现的,且此过程的资源消耗非常低,因为这部分的初始设计是为了图像渲染和可视化。

4)通过这部分代码,实现相同的效果在TX2上的耗时是165ms,快了近10倍。

function bench_match_smem(cfg, phaL, phaR, w, h, winSize, pha_dif)
    texarr2D = CuTextureArray(phaR)
    tex2D = CuTexture(texarr2D; interpolation = CUDA.LinearInterpolation())
    cp, minv, maxv = cfg.cpdiff, cfg.minv, cfg.maxv
    colstart, colend = cfg.colstart, cfg.colend
    rowstart, rowend = cfg.rowstart, cfg.rowend
    mindis, maxdis = cfg.mindis, cfg.maxdis
    col_ = cld((colend - colstart), 32) * 32
    row_ = cld(rowend - rowstart, 32) * 32 
    stride = Int(cld((maxv - minv + 1), 512))
    threadsPerBlock = round(Int32, cld((maxv - minv + 1) / stride, 32) * 32)
    blocksPerGrid = row_
    println("blocks = $blocksPerGrid threads = $threadsPerBlock left  $(col_) right $(maxv - minv) h=$(row_)")
    @cuda  blocks = blocksPerGrid threads = threadsPerBlock shmem =
        (threadsPerBlock * sizeof(Float32))  phaseMatch_smem!(cp, mindis, maxdis, minv, maxv, colstart, colend, rowstart, rowend, phaL, tex2D, threadsPerBlock,blocksPerGrid,stride, w, h, winSize, pha_dif)
    CUDA.synchronize()
    return
end
 
#进行立体相位匹配
function phaseMatch_smem!(cp, mindis, maxdis, minv, maxv, colstart, colend, rowstart, rowend, phaL, phaR, threadsPerBlock, blocksPerGrid, stride, w, h, winSize, pha_dif)
    #---------------------------------------------------
    # cp: diparity map
    # mindis, maxdis 最近最远视差
    #minv, maxv 仿射变换计算得到的R图中有效横向范围
    # colstart, colend, rowstart, rowend仿射变换计算得到的左图中有效横向、竖向范围
    #phaL, phaR 左右图像
    #w, h图像大小
    #winSize, pha_dif 3*3的框; 阈值:约等于20个像素的平均相位距离和
    # Set up shared memory cache for this current block.
    #--------------------------------------------------- 
    wh = fld(winSize, 2)
    cache = @cuDynamicSharedMem(Float32, threadsPerBlock)
    left_stride = 64 
    minv = max(1,minv)#必须是有效值,且是julia下的下标计数方式
    colstart= max(1,wh*stride)
    # 数据读入共享内存
    j = blockIdx().x + rowstart  # 共用的行序号
    i = threadIdx().x + colstart# 左图的列序号

    while(j <= min(rowend,h - wh)) 
        #数据拷贝到共享内存中去,并将由threadsPerBlock共享
        ri = (threadIdx().x - 1) * stride + minv
        tid = threadIdx().x
        while(tid <= threadsPerBlock && ri <= maxv)
            cache[tid] = phaR[j, ri] 
            tid+=threadsPerBlock
            ri+=threadsPerBlock
        end
        # synchronise threads
        sync_threads()
        maxv = min(fld(maxv - minv,stride) * stride + minv,maxv)
        # 计算最小匹配项 
        while(i <= min(colend,w - (wh*stride))) 
            min_v = 10000
            XR = -1
            VV = phaL[j, i]
            if(VV > 0.001f0)
                kStart = max(minv, i - maxdis) + 1
                kEnd = min(maxv, i - mindis) - 1
                for k = kStart:kEnd  #遍历一整行
                    RK = cache[cld(k - minv + 1,stride)]#从0开始计数
                    if RK <= 0.001f0
                        continue
                    end
                    dif = abs(VV - RK)
                    if dif < pha_dif
                        sum = 0.0f0
                        sn = 1 
                        for ki in 0:(winSize - 1) 
                            R_local = cache[cld(k - minv + 1 - wh + ki,stride)]
                            (R_local < 1e-5) && continue
                            #phaL[j + kj - wh, i + ki - wh*stride]
                            VR = VV - R_local
                            sum = sum + abs(VR)# * VR
                            sn += 1
                        end 
                        v = sum / sn
                        if v < min_v
                            min_v = v
                            XR = k
                        end
                    end
                end 
                #需要作插值
                #https://discourse.julialang.org/t/base-function-in-cuda-kernels/21866
                if XR > 0
                    XR_new = bisection(VV, phaR, Float32(j), Float32(XR - 3), Float32(XR + 3))
                    # 注意,这里直接做了视差处理了
                    state = (i - XR_new) > 0
                    @inbounds cp[j, i] = state ? (i - XR_new) : 0.0f0
                end
            end 
            i+=threadsPerBlock
        end
        j+=blocksPerGrid
    end
    sync_threads()
    return
end

4. 使用金字塔原理再次加速

1)这部分和上述代码的区别是,没有让少于列数目的线程进行多次运算,(上述代码中有个自增操作,是让线程进行了多次运算,原因是可分配线程总数不够)

2)整体是金字塔模式,即相邻者相似的原理。

3)时间消耗在TX2上降低到了72ms,相比于前一种方法又有了58%的降幅。

#进行立体相位匹配
function phaseMatch_smem!(cp, mindis, maxdis,
    minv, maxv, colstart, colend,
    rowstart, rowend, phaL, phaR,
    threadsPerBlock, blocksPerGrid, stride,
     w, h, winSize, pha_dif)
    #---------------------------------------------------
    # cp: diparity map
    # mindis, maxdis 最近最远视差
    #minv, maxv 仿射变换计算得到的R图中有效横向范围
    # colstart, colend, rowstart, rowend仿射变换计算得到的左图中有效横向、竖向范围
    #phaL, phaR 左右图像
    #w, h图像大小
    #winSize, pha_dif 3*3的框; 阈值:约等于20个像素的平均相位距离和
    # Set up shared memory cache for this current block.
    #--------------------------------------------------- 
    wh = fld(winSize, 2)
    cache = @cuDynamicSharedMem(Float32, threadsPerBlock) 
    minv = max(1,minv)#必须是有效值,且是julia下的下标计数方式
    colstart = max(1, colstart, wh*stride)
    rowstart = max(1, rowstart, wh)#需要算3*3的矩阵,目前没有计算,所以不用严格满足>wh
    # i 最大、最小区间,所需要的迭代次数,或者可以看成所需处理的步长
    left_stride = Int(cld(colend - colstart, threadsPerBlock))
    j = (blockIdx().x - 1) + rowstart  # 共用的行序号
    i = (threadIdx().x - 1) * left_stride + colstart# 左图的列序号

    while(j <= min(rowend,h - wh))
        #数据拷贝到共享内存中去,并将由threadsPerBlock共享
        ri = (threadIdx().x - 1) * stride + minv
        tid = threadIdx().x
        while(tid <= threadsPerBlock && ri <= maxv)
            cache[tid] = phaR[j, ri] 
            tid+=threadsPerBlock
            ri+=threadsPerBlock
        end
        # synchronise threads
        sync_threads()
        maxv = min(fld(maxv - minv,stride) * stride + minv,maxv)
        # 计算最小匹配项
        #end_i = i + left_stride
        #while(i <= min(colend, w - (wh*stride))) #end_i - 1,
        if(i <= min(colend, w - (wh*stride))) #end_i - 1,
            min_v = 10000
            XR = -1
            VV = phaL[j, i]
            if(VV > 0.001f0)
                kStart = max(minv, i - maxdis)
                kEnd = min(maxv, i - mindis)
                for k = kStart:kEnd  #遍历一整行
                    RK = cache[cld(k - minv + 1,stride)]#从0开始计数
                    if RK <= 0.001f0
                        continue
                    end
                    dif = abs(VV - RK)
                    if dif < pha_dif
                        sum = 0.0f0
                        sn = 1 
                        for ki in 0:(winSize - 1) 
                            R_local = cache[cld(k - minv + 1 - wh + ki,stride)]
                            (R_local < 1e-5) && continue
                            #phaL[j + kj - wh, i + ki - wh*stride]
                            VR = VV - R_local
                            sum = sum + abs(VR)# * VR
                            sn += 1
                        end 
                        #v = sqrt(sum)/ sn
                        v = sum / sn
                        if v < min_v
                            min_v = v
                            XR = k
                        end
                    end
                end

                #需要作插值
                #https://discourse.julialang.org/t/base-function-in-cuda-kernels/21866
                if XR > 0
                    for offset in 0:left_stride - 1
                        i += offset
                        VV = phaL[j, i]
                        XR_new = bisection(VV, phaR, Float32(j), Float32(XR - 3), Float32(XR + 3))
                        # 注意,这里直接做了视差处理了
                        state = (i - XR_new) > 0
                        @inbounds cp[j, i] = state ? (i - XR_new) : 0.0f0
                    end
                end
            end
        end
        j+=blocksPerGrid
    end
    sync_threads()
    return
end

四、总结

julia作为一个交互性强的语言,在上述目的的达成上,它至少是做到了。对于上述算是一个标准的cuda加速过程,用cuda-c进行编写的话,也是类似的过程,典型操作。但是c++编写的可视化、调试、最终的编译要麻烦的多得多得多。julia达到高效率的目的的同时,让编写的过程没有那么痛苦,堪称完美的一次体验。

与用于双目重建中的GPU编程:julia-cuda相似的内容:

用于双目重建中的GPU编程:julia-cuda

julia是2010年开始面世的语言,作为一个10后,Julia必然有前辈们没有的特点。本文着重介绍julia的项目背景、效率问题,如何使用for训练的方式、julia-cuda的实现方式。

行转列排除重复数据并且对比的方法

行转列排除重复数据并且对比的方法 摘要 出差成都. 突然发现被人当成Shell脚本小子了 今天让对着投影仪确定文件是否正确和完备 几乎闪瞎我的双眼 感觉国家这么多年的英语教育的确卓有成效 看简写, 耗费大半天也猜不出是啥意思来.. 为了能够记录下来,干的事情, 把用到的命令和处理过程记录一下. 备忘

大数据实时链路备战 —— 数据双流高保真压测

大数据时代,越来越多的业务依赖实时数据用于决策,比如促销调整,点击率预估、广告分佣等。为了保障业务的顺利开展,也为了保证整体大数据链路的高可用性,越来越多的0级系统建设双流,以保证日常及大促期间数据流的稳定性。

14.4 Socket 双向数据通信

所谓双向数据传输指的是客户端与服务端之间可以无差异的实现数据交互,此类功能实现的核心原理是通过创建`CreateThread()`函数多线程分别接收和发送数据包,这样一旦套接字被建立则两者都可以异步发送消息,本章将实现简单的双向交互功能。首先我们需要封装两个函数,这里`RecvFunction`函数用于接收数据,`SendFunction`函数则用于发送数据,这两段代码在服务端与客户端之间是一致的

【转帖】一文解析ethtool 命令的使用

命令简介 ethtool命令用于查询和控制网络设备驱动程序和硬件设置,尤其是有线以太网设备,devname网卡的名称。网卡就像是交换机的一个端口,正常使用我们只是配置网卡IP地址等信息,网卡的速率、双工模式等我们并不关心。通过ethtool命令我们可以像配置交换机网卡一样配置这些参数,这就是这个命令

[golang]在Gin框架中使用JWT鉴权

什么是JWT JWT,全称 JSON Web Token,是一种开放标准(RFC 7519),用于安全地在双方之间传递信息。尤其适用于身份验证和授权场景。JWT 的设计允许信息在各方之间安全地、 compactly(紧凑地)传输,因为其自身包含了所有需要的认证信息,从而减少了需要查询数据库或会话存储

【OpenVINO™】在C#中使用 OpenVINO™ 部署 YOLOv10 模型实现目标

最近YOLO家族又添新成员:YOLOv10,YOLOv10 提出了一种一致的双任务方法,用于无nms训练的YOLOs,它同时带来了具有竞争力的性能和较低的推理延迟。此外,还介绍了整体效率-精度驱动的模型设计策略,从效率和精度两个角度对YOLOs的各个组成部分进行了全面优化,大大降低了计算开销,增强了...

修改页面内文字颜色

博客地址:https://www.cnblogs.com/zylyehuo/ 该方法仅用于截图 未改变网站数据 在所在页面打开“检查”(或者按键盘上F12 部分笔记本电脑需要结合Fn键使用) 在跳出的窗口中找到该按键,按下后选择要选择的区域 找到距离td标签最近的tr标签 双击该tr行 将光标移到最

助听器降噪神经网络模型

具体的软硬件实现点击 http://mcu-ai.com/ MCU-AI技术网页_MCU-AI人工智能 本文介绍了一种用于实时语音增强的双信号变换 LSTM 网络 (DTLN),作为深度噪声抑制挑战 (DNS-Challenge) 的一部分。该方法将短时傅立叶变换 (STFT) 和学习分析和综合基础

基于Spring事务的可靠异步调用实践

SpringTxAsync组件是仓储平台组(WMS6)自主研发的一个专门用于解决可靠异步调用问题的组件。 通过使用SpringTxAsync组件,我们成功地解决了在仓储平台(WMS6)中的异步调用需求。经过近二年多的实践并经历了两次618活动以及两次双11活动,该组件已经在我们的所有应用中稳定运行并