Semantic-Guided Zero-Shot Learning for Low-Light ImageVideo Enhancement

news2025/1/11 20:08:15

论文阅读之无监督低光照图像增强 Semantic-Guided Zero-Shot Learning for Low-Light Image/Video Enhancement

代码: https://github.com/ShenZheng2000/SemantiGuided-Low-Light-Image-Enhancement

在低光条件下增加亮度的一个可行方法是使用更高的ISO或更长时间的曝光时间。然而,这些策略分别加剧了噪声和引入了运动模糊[2]。另一种合理的方法是使用Photoshop或Lightroom等现代软件进行光线调整。然而,这些软件需要艺术技能,并且对于不同光照条件的大规模数据集效率低下。

近年来,基于深度学习的低照度图像增强方法因其令人瞩目的高效性、准确性和鲁棒性而受到广泛关注。监督方法[33,48,52,45]在上得分最高。一些基准数据集[47,13,35,25]具有出色的图像到图像映射能力。然而,它们需要成对的训练图像(即低/正常光线对),这要么需要昂贵的润饰,要么要求不可实现的具有相同场景但不同照明条件的图像捕捉。另一方面,无监督方法[20]只需要一个未配对的数据集进行训练。尽管如此,人工选择的数据集的数据偏差限制了他们的泛化能力。零样本学习[11,27]方法消除了对成对图像和未配对数据集的需要。然而,它们忽略了语义信息,[37,10,31]表明这对高级视觉任务至关重要。因此,他们增强的图像在次优视觉质量。图1揭示了在以往研究中的局限性.

针对上述局限性,我们提出了语义指导的低照度图像增强零元框架(图 2)。由于我们专注于低照度图像/视频增强,我们首先设计了一个轻量级增强因子提取(EFE)网络,该网络具有深度可分离卷积[15]和对称跳转连接。可分离卷积[15]和对称跳转连接设计轻量级增强因子提取(EFE)网络。EFE 具有很强的自适应能力,可以利用低照度图像的空间信息来增强图像/视频效果。为了以可承受的模型大小执行图像增强,我们随后引入了递归图像增强(RIE)网络。
图像增强(RIE)网络 能够逐步增强图像,将前一阶段的输出作为后一递归阶段的输入。为了在增强过程中保留语义信息,我们最后
最后,我们提出了一种无监督语义分割(USS)网络,无需昂贵的分割注释。该网络USS 接收来自 RIE 的增强图像,并利用
特征金字塔网络 [29] 来计算分割损失。分割损失与其他非参考损失函数合并为总损失,并在训练过程中更新 EFE 的参数。建议工作的贡献总结如下:

  • 我们提出了一种新的以语义为指导的零元低照度图像增强网络。据我们所知,我们是第一个将高层次语义信息与低照度图像增强网络相融合的框架。
  • 我们开发了一个轻量级的卷积神经网络来自动提取增强因子,它记录了低照度图像像素级的光照不足。
  • 设计了一种循环图像增强策略,其中包含五个非参考损失函数,以提高模型对不同光照条件图像的泛化能力。
  • 进行了广泛的实验,证明了模型在定性和定量指标上的优越性。我们的模型是低光视频增强的理想选择,因为它可以在单GPU上1秒内处理1000张大小1200 × 900的图像
  1. Proposed Method

3.1. Enhancement Factor Extraction Network

增强因子提取(EFE)旨在学习低照度图像的像素级光不足,记录该信息在增强因子中。受启发受U-Net[42]架构的启发,EFE是一个具有对称跳过连接的全卷积神经网络,这意味着它可以处理任意大小的输入图像。没有采用批量归一化或上/下采样,因为它们会破坏增强图像的空间连贯性[43,21,18]。EFE中的每个卷积块由一个3 × 3深度可分离卷积层和随后的ReLU[38]激活层组成。最后一个卷积块将通道数量从32减少到3,并通过Tanh激活输出增强因子xr。图3将从2幅低照度图像中提取的增强因子可视化。可见,低照度图像中较亮的区域对应较低的增强因子值,反之亦然。

3.2. Recurrent Image Enhancement Network

受递归[41,55,28]和光增强曲线[50,11]在低照度图像增强中的成功启发,我们构建了一个递归图像增强(RIE)网络,根据增强因子对低照度图像进行增强,然后输出增强后的图像。每个递归都将前一阶段的输出和增强因子作为其输入。循环增强的过程如下:

