心中无码,自然高清 | 联合去马赛克与超分辨率研究论文Pytorch复现
作者 | 知凡,个人公众号:林木蔚然读书会(ID:EspressoOcean),知乎ID:Uno Whoiam
本文授权转载自知乎
本文结构
简单扫盲
什么是去马赛克
什么是超分辨率
《Deep Residual Network for Joint Demosaicing and Super-Resolution》论文简介
论文创新点
论文模型结构
训练数据
论文模型效果
论文复现
Pytorch代码
Model
DataSet
Train
需要注意的细节
复现结果
数值结果
图片展示
一、简单扫盲
1、什么是去马赛克
首先,去马赛克嘛,大家都知道:

当然不是上图这样的,各位读者姥爷别想歪了,此马赛克非彼马赛克,这个去马赛克是数码相机成像中的一个关键性的环节。要说明白这个得从数码相机的感光元件说起。
我们知道,数码图像是由像素排列成的,而一个像素点是由RGB即红、绿、蓝三种颜色混合而成的,而数码相机的感光元件只能感受到光照的强度,要想在一个点上同时采集红、绿、蓝三种颜色的光照强度,在结构和制作成本上会是一场噩梦。这个问题该如何解决呢?
这个时候布莱斯.拜尔拿着自己发明的Bayer阵列振臂疾呼:弟弟们,大哥来救你们了!
Bayer阵列的思路很简单,既然在一个点上采三种光很难,那就只采一种光呗,何必为难感光元件?既然我们又必须采集到三种不同颜色的光,那么就在感光的排列上做做文章呗:

Bayer阵列

Bayer 阵列感光元件
采集到每个点只能采集到三种颜色的光中的一种,其它两种颜色的光则可以向邻居借得到,而这“借”的过程,我们就称之为“去马赛克”:

左:Bayer阵列图像(RAW图像) 右:高清无码TIFF图像

Bayer阵列图像局部放大

高清图像局部放大
看了这上面的图,知道为啥叫“去马赛克”了吗??
相关的算法有FlexISP、ADMM、DemosaicNet等。
2、什么是超分辨率?
简而言之,就是把低分辨率的图像变成高分辨率的:

深度学习的超分辨率方法已有很多,如SRCNN、FSRCNN、ESPCN、VDSR等。
桂花糖:从SRCNN到EDSR,总结深度学习端到端超分辨率方法发展历程
二、《Deep Residual Network for Joint Demosaicing and Super-Resolution》论文简介
下载地址:https://arxiv.org/abs/1802.06573
1、论文创新点
该论文的最大创新点和其标题一样,是第一次把去马赛克和超分辨率结合在一起做,直接从单通道的RAW图像中挖掘尽可能多的信息,直接生成超分辨率的三通道图片。相对于先做去马赛克,再做超分辨率,这样做的好处在于一可避免两个阶段的错误积累,产生质量更高的图片,二可减少运算量,减少计算时间。
2、论文模型结构
模型分为三个阶段:
a、提取颜色:用4x4的卷积,达到在Bayer图像中提取每个点真实颜色的目的
b、非线性映射:借鉴残差网络的模块构成深层网络提取特征
c、图像重构:借鉴ESPCN里的sub-pixel结构,将通道数减少4倍从而使得图像的高和宽分别提升两倍,达到超分辨率的目的


在论文中
a、Feature map的数量C=256。
b、采用的残差网络块的结构如下图,论文采用24个模块:

c、Sub-Pixel可参考ESPCN:

d、Batch Size为16x3x64x64
e、Learning Rate 每10000个batch降低一半
3、训练用的数据集
采用的是RAISE数据集中的6000张高清图片:
下载地址:http://loki.disi.unitn.it/RAISE/
对这些图片的处理如图所示:


1、将16MP的原始TIFF图像经过三次factor=1.25的resize后变成4MP的TIFF图像
2、将4MP的TIFF图像经过一次factor=2 的resize后变成1MP的TIFF图像
3、将1MP的图像,对于每个像素,抹去G、B,R、B,R,B通道的数据仅留下一个与Bayer阵列相匹配的通道,形成Bayer图像(类似下图),然后将三通道合并成一个通道。
4、至此,训练集已经制作完成,data为1MP的Bayer图像,label是步骤2产生的4MP图像。
4、论文模型效果