其中x为输出,xr为增强因子,t为递归步长。下一步是决定照亮图像的最佳顺序。由于循环网络应该是简单的微分,并且应该是有效的对于渐进减轻,我们只考虑正整数的顺序。考虑到这一点,我们在图中绘制关于不同xr和顺序的递归图像增强。4. 阶数为1时,像素值对xr不敏感,与上一阶段相同。当Order等于3或4时,像素值接近甚至超过1.0,使图像看起来太亮。相比之下,2的顺序赋予最鲁棒的递归增强。

3.3. Unsupervised Semantic Segmentation Network

无监督语义分割(USS)网络旨在对增强后的图像进行精确的像素分割,在渐进图像增强的过程中保持语义信息。与[7,32,46,12]类似,我们在训练期间冻结分割网络的所有层。在这里,我们使用两个路径,包括bottom- bottom路径,它使用ResNet-50[14]与ImageNet[5]权重,以及top-down路径,它使用高斯初始化,均值为0,标准差为0.01。两条通路都有四个卷积块,它们通过横向连接相互连接。权重初始化的选择将在消融研究中解释。

来自RIE的增强图像将首先进入自下而上的路径进行特征提取。然后,自上而下的路径将高语义层转换为高分辨率层,以进行空间感知的语义分割。自顶向下方法中的每个卷积块对图像执行双线性上采样,并将其与横向结果连接。为了更好的感知质量,在拼接后应用了两个平滑的3×3卷积层。最后,我们将中每个块的结果连接起来并计算分割。

3.4. Loss Functions

采用5种无参考损失函数,包括Lspa、Lrgb、Lbri、Ltv和Lsem。我们没有考虑由于配对训练图像不可用而造成的内容损失或感知损失[35]。

**空间一致性损失**这种空间一致性损失通过在增强过程中保留相邻像素的差异,有助于保持低照度图像和增强图像之间的空间一致性。与[11,27]只考虑相邻单元不同,我们还包括与非相邻单元的空间一致性(见图5)。空间一致性损失为:

image-20231109203803367

其中,Y和I分别是增强图像和低照度图像中a × a局部区域的平均像素值。A为局部区域的一侧,根据消融研究,我们将设为4。ψ(i)是四个相邻的邻域值(top, down, left, right), ψ(i)是四个不相邻的邻域值(top left, top right, lower left, lower right)。α值为0.5是因为非相邻邻居的权重不那么重要。

RGB 损失 色彩损失[45, 52, 11]通过桥接不同的色彩通道来减少增强图像中的色彩不正确性。我们采用的是 Charbonnier 损失,它有助于高质量图像重建[23, 19]。RGB 损失为

image-20231109204718447

其中,ε是一个惩罚项,根据经验将其设置为10−6,以提高训练稳定性。

亮度损失 受[34,45,11]的启发,我们设计了亮度损失来限制图像中的曝光不足/过度。损失衡量的是两者之间的L1差特定区域到预定义曝光水平e的平均像素值,亮度损失为

image-20231109205038003

其中E是理想的图像曝光等级,设置为0。消融研究显示60。总变差损失总变差损失[3]测量图像中相邻像素之间的差异。我们在这里使用全变差损失来减少噪声并增加图像的平滑性。与之前的低照度图像增强工作[48,52,45,11]不同,我们在损失中额外考虑通道间(R, G和B)关系以改善颜色亮度。我们的总变差损失为:

image-20231109205538899

其中C、H、W分别表示图像的通道、高度、宽度。∇x和∇y分别是水平和垂直的梯度运算。语义损失语义损失有助于在增强过程中保持图像的语义信息。我们参考焦点损失[30]来编写我们的成本函数。消融研究推荐的语义损失不需要分割标签,只需要一个预先初始化的模型。语义损失为:

image-20231109205645699

其中,p为分割网络对一个像素的估计类概率。受[7]的启发,我们选择焦点系数β和γ分别为1和2。

代码

需要两个网络成分,一个是递归网络络,一个是分割网络(不需要优化)

image-20231109210358005

class enhance_net_nopool(nn.Module):
    def __init__(self, scale_factor, conv_type='dsc'):
        super(enhance_net_nopool, self).__init__()
        self.relu = nn.ReLU(inplace=True)
        self.scale_factor = scale_factor
        self.upsample = nn.UpsamplingBilinear2d(scale_factor=self.scale_factor)
        number_f = 32
        # Define Conv type
        if conv_type == 'dsc':
            self.conv = DSC
        elif conv_type == 'dc':
            self.conv = DC
        elif conv_type == 'tc':
            self.conv = TC
        else:
            print("conv type is not available")
        #   zerodce DWC + p-shared
        self.e_conv1 = self.conv(3, number_f)
        self.e_conv2 = self.conv(number_f, number_f)
        self.e_conv3 = self.conv(number_f, number_f)
        self.e_conv4 = self.conv(number_f, number_f)
        self.e_conv5 = self.conv(number_f * 2, number_f)
        self.e_conv6 = self.conv(number_f * 2, number_f)
        self.e_conv7 = self.conv(number_f * 2, 3)

    def enhance(self, x, x_r):
        x = x + x_r * (torch.pow(x, 2) - x)
        x = x + x_r * (torch.pow(x, 2) - x)
        x = x + x_r * (torch.pow(x, 2) - x)
        enhance_image_1 = x + x_r * (torch.pow(x, 2) - x)
        x = enhance_image_1 + x_r * (torch.pow(enhance_image_1, 2) - enhance_image_1)
        x = x + x_r * (torch.pow(x, 2) - x)
        x = x + x_r * (torch.pow(x, 2) - x)
        enhance_image = x + x_r * (torch.pow(x, 2) - x)
        return enhance_image
    def forward(self, x):
        if self.scale_factor == 1:
            x_down = x
        else:
            x_down = F.interpolate(x, scale_factor=1 / self.scale_factor, mode='bilinear')
        # extraction
        x1 = self.relu(self.e_conv1(x_down))
        x2 = self.relu(self.e_conv2(x1))
        x3 = self.relu(self.e_conv3(x2))
        x4 = self.relu(self.e_conv4(x3))
        x5 = self.relu(self.e_conv5(torch.cat([x3, x4], 1)))
        x6 = self.relu(self.e_conv6(torch.cat([x2, x5], 1)))
        x_r = F.tanh(self.e_conv7(torch.cat([x1, x6], 1)))
        
        #稠密链接提取图像的特征(放大系数)
        if self.scale_factor == 1:
            x_r = x_r
        else:
            x_r = self.upsample(x_r)
        #保证x_r 的大小和图像的大小一致
        # enhancement
        #zero-dce 的网络结构不同的是x_r是变化的,而本文的x_r得到以后就不会变化了(只是在 self.enhance中)
        enhance_image = self.enhance(x, x_r)
        return enhance_image, x_r

对比zero-dce网络的结构

"""
Model File
"""

from mindspore import nn
from mindspore import ops
from mindspore.common.initializer import Normal

class ZeroDCE(nn.Cell):
    """
    Main Zero DCE Model
    """
    def __init__(self, *, sigma=0.02, mean=0.0):
        super().__init__()

        self.relu = nn.ReLU()

        number_f = 32
        self.e_conv1 = nn.Conv2d(3, number_f, 3, 1, pad_mode='pad', padding=1, has_bias=True,
                                 weight_init=Normal(sigma, mean))
        self.e_conv2 = nn.Conv2d(number_f, number_f, 3, 1, pad_mode='pad', padding=1, has_bias=True,
                                 weight_init=Normal(sigma, mean))
        self.e_conv3 = nn.Conv2d(number_f, number_f, 3, 1, pad_mode='pad', padding=1, has_bias=True,
                                 weight_init=Normal(sigma, mean))
        self.e_conv4 = nn.Conv2d(number_f, number_f, 3, 1, pad_mode='pad', padding=1, has_bias=True,
                                 weight_init=Normal(sigma, mean))
        self.e_conv5 = nn.Conv2d(number_f * 2, number_f, 3, 1, pad_mode='pad', padding=1,
                                 has_bias=True, weight_init=Normal(sigma, mean))
        self.e_conv6 = nn.Conv2d(number_f * 2, number_f, 3, 1, pad_mode='pad', padding=1,
                                 has_bias=True, weight_init=Normal(sigma, mean))
        self.e_conv7 = nn.Conv2d(number_f * 2, 24, 3, 1, pad_mode='pad', padding=1, has_bias=True,
                                 weight_init=Normal(sigma, mean))

        self.split = ops.Split(axis=1, output_num=8)
        self.cat = ops.Concat(axis=1)

    def construct(self, x):
        """
        ZeroDCE inference
        """
        x1 = self.relu(self.e_conv1(x))
        x2 = self.relu(self.e_conv2(x1))
        x3 = self.relu(self.e_conv3(x2))
        x4 = self.relu(self.e_conv4(x3))

        x5 = self.relu(self.e_conv5(self.cat([x3, x4])))
        x6 = self.relu(self.e_conv6(self.cat([x2, x5])))

        x_r = ops.tanh(self.e_conv7(self.cat([x1, x6])))
        r1, r2, r3, r4, r5, r6, r7, r8 = self.split(x_r)

        x = x + r1 * (ops.pows(x, 2) - x)
        x = x + r2 * (ops.pows(x, 2) - x)
        x = x + r3 * (ops.pows(x, 2) - x)
        enhance_image_1 = x + r4 * (ops.pows(x, 2) - x)
        x = enhance_image_1 + r5 * (ops.pows(enhance_image_1, 2) - enhance_image_1)
        x = x + r6 * (ops.pows(x, 2) - x)
        x = x + r7 * (ops.pows(x, 2) - x)
        enhance_image = x + r8 * (ops.pows(x, 2) - x)
        r = self.cat([r1, r2, r3, r4, r5, r6, r7, r8])
        return enhance_image, r