三、论文复现
1、Pytorch代码:
1.1、Model:
import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms
import numpy as np
# ResNet
# https://blog.csdn.net/sunqiande88/article/details/80100891
class ResidualBlock(nn.Module):
def __init__(self):
super(ResidualBlock, self).__init__()
self.left = nn.Sequential(
nn.Conv2d(in_channels=256, out_channels=256, kernel_size=3, stride=1, padding=1, bias=True),
nn.PReLU(),
nn.Conv2d(in_channels=256, out_channels=256, kernel_size=3, stride=1, padding=1, bias=True),
)
self.shortcut = nn.Sequential()
self.active_f = nn.PReLU()
def forward(self, x):
out = self.left(x)
out += self.shortcut(x)
out = self.active_f(out)
return out
class Net(nn.Module):
def __init__(self, resnet_level=2):
super(Net, self).__init__()
# ***Stage1***
# class torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)
self.stage1_1_conv4x4 = nn.Conv2d(in_channels=1, out_channels=256,
kernel_size=4, stride=2, padding=1, bias=True)
# Reference:
# CLASS torch.nn.PixelShuffle(upscale_factor)
# Examples:
#
# >>> pixel_shuffle = nn.PixelShuffle(3)
# >>> input = torch.randn(1, 9, 4, 4)
# >>> output = pixel_shuffle(input)
# >>> print(output.size())
# torch.Size([1, 1, 12, 12])
self.stage1_2_SP_conv = nn.PixelShuffle(2)
self.stage1_2_conv4x4 = nn.Conv2d(in_channels=64, out_channels=256,
kernel_size=3, stride=1, padding=1, bias=True)
# CLASS torch.nn.PReLU(num_parameters=1, init=0.25)
self.stage1_2_PReLU = nn.PReLU()
# ***Stage2***
self.stage2_ResNetBlock = []
for i in range(resnet_level):
self.stage2_ResNetBlock.append(ResidualBlock())
self.stage2_ResNetBlock = nn.Sequential(*self.stage2_ResNetBlock)
# ***Stage3***
self.stage3_1_SP_conv = nn.PixelShuffle(2)
self.stage3_2_conv3x3 = nn.Conv2d(in_channels=64, out_channels=256,
kernel_size=3, stride=1, padding=1, bias=True)
self.stage3_2_PReLU = nn.PReLU()
self.stage3_3_conv3x3 = nn.Conv2d(in_channels=256, out_channels=3,
kernel_size=3, stride=1, padding=1, bias=True)
def forward(self, x):
out = self.stage1_1_conv4x4(x)
out = self.stage1_2_SP_conv(out)
out = self.stage1_2_conv4x4(out)
out = self.stage1_2_PReLU(out)
out = self.stage2_ResNetBlock(out)
out = self.stage3_1_SP_conv(out)
out = self.stage3_2_conv3x3(out)
out = self.stage3_2_PReLU(out)
out = self.stage3_3_conv3x3(out)
return out
1.2、DataSet:
import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms
import torch.utils.data as data
from PIL import Image
import random
import numpy as np
# Reference link:
# 如何构建数据集
# https://oidiotlin.com/create-custom-dataset-in-pytorch/
# https://www.pytorchtutorial.com/pytorch-custom-dataset-examples/
# transforms 函数的使用
# https://www.jianshu.com/p/13e31d619c15
# ToTensor:convert a PIL image to tensor (H*W*C) in range [0,255] to a torch.Tensor(C*H*W) in the range [0.0,1.0]
# torch.set_default_tensor_type('torch.DoubleTensor')
class CustomDataset(data.Dataset):
# file_path TXT文件路径
# random_augment=1 随机裁剪数据增强
# block_size=64 裁剪大小
def __init__(self, file_path, block_size=64):
with open(file_path, 'r') as file:
self.imgs = list(map(lambda line: line.strip().split(' '), file))
self.Block_size = block_size
print("DataSet Size is: ", self.__len__())
# print(len(self.imgs))
# for i in self.imgs:
# print(len(i))
def __getitem__(self, index):
# 注意!!! 读入的Bayer图像最左上为:
# R G
# G B
# Reference API
# class torchvision.transforms.RandomCrop(size, padding=0, pad_if_needed=False)
# class torchvision.transforms.Compose([transforms_list,])->生成一个函数
data_path, label_path = self.imgs[index]
# print(index, data_path, label_path)
data = Image.open(data_path).convert('L')
label = Image.open(label_path).convert('RGB')
trans = transforms.Compose([transforms.ToTensor()])
data_img = trans(data)
label_img = trans(label)
return data_img, label_img
def __len__(self):
return len(self.imgs)
1.3、Train:
import torch
import torch.utils.data as data
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms
import numpy as np
import time
from PIL import Image
from DataSet import CustomDataset
from NewResNet import Net
from multiprocessing import Process
from Test_class import Run_test
# *** 超参数*** `
Parameter_path = './Final_train_LR.txt'
MODEL_PATH = './Final_Model.pkl'
EPOCH = 1
HALF_LR_STEP = 40000
LR = 0.0001
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# 训练集与测试集的路径
train_data_path = "./8K_TRAIN_DATA/8K_TRAIN_DATA.txt"
test_data_path = "./8K_CROSS_DATA/8K_CROSS_DATA.txt"
BATCH_BLOCK_SIZE = 64
BATCH_SIZE = 8
DATA_SHUFFLE = True
# 检查GPU是否可用
print("cuda:", torch.cuda.is_available(), "GPUs", torch.cuda.device_count())
# 保存和恢复模型
# https://www.cnblogs.com/nkh222/p/7656623.html
# https://blog.csdn.net/quincuntial/article/details/78045036
#
# 保存
# torch.save(the_model.state_dict(), PATH)
# 恢复
# the_model = TheModelClass(*args, **kwargs)
# the_model.load_state_dict(torch.load(PATH))
# # 只保存网络的参数, 官方推荐的方式
# torch.save(net.state_dict(), 'net_params.pkl')
## 加载网络参数
# net.load_state_dict(torch.load('net_params.pkl'))
print("Loading the LR...")
try:
P = open(Parameter_path)
P = list(P)
LR = float(P[0])
except:
print("Loading LR fail...")
print("Loading the saving Model...")
MyNet = Net(24).to(device)
try:
MyNet.load_state_dict(torch.load(MODEL_PATH))
except:
print("Loading Fail.")
pass
print("Loading the Training data...")
MyData = CustomDataset(file_path=train_data_path,
block_size=BATCH_BLOCK_SIZE)
# CLASS torch.utils.data.DataLoader(dataset, batch_size=1, shuffle=False,
# sampler=None, batch_sampler=None, num_workers=0, collate_fn=<function default_collate>,
# pin_memory=False, drop_last=False, timeout=0, worker_init_fn=None)
train_data = data.DataLoader(dataset=MyData,
batch_size=BATCH_SIZE,
shuffle=DATA_SHUFFLE)
# CLASS torch.optim.Adam(params, lr=0.001, betas=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=False)
Optimizer = torch.optim.Adam(MyNet.parameters(), lr=LR, betas=(0.9, 0.999), eps=1e-08)
# CLASS torch.nn.MSELoss(size_average=None, reduce=None, reduction='mean')
Loss_Func = nn.MSELoss()
counter = 0
print("Start training...")
for epoch in range(EPOCH):
for step, (data, label) in enumerate(train_data):
counter = counter + 1
if counter != 0 and counter % HALF_LR_STEP == 0:
LR = LR / 2
Optimizer = torch.optim.Adam(MyNet.parameters(), lr=LR, betas=(0.9, 0.999), eps=1e-08)
with open(Parameter_path, 'w') as f:
f.write(str(LR))
print('LR:', LR)
data, label = data.to(device), label.to(device)
start = time.perf_counter()
out = MyNet(data)
# print(type(out), out.shape)
loss = Loss_Func(out, label)
Optimizer.zero_grad()
loss.backward()
Optimizer.step()
print(loss)
print(epoch, step)
print("Time:", time.perf_counter() - start)
if counter != 0 and 0 == counter % 100:
print("Saving the model...")
torch.save(MyNet.state_dict(), MODEL_PATH)
2、需要注意的细节
a、卷积层大小的选择
VGG告诉了我们,没啥特殊的情况,3x3就是最好的选择。
b、训练集的制作
论文将HD图片裁剪成128x128的大小作为DNN模型的输出,后将128x128制作成64x64的Bayer图像作为模型的输入,必须要注意的是,每张64x64的图像像素的Bayer排列必须一致。我设定的Bayer排列从左上角开始为:
# R G
# G B
如果输入图像从左上角开始的Bayer排列不同,输出的颜色将会错乱。
c、生成图像
模型训练好后,想要生成高清图像,如果显存没法一次性将1MP大小的Bayer图片放进去,那么切成一块一块放进去,然后一块块拼起来即可。
但切块再拼起来的图块与图块之间会有明显的不连续:

左:原始图像 右:神经网络合成的图像 PSNR=25.1418

右图局部放大,拼接痕迹明显
为了避免生成的图像块与块之间存在不连续的情况,我的具体流程如下:
将Bayer图像对镜像Padding成图块的整数倍大小,比如HxW的原始图像,镜像Padding成(ceil(H/B)xB+2xS)+(ceil(W/B)xB+2xS)的大小,ceil表示上取整,B为块的边长,S为2的倍数,取2就可以。输入的图像要大一圈,然后取产生图像的中间部分做拼接,最后的图像就是连续的,如果不理解可以看示意图:


这样就可以解决图像拼接间隙的问题:

左:原始图像 右:神经网络合成的图像 PSNR=25.1272

然而,一个现象是,拼接痕迹没了,但图像的PSNR值也会降低一些。如下表所示:

当然不切割直接输入模型生成图片(B列)效果最好,然而图片太大会爆显存,真是纠结。
3、复现结果
a、数值结果

与论文结果对比:
SSIM值没有论文高,但很接近,PSNR值更好一些。
BTW,SSIM计算出的结果与其计算时选用的window size即滑窗大小很有关系,滑窗大小越大,SSIM越高,本文在计算时采用的11x11大小的滑窗,这与提出SSIM的论文《Image Quality Assessment: From Error Visibility to Structural Similarity》中一致。
(相关地址:http://www.voidcn.com/article/p-auyocqzg-bac.html)
b、图片展示
左为原始图片,右为神经网络模型生成的图片:

PSNR: 31.197238996689617 SSIM: 0.9097831587657645

PSNR: 32.89967806219095 SSIM: 0.9294818208128227

PSNR: 33.15050503169419 SSIM: 0.9472909901611216

PSNR: 30.873442524392864 SSIM: 0.9473571002561766

PSNR: 25.052382881653507 SSIM: 0.9404708529075997

PSNR: 38.69040333179672 SSIM: 0.9570685066296898
原文链接:
https://zhuanlan.zhihu.com/p/56493507
(本文为 AI科技大本营转载文章,转载请联系原作者)
在线分享会
◆
3月21日晚8点
◆
近年来,聊天机器人技术及产品得到了快速的发展,本课程将全面阐述聊天机器人的技术框架及工程实现细节,并对于聊天机器人的下一代范式:虚拟生命,进行了详细的剖析,同时,聚焦知识图谱在实现认知智能过程中的重要作用,给出了知识图谱的落地实践。
推荐阅读:
Pig变飞机?AI为什么这么蠢 | Adversarial Attack
3.15曝光:40亿AI骚扰电话和11家合谋者
如何从零开始用PyTorch实现Chatbot?(附完整代码)
杨超越第一,Python第二
麦克阿瑟奖得主Dawn Song:区块链能保密和保护隐私?图样图森破!
315 后,等待失业的程序员
大数据背后的无奈与焦虑:“128元连衣裙”划分矮穷挫与白富美?
京东强推 995 工作制,中国式变态加班何时休?
教训!学 Python 没找对路到底有多惨?
❤点击“阅读原文”,查看历史精彩文章。
相关文章:

【linux】Valgrind工具集详解(十四):Cachegrind(缓存和分支预测分析器)
一、概述 Cachegrind,它模拟CPU中的一级缓存I1,Dl和二级缓存,能够精确地指出程序中cache的丢失和命中。如果需要,它还能够为我们提供cache丢失次数,内存引用次数,以及每行代码,每个函数,每个模块,整个程序产生的指令数。这对优化程序有很大的帮助。 Cachegrind模拟程…

使用mvc框架搭建跟人站点
1、使用工具 vs 、iis 2、新建一个ASP.net mvc 项目。并写好必要的代码 3、解决方案管理器,项目右键、发布 4、 创建配置文件 弹出网站发布设置面板,点击自定义,创建新的发布配置文件: 输入你自己定义的配置文件名(这里随便输入&…

GitHub上7000+ Star的Python常用代码合集
作者 | 二胖并不胖来源 | 大数据前沿(ID:bigdataqianyan)今天二胖给大家介绍一个由一个国外小哥用好几年时间维护的Python代码合集。简单来说就是,这个程序员小哥在几年前开始保存自己写过的Python代码,同时把一些自己比较常用的代…

MIS通用管理组件_通用管理组件V2.1.0发布
MIS通用管理组件是一个基于.NET4.0的MIS微型框架,实现单点登录,MIS类管理系统集群化管理配置,操作权限细化,数据集权限逐级授权;提供C/S代码生成器,丰富的类库;提供全部相关的源代码,…
python 十大经典排序算法
排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。常见的内部排序算法有:插入排序、希尔排序、选择排序…

【linux】Valgrind工具集详解(十五):Callgrind(性能分析图)
一、概述 1、Callgrind Callgrind用于记录程序中函数之间的调用历史信息,对程序性能分析。默认情况下,收集的数据包括执行的指令数,它们与源码行的关系,函数之间的调用者、被调用者关系以及此类调用的数量。可选项是,对高速缓存模拟和分支预测(类似于Cachegrind)。 2…

WCF服务重构实录(上)
项目需求 之前的项目中采用了WCF,绑定模式选择的是netTcpBinding,宿主选择了控制台方式,主要考虑两方面优点: 方便管理宿主的生命周期提升服务性能但是在实际的开发过程中产生了许多问题,比如: 调试项目时必…

【Qt】QTest:编译Qt单元测试程序
一、使用方法 1、测试程序源码 TestQString.pro QT += testlib QT -= gui TARGET = tst_TestQStringTest CONFIG += console CONFIG -= app_bundle TEMPLATE = app SOURCES += tst_TestQStringTest.cpp DEFINES += SRCDIR=\\\"$$PWD/\\\"tst_Test…

1/10个iPhone Xs = 英伟达最便宜AI计算机,这是唯一的“核弹”?
整理 | 一一、阿司匹林出品 | AI科技大本营(ID:rgznai 100)北京时间 3 月 19 日 8 点左右,在美国加州圣何塞的圣何塞大学活动中心,第十届 GTC 大会的主会结束。与往年相比,尽管 99 美元的 AI 计算机备受关注࿰…

Python链接MySQL
本文介绍Python3连接MySQL的第三方库--PyMySQL的基本使用。 PyMySQL介绍 PyMySQL 是在 Python3.x 版本中用于连接 MySQL 服务器的一个库,Python2中则使用mysqldb。 Django中也可以使用PyMySQL连接MySQL数据库。 PyMySQL安装 使用pycharm安装PyMySQL 点击File-->右…

利用sendEmail-v1.55转发邮件
设置: /sendEmail-v1.55/sendEmail -f testtest.com -t 532126277qq.com -s s.test.com -xu testtest.com -xp 123456789 -a 文件的路径 -u 主题 -m 内容转载于:https://blog.51cto.com/holy2010/537968

【Git】git系统学习(一):常用指令
1、配置工具 $ git config --global user.name "[name]"设置用户名 $ git config --global user.email "[email address]"设置邮箱 $ git config --global color.ui auto自动配置命令行的输出颜色 $ git config --global color.ui true全部打开颜色配置…

腾讯裁撤中层干部,拥抱年轻人
据 36Kr 最新报道,数名消息人士证实,2018 年 12 月内部员工大会后,腾讯开始裁撤一批中层干部。整个腾讯大概有两百多名中干,此轮调整比例约为10%,有战略发展部的腾讯员工认为,实际甚至超过了这个比例。截止…

cmder里ls、pwd、自定义的alias等一系列命令都无法使用
win10下cmder很多命令history pwd无法使用,ls字体也没有颜色显示,其根本原因是win10下cmd控制台版本问题,切换回老版本就OK了 转载于:https://www.cnblogs.com/hdk1993/p/8620799.html

文件操作01 - 零基础入门学习C语言60
第十一章:文件操作01 让编程改变世界 Change the world by program C文件概述 所谓“文件”是指一组相关数据的有序集合。这个数据集有一个名称,叫做文件名。实际上在前面的各章中我们已经多次使用了文件,例如源程序文件、目标文件、可执…

iPad mini时隔四年更新,搭载A12芯片,起售价2999
整理 | 非主流出品 | AI科技大本营(公众号id:rgznai100)距离苹果的春季发布还有一周,但就在昨天,苹果毫无征兆地给广大果粉来了一场预热。3 月 18 日下午,苹果官网进行更新,悄悄地推出了两款新品…

【Qt】通过QtCreator源码学习Qt(一):pro文件
1、学习目的 学习pro文件的语法规则,这在跨平台项目中会经常用到。和条件编译相似,在pro中可以根据平台选择不同的编译模块、文件,还可以向源码中传递变量等。 2、学习方法 通过学习QtCreator源码中的pro文件,来掌握pro文件语法规则,下面以qtcreator.pro文件为例,先看…

TCP和UDP相关记录
有关于计算机网络的知识,准确来说我也忘得差不多了,现在要开始找实习了。努力从新学一下,记录在这里以防丢失。 --------------------------------------------------------- 首先对于网络层次有很多种分法。大致有7层结构、5层结构、4层结构…

win2003服务器iis6.0环境下php5.3.2安装配置
IIS6PHP5.3.2配置: 在windows下使用ApachePHP的,请选择VC6版本; windows下使用IISPHP的,请选择VC9版本 首先要知道的是,那个服务器平台对应PHP那个版本: 1、在windows下使用ApachePHP的,请选择…

李飞飞宣布成立斯坦福“以人为本AI研究院”
本位首发于公众号极客公园(ID:GeekPark)作者 | 沈知涵、biu编辑 | 宋德胜AI 不是要取代我们,而是让我们做得更好。这一次,台上的李飞飞不是 Google Cloud 的首席科学家,也不是斯坦福人工实验室(…

【Qt】菜单栏、工具栏、状态栏、右键菜单的实现
在QMainWidget基础上实现菜单栏、工具栏、状态栏、右键菜单。 头文件: #ifndef GWDEMO_H #define GWDEMO_H#include <QMainWindow> #include <QMenu> #include

云计算公司Zuora提交IPO申请 预计募资1亿美元
2019独角兽企业重金招聘Python工程师标准>>> 总部位于硅谷的云计算公司 Zuora 周五向美国证券交易委员会(SEC)提交招股说明书,计划通过首次公开募股(IPO)募集 1 亿美元资金。 Zuora 已发布针对云计算提供商…

浅谈“闭包”,什么才是“闭包”思想!—— javascript
先看一个简单小案例:<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"><HTML> <HEAD> <TITLE> New Document </TITLE> <META NAME"Generator" CONTENT""> <META NAME"…

Debug神经网络的五项基本原则
整理 | 琥珀出品 | AI科技大本营(公众号id:rgznai100)很多情况下,研究人员会遇到一个问题:使用机器学习框架实现的神经网络可能与理论模型相去甚远。验证这款模型是否可靠,直接方式就是不断修正和调参。例如…

iOS获取手机型号
2019独角兽企业重金招聘Python工程师标准>>> //不同iPhone设备屏幕比例 iPhone5,4寸,比例16:9 iPhone5c,4寸,比例16:9 iPhone5s,4寸,比例16:9 iPhone6&#x…

【Qt】通过QtCreator源码学习Qt(二):跨平台编程
1、Qt对当前平台的判断 在qsystemdetection.h中根据宏定义来判断当前的操作系统,常用的操作系统如下: Q_OS_WIN、Q_OS_LINUX、Q_OS_MAC、Q_OS_UNIX qsystemdetection.h源码如下 #ifndef QGLOBAL_H # include <QtCore/qglobal.h> #endif#ifndef QSYSTEMDETECTION_H

能说明你的Javascript技术很烂的五个原因
Javascript 在互联网上名声很臭,但你又很难再找到一个像它这样如此动态、如此被广泛使用、如此根植于我们的生活中的另外一种语言。它的低学习门槛让很多人都称它为学 前脚本语言,它另外一个让人嘲笑的东西是动态语言的概念是偏偏使用了高标准的静态数据…

FPGA在人工智能时代的独特优势
来源 | 老石谈芯作者 | 老石,博士毕业于伦敦帝国理工大学电子工程系,现任某知名半导体公司高级FPGA研发工程师,深耕于FPGA的数据中心网络加速、网络功能虚拟化、高速有线网络通信等领域的研发和创新工作。曾经针对FPGA、高性能与可重构计算等…

【Qt】通过QtCreator源码学习Qt(三):linux平台的信号、程序崩溃处理
崩溃处理设置:CrashHandlerSetup 1、原理 在堆中为信号处理函数分配一块区域,作为该函数的栈使用,当系统默认的栈空间用尽时,调用信号处理函数使用的栈是在堆中分配的空间,而不是系统默认的栈中,所以它仍旧可以继续工作,执行崩溃处理程序。 崩溃处理使用的LSM(Linux…

WebGL 3D 工业隧道监控实战
2019独角兽企业重金招聘Python工程师标准>>> 前言 监控隧道内的车道堵塞情况、隧道内的车祸现场,在隧道中显示当前车祸位置并在隧道口给与提示等等功能都是非常有必要的。这个隧道 Demo 的主要内容包括:照明、风机、车道指示灯、交通信号灯、…