语义分割网络

#这里的calss 文中默认参数为21
class fpn(nn.Module):
    def __init__(self, numClass):
        super(fpn, self).__init__()
        # Res net
        self.resnet = resnet50(True)

        # fpn module
        self.fpn = fpn_module(numClass)#详细的代码看作者公开的代码

        # init fpn
        for m in self.fpn.children():
            nn.init.normal_(m.weight, mean=0, std=0.01)
            nn.init.constant_(m.bias, 0)

    def forward(self, x):
        # Top-down
        c2, c3, c4, c5 = self.resnet.forward(x)#特征来自于预训练模型的不同尺度特征
        
        return self.fpn.forward(c2, c3, c4, c5)
    #输出是一个二维的张量为28个通道类别的概率

网络的训练:

    def train(self):
        self.net.train()

        for epoch in range(self.num_epochs):

            for iteration, img_lowlight in enumerate(self.train_loader):

                img_lowlight = img_lowlight.to(device)
                enhanced_image, A = self.net(img_lowlight)#需要学习的网络
                loss = self.get_loss(A, enhanced_image, img_lowlight, self.E)#需要优化的损失

                self.optimizer.zero_grad()
                loss.backward()
                torch.nn.utils.clip_grad_norm(self.net.parameters(), self.grad_clip_norm)
                self.optimizer.step()

                if ((iteration + 1) % self.display_iter) == 0:
                    print("Loss at iteration", iteration + 1, ":", loss.item())
                if ((iteration + 1) % self.snapshot_iter) == 0:
                    torch.save(self.net.state_dict(), self.snapshots_folder + "Epoch" + str(epoch) + '.pth')
  def get_loss(self, A, enhanced_image, img_lowlight, E):
        Loss_TV = 1600 * self.L_TV(A)
        loss_spa = torch.mean(self.L_spa(enhanced_image, img_lowlight))
        loss_col = 5 * torch.mean(self.L_color(enhanced_image))
        loss_exp = 10 * torch.mean(self.L_exp(enhanced_image, E))
        loss_seg = self.get_seg_loss(enhanced_image)

        loss = Loss_TV + loss_spa + loss_col + loss_exp + 0.1 * loss_seg

        return loss

本文主要提出的损失函数

   def get_NoGT_target(inputs):
    sfmx_inputs = F.log_softmax(inputs, dim=1)#按照行或者列来做归一化的,再做多一次log运算
    target = torch.argmax(sfmx_inputs, dim=1)#将输入input张量,无论有几维,首先将其reshape排列成一个一维向量,然后找出这个一维向量里面最大值的索引
    return target
    def get_seg_loss(self, enhanced_image):
        # segment the enhanced image
        seg_input = enhanced_image.to(device)
        seg_output = self.seg(seg_input).to(device)

        # build seg output
        target = (get_NoGT_target(seg_output)).data.to(device)

        # calculate seg. loss
        seg_loss = self.seg_criterion(seg_output, target)

        return seg_loss
self.seg_criterion = FocalLoss(gamma=2).to(device)
class FocalLoss(nn.Module):

    # def __init__(self, device, gamma=0, eps=1e-7, size_average=True):
    def __init__(self, gamma=0, eps=1e-7, size_average=True, reduce=True):
        super(FocalLoss, self).__init__()
        self.gamma = gamma
        self.eps = eps
        self.size_average = size_average
        self.reduce = reduce
        # self.device = device

    def forward(self, input, target):
        # y = one_hot(target, input.size(1), self.device)
        y = one_hot(target, input.size(1))
        probs = F.softmax(input, dim=1)
        probs = (probs * y).sum(1)  # dimension ???
        probs = probs.clamp(self.eps, 1. - self.eps)

        log_p = probs.log()
        # print('probs size= {}'.format(probs.size()))
        # print(probs)

        batch_loss = -(torch.pow((1 - probs), self.gamma)) * log_p
        # print('-----bacth_loss------')
        # print(batch_loss)

        if self.reduce:
            if self.size_average:
                loss = batch_loss.mean()
            else:
                loss = batch_loss.sum()
        else:
            loss = batch_loss
        return loss

公布的实验结果

image-20231109215024247

   if self.reduce:
        if self.size_average:
            loss = batch_loss.mean()
        else:
            loss = batch_loss.sum()
    else:
        loss = batch_loss
    return loss

公布的实验结果

[外链图片转存中...(img-34Z4SH7g-1699538813410)]















































本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1190851.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AI:76-基于机器学习的智能城市交通管理

🚀 本文选自专栏:AI领域专栏 从基础到实践,深入了解算法、案例和最新趋势。无论你是初学者还是经验丰富的数据科学家,通过案例和项目实践,掌握核心概念和实用技能。每篇案例都包含代码实例,详细讲解供大家学习。 📌📌📌在这个漫长的过程,中途遇到了不少问题,但是…

leetcode:LCP 11. 期望个数统计(python3解法)

难度:简单 某互联网公司一年一度的春招开始了,一共有 n 名面试者入选。每名面试者都会提交一份简历,公司会根据提供的简历资料产生一个预估的能力值,数值越大代表越有可能通过面试。 小 A 和小 B 负责审核面试者,他们均…

计算机二级公共基础

知识点 1.树 树的最大层次(最长路径的长度)称为树的深度 二叉树的后件最多不超过两个 满二叉树:除最后一层每一层的所有节点都有两个子节点。(满二叉树一定是完全二叉树) 完全二叉树:所有节点均达到最大数…

显著提升!| (WOA)融合模拟退火和自适应变异的混沌鲸鱼优化算法应用于函数寻优

鲸鱼优化算法(whale optimization algorithm,WOA)是由Mirjalili和Lewis[1]于2016年提出的一种新型群体智能优化搜索方法,它源于对自然界中座头鲸群体狩猎行为的模拟,与其它群体智能优化算法相比,WOA算法结构新颖, 控制参数少,在许多数值优化和…

JavaScript使用Ajax

Ajax(Asynchronous JavaScript and XML)是使用JavaScript脚本,借助XMLHttpRequest插件,在客户端与服务器端之间实现异步通信的一种方法。2005年2月,Ajax第一次正式出现,从此以后Ajax成为JavaScript发起HTTP异步请求的代名词。2006…

【论文阅读】DALL·E: Zero-Shot Text-to-Image Generation

OpenAI第一代文本生成图片模型 paper:https://arxiv.org/abs/2102.12092 DALLE有120亿参数,基于自回归transformer,在2.5亿 图片-文本对上训练的。实现了高质量可控的text to image,同时也有zero-shot的能力。 DALL-E没有使用扩…

汽车螺丝扭力标准/汽车常见螺栓扭矩参照

汽车螺丝扭力标准参照表如下: 1、支座与车身螺栓(13MM)--25Nm; 2、支座与车身螺栓(18MM)--40Nm90度/50Nm; 3、支座与发动机支座螺栓(18Mm)--100Nm; 4、支座与车身螺栓(13MM)--25Nm; 5、支座与车身螺栓(18MM)--40N…

【Rust日报】2023-11-08 RustyVault -- 基于 rust 的现代秘密管理系统

RustyVault -- 基于 rust 的现代秘密管理系统 RustyVault 是一个用 Rust 编写的现代秘密管理系统。RustyVault 提供多种功能,支持多种场景,包括安全存储、云身份管理、秘密管理、Kubernetes 集成、PKI 基础设施、密码计算、传统密钥管理等。RustyVault 可…

面试10000次依然会问的【线程池】,你还不会?

线程池的基本概念 线程池是一种基于池化技术的线程使用方式,它允许我们有效地管理和复用线程,减少线程的创建和销毁的开销,从而提高系统的响应速度。在Java中,线程池的管理主要通过ThreadPoolExecutor类来实现。 线程池的定义与…

docker可视化

什么是portainer? portainer就是docker图形化界面的管理工具,提供一个后台面板供我们操作 目前先用portainer(先用这个),以后还会用到Rancher(CI/CD在用) 1.下载portainer 9000是内网端口,8088是外网访问端口 docker run…

19 数据中心详解

1、数据中心的概念 其实平时我们不管是看新闻,视频,下载文件等,最终访问的目的地都是在数据中心里面。数据中心存放的是服务器,区别于我们平时使用的笔记本或者台式机。 机架:数据中心的服务器被放在一个个叫作机架&…

Go 接口:Go中最强大的魔法,接口应用模式或惯例介绍

Go 接口:Go中最强大的魔法,接口应用模式或惯例介绍 文章目录 Go 接口:Go中最强大的魔法,接口应用模式或惯例介绍一、前置原则二、一切皆组合2.1 一切皆组合2.2 垂直组合2.2.1 第一种:通过嵌入接口构建接口2.2.2 第二种:通过嵌入接…

Guli商城-商品服务-API-三级分类-配置网关路由与路径重写

启动人人fast服务: 打开本地的前端项目,启动: 命令:npm run dev 账号密码:admin/admin 对应的数据库: 接下来在商品系统目录中添加子菜单: 数据库中可以看到记录 退出账号,重新登录…

vue中实现千位分隔符

vue中实现千位分隔符有两种,一种是某一个字段转换,一种是表格table中的整列字段转换 比如将3236634.12,经过转换后变为 3,236,634.12 1. 某一个字段转换 写js方法: export function numberExchange(value){if (!value) return…

Android自定义 View惯性滚动效果(不使用Scroller)

效果图: 前言: 看了网上很多惯性滚动方案,都是通过Scroller 配合 computeScroll实现的,但在实际开发中可能有一些场景不合适,比如协调布局,内部子View有特别复杂的联动效果,需要通过偏移来配合…

JavaScript从入门到精通系列第三十二篇:详解正则表达式语法(一)

文章目录 一:正则表达式 1:量词设置次数 2:检查字符串以什么开头 3:检查字符串以什么结尾 4: 同时使用开头结尾 5:同值开头同值结尾 二:练习 1:检查是否是一个手机号 大神链…

(附源码)基于Springboot智慧园区管理系统-计算机毕设 88160

Springboot智慧园区管理系统的开发 摘要 随着互联网趋势的到来,互联网概念越来越盛行,园区管理最好方式就是建立自己的互联网系统。在现实运用中,应用软件的工作规则和开发步骤,采用Springboot框架建设智慧园区管理系统。 本设计主…

线性代数(五) | 矩阵对角化 特征值 特征向量

文章目录 1 矩阵的特征值和特征向量究竟是什么?2 求特征值和特征向量3 特征值和特征向量的应用4 矩阵的对角化 1 矩阵的特征值和特征向量究竟是什么? 矩阵实际上是一种变换,是一种旋转伸缩变换(方阵) 不是方阵的话还有可能是一种…

博阳精讯、凡得科技访问上海斯歌:共探BPM流程服务新高地

10月27日下午,来自博阳精讯、凡得科技的流程领域专家、领导一行参观访问了上海斯歌总部。三方举行了深度交流会谈,分享了彼此对流程领域的前沿洞察和技术实践,共同探索了BPM流程服务科技力与价值力的新高地。 本次研讨会上,博阳精…

WPF 线程模型

Windows Presentation Foundation (WPF) 旨在将开发人员从线程处理困难中解脱出来。 因此,大多数 WPF 开发人员不会编写使用多个线程的界面。 由于多线程程序既复杂又难以调试,因此当存在单线程解决方案时,应避免使用多线程程序。 但是&…