超分辨率——基于SRGAN的图像超分辨率重建(Pytorch实现)

article/2025/11/8 16:46:24

基于SRGAN的图像超分辨率重建

本文偏新手项,因此只是作为定性学习使用,因此不涉及最后的定量评估环节


目录

  • 基于SRGAN的图像超分辨率重建
    • 1 简要介绍
    • 2 代码实现
        • 2.1 开发环境
        • 2.2 主要流程
        • 2.3 构建数据集
        • 2.4 构建生成模型(Generator)
        • 2.5 构建辨别模型(Discriminator)
        • 2.6 初始化训练迭代器
        • 2.7 构造训练循环
    • 3 结果可视化

1 简要介绍

SRGAN的原论文发表于CVPR2017,即《Photo-Realistic Single Image Super-Resolution Using a Generative Adversarial Network》

SRGAN使用了生成对抗的方式来进行图像的超分辨率重建,同时提出了一个由Adversarial Loss和Content Loss组成的损失函数。

更详细的介绍可以去看看这篇文章 传送门

2 代码实现

2.1 开发环境

pytorch == '1.7.0+cu101'
numpy == '1.19.4'
PIL == '8.0.1'
tqdm == '4.52.0'
matplotlib == '3.3.3'

对于开发文件的路径为

/root- /Urban100- img_001.png- img_002.png···- img_100.png- /Img- /model- /result- main.py  #主代码应该放在这里

2.2 主要流程

这次代码的主要流程为
构 建 数 据 集 → 构 建 生 成 模 型 → 构 建 辨 别 模 型 → 构 建 迭 代 器 → 构 建 训 练 循 环 构建数据集\rightarrow 构建生成模型\rightarrow 构建辨别模型\rightarrow 构建迭代器\rightarrow 构建训练循环

2.3 构建数据集

这次的数据集用的是Urban100数据集,当然使用其他数据集也没有太大的问题(不建议使用带有灰度图的数据集,会报错)

在这里插入图片描述
在这里使用的构造方法和我的上一篇博客相同 传送门

首先我们先把数据集预处理类构建好

import torchvision.transforms as transforms
import torch
from torch.utils.data import Dataset
import numpy as np
import os
from PIL import Image#图像处理操作,包括随机裁剪,转换张量
transform = transforms.Compose([transforms.RandomCrop(96),transforms.ToTensor()]) class PreprocessDataset(Dataset):"""预处理数据集类"""def __init__(self,imgPath = path,transforms = transform, ex = 10):"""初始化预处理数据集类"""self.transforms = transformfor _,_,files in os.walk(imgPath): self.imgs = [imgPath + file for file in files] * exnp.random.shuffle(self.imgs)  #随机打乱def __len__(self):"""获取数据长度"""return len(self.imgs)def __getitem__(self,index):"""获取数据"""tempImg = self.imgs[index]tempImg = Image.open(tempImg)sourceImg = self.transforms(tempImg)  #对原始图像进行处理cropImg = torch.nn.MaxPool2d(4,stride=4)(sourceImg)return cropImg,sourceImg

随后,我们只需要构造一个DataLoader就可以在后续训练中使用到我们的模型了

path = './Urban100/'
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
BATCH = 32
EPOCHS = 100#构建数据集
processDataset = PreprocessDataset(imgPath = path)
trainData = DataLoader(processDataset,batch_size=BATCH)#构造迭代器并取出其中一个样本
dataiter = iter(trainData)
testImgs,_ = dataiter.next()
testImgs = testImgs.to(device)  #testImgs的用处是为了可视化生成对抗的结果

2.4 构建生成模型(Generator)

在文章中的生成模型即为SRResNet,下图为他的网络结构图

在这里插入图片描述
该模型是可以单独用于进行超分辨率训练的,详情请看 → \rightarrow 传送门

模型的构造代码如下

import torch.nn as nn
import torch.nn.functional as Fclass ResBlock(nn.Module):"""残差模块"""def __init__(self,inChannals,outChannals):"""初始化残差模块"""super(ResBlock,self).__init__()self.conv1 = nn.Conv2d(inChannals,outChannals,kernel_size=1,bias=False)self.bn1 = nn.BatchNorm2d(outChannals)self.conv2 = nn.Conv2d(outChannals,outChannals,kernel_size=3,stride=1,padding=1,bias=False)self.bn2 = nn.BatchNorm2d(outChannals)self.conv3 = nn.Conv2d(outChannals,outChannals,kernel_size=1,bias=False)self.relu = nn.PReLU()def forward(self,x):"""前向传播过程"""resudial = x out = self.conv1(x)out = self.bn1(out)out = self.relu(out)out = self.conv2(out)out = self.bn2(out)out = self.relu(out)out = self.conv3(out)out += resudialout = self.relu(out)return outclass Generator(nn.Module):"""生成模型(4x)"""def __init__(self):"""初始化模型配置"""super(Generator,self).__init__()#卷积模块1self.conv1 = nn.Conv2d(3,64,kernel_size=9,padding=4,padding_mode='reflect',stride=1)self.relu = nn.PReLU()#残差模块self.resBlock = self._makeLayer_(ResBlock,64,64,5)#卷积模块2self.conv2 = nn.Conv2d(64,64,kernel_size=1,stride=1)self.bn2 = nn.BatchNorm2d(64)self.relu2 = nn.PReLU()#子像素卷积self.convPos1 = nn.Conv2d(64,256,kernel_size=3,stride=1,padding=2,padding_mode='reflect')self.pixelShuffler1 = nn.PixelShuffle(2)self.reluPos1 = nn.PReLU()self.convPos2 = nn.Conv2d(64,256,kernel_size=3,stride=1,padding=1,padding_mode='reflect')self.pixelShuffler2 = nn.PixelShuffle(2)self.reluPos2 = nn.PReLU()self.finConv = nn.Conv2d(64,3,kernel_size=9,stride=1)def _makeLayer_(self,block,inChannals,outChannals,blocks):"""构建残差层"""layers = []layers.append(block(inChannals,outChannals))for i in range(1,blocks):layers.append(block(outChannals,outChannals))return nn.Sequential(*layers)def forward(self,x):"""前向传播过程"""x = self.conv1(x)x = self.relu(x)residual = xout = self.resBlock(x)out = self.conv2(out)out = self.bn2(out)out += residualout = self.convPos1(out)   out = self.pixelShuffler1(out)out = self.reluPos1(out)out = self.convPos2(out)   out = self.pixelShuffler2(out)out = self.reluPos2(out)out = self.finConv(out)return out

2.5 构建辨别模型(Discriminator)

辨别器采用了类似于VGG结构的模型,因此在实现上也没有很大难度
在这里插入图片描述

class ConvBlock(nn.Module):"""残差模块"""def __init__(self,inChannals,outChannals,stride = 1):"""初始化残差模块"""super(ConvBlock,self).__init__()self.conv = nn.Conv2d(inChannals,outChannals,kernel_size=3,stride = stride,padding=1,padding_mode='reflect',bias=False)self.bn = nn.BatchNorm2d(outChannals)self.relu = nn.LeakyReLU()def forward(self,x):"""前向传播过程"""out = self.conv(x)out = self.bn(out)out = self.relu(out)return outclass Discriminator(nn.Module):def __init__(self):super(Discriminator,self).__init__()self.conv1 = nn.Conv2d(3,64,kernel_size=3,stride=1,padding=1,padding_mode='reflect')self.relu1 = nn.LeakyReLU()self.convBlock1 = ConvBlock(64,64,stride = 2)self.convBlock2 = ConvBlock(64,128,stride = 1)self.convBlock3 = ConvBlock(128,128,stride = 2)self.convBlock4 = ConvBlock(128,256,stride = 1)self.convBlock5 = ConvBlock(256,256,stride = 2)self.convBlock6 = ConvBlock(256,512,stride = 1)self.convBlock7 = ConvBlock(512,512,stride = 2)self.avePool = nn.AdaptiveAvgPool2d(1)self.conv2 = nn.Conv2d(512,1024,kernel_size=1)self.relu2 = nn.LeakyReLU()self.conv3 = nn.Conv2d(1024,1,kernel_size=1)self.sigmoid = nn.Sigmoid()def forward(self,x):x = self.conv1(x)x = self.relu1(x)x = self.convBlock1(x)x = self.convBlock2(x)x = self.convBlock3(x)x = self.convBlock4(x)x = self.convBlock5(x)x = self.convBlock6(x)x = self.convBlock7(x)x = self.avePool(x)x = self.conv2(x)x = self.relu2(x)x = self.conv3(x)x = self.sigmoid(x)return x

(原谅我丑的一批的代码…)

2.6 初始化训练迭代器

在构建完数据集和两个网络之后,我们需要构造训练所需要的模型实例,损失函数,迭代器等。

这里迭代器使用的是Adam,两个网络的迭代器是互不相同的,为了保证网络之间对抗的稳定性,这里设置了两个模型的学习率相同。

SRGAN中使用了基于VGG提取的高级特征作为损失函数,因此需要使用到VGG预训练模型。

import torch.optim as optim
from torchvision.models.vgg import vgg16#构造模型
netD = Discriminator()
netG = Generator()
netD.to(device)
netG.to(device)#构造迭代器
optimizerG = optim.Adam(netG.parameters())
optimizerD = optim.Adam(netD.parameters())#构造损失函数
lossF = nn.MSELoss().to(device)#构造VGG损失中的网络模型
vgg = vgg16(pretrained=True).to(device)
lossNetwork = nn.Sequential(*list(vgg.features)[:31]).eval()
for param in lossNetwork.parameters():param.requires_grad = False  #让VGG停止学习

2.7 构造训练循环

训练的循环如下

from tqdm import tqdm
import torchvision.utils as vutils
import matplotlib.pyplot as pltfor epoch in range(EPOCHS):netD.train()netG.train()processBar = tqdm(enumerate(trainData,1))for i,(cropImg,sourceImg) in processBar:cropImg,sourceImg = cropImg.to(device),sourceImg.to(device)fakeImg = netG(cropImg).to(device)#迭代辨别器网络netD.zero_grad()realOut = netD(sourceImg).mean()fakeOut = netD(fakeImg).mean()dLoss = 1 - realOut + fakeOutdLoss.backward(retain_graph=True)optimizerD.step()#迭代生成器网络netG.zero_grad()gLossSR = lossF(fakeImg,sourceImg) gLossGAN = 0.001 * torch.mean(1 - fakeOut)gLossVGG = 0.006 * lossF(lossNetwork(fakeImg),lossNetwork(sourceImg))gLoss = gLossSR + gLossGAN + gLossVGGgLoss.backward()optimizerG.step()#数据可视化processBar.set_description(desc='[%d/%d] Loss_D: %.4f Loss_G: %.4f D(x): %.4f D(G(z)): %.4f' % (epoch, EPOCHS, dLoss.item(),gLoss.item(),realOut.item(),fakeOut.item()))#将文件输出到目录中with torch.no_grad():fig = plt.figure(figsize=(10,10))plt.axis("off")fakeImgs = netG(testImgs).detach().cpu()plt.imshow(np.transpose(vutils.make_grid(fakeImgs,padding=2,normalize=True),(1,2,0)), animated=True)plt.savefig('./Img/Result_epoch % 05d.jpg' % epoch, bbox_inches='tight', pad_inches = 0)print('[INFO] Image saved successfully!')#保存模型路径文件torch.save(netG.state_dict(), 'model/netG_epoch_%d_%d.pth' % (4, epoch))torch.save(netD.state_dict(), 'model/netD_epoch_%d_%d.pth' % (4, epoch))
[0/100] Loss_D: 1.0737 Loss_G: 0.0360 D(x): 0.1035 D(G(z)): 0.1772: : 33it [00:32,  1.02it/s]
0it [00:00, ?it/s]
[INFO] Image saved successfully!
[1/100] Loss_D: 0.8497 Loss_G: 0.0216 D(x): 0.6464 D(G(z)): 0.4960: : 33it [00:31,  1.04it/s]
0it [00:00, ?it/s]
[INFO] Image saved successfully!
[2/100] Loss_D: 0.9925 Loss_G: 0.0235 D(x): 0.5062 D(G(z)): 0.4987: : 33it [00:31,  1.05it/s]
0it [00:00, ?it/s]
[INFO] Image saved successfully!
[3/100] Loss_D: 0.9907 Loss_G: 0.0277 D(x): 0.4948 D(G(z)): 0.4856: : 33it [00:31,  1.06it/s]
0it [00:00, ?it/s]
[INFO] Image saved successfully!
[4/100] Loss_D: 0.9936 Loss_G: 0.0180 D(x): 0.0127 D(G(z)): 0.0062: : 33it [00:31,  1.06it/s]
0it [00:00, ?it/s]
[INFO] Image saved successfully!
[5/100] Loss_D: 1.0636 Loss_G: 0.0300 D(x): 0.2553 D(G(z)): 0.3188: : 33it [00:31,  1.06it/s]
0it [00:00, ?it/s]
[INFO] Image saved successfully!
[6/100] Loss_D: 1.0000 Loss_G: 0.0132 D(x): 0.1667 D(G(z)): 0.1667: : 33it [00:31,  1.06it/s]
0it [00:00, ?it/s]
[INFO] Image saved successfully!
[7/100] Loss_D: 1.1650 Loss_G: 0.0227 D(x): 0.1683 D(G(z)): 0.3333: : 33it [00:31,  1.06it/s]
0it [00:00, ?it/s]
[INFO] Image saved successfully!
[8/100] Loss_D: 1.0000 Loss_G: 0.0262 D(x): 0.1667 D(G(z)): 0.1667: : 33it [00:31,  1.05it/s]
0it [00:00, ?it/s]
[INFO] Image saved successfully!
···
[56/100] Loss_D: 1.0000 Loss_G: 0.0119 D(x): 1.0000 D(G(z)): 1.0000: : 33it [00:32,  1.01it/s]
0it [00:00, ?it/s]
[INFO] Image saved successfully!
[57/100] Loss_D: 1.0000 Loss_G: 0.0084 D(x): 1.0000 D(G(z)): 1.0000: : 33it [00:32,  1.03it/s]
0it [00:00, ?it/s]
[INFO] Image saved successfully!
[58/100] Loss_D: 1.0000 Loss_G: 0.0065 D(x): 1.0000 D(G(z)): 1.0000: : 33it [00:32,  1.03it/s]
0it [00:00, ?it/s]
[INFO] Image saved successfully!

在Img文件夹中保存了每次训练的可视化结果,在训练中,第一轮的结果如下所示:
在这里插入图片描述
而在第58轮中的结果为:
在这里插入图片描述

3 结果可视化

接下来将构造结果可视化的代码。
该代码的头文件为

import torch.nn as nn
import torch.nn.functional as F
import torch
from PIL import Image
import torchvision.transforms as transforms
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
import matplotlib.pyplot as plt

首先我们需要引入生成模型

class ResBlock(nn.Module):"""残差模块"""def __init__(self,inChannals,outChannals):"""初始化残差模块"""super(ResBlock,self).__init__()self.conv1 = nn.Conv2d(inChannals,outChannals,kernel_size=1,bias=False)self.bn1 = nn.BatchNorm2d(outChannals)self.conv2 = nn.Conv2d(outChannals,outChannals,kernel_size=3,stride=1,padding=1,bias=False)self.bn2 = nn.BatchNorm2d(outChannals)self.conv3 = nn.Conv2d(outChannals,outChannals,kernel_size=1,bias=False)self.relu = nn.PReLU()def forward(self,x):"""前向传播过程"""resudial = x out = self.conv1(x)out = self.bn1(out)out = self.relu(out)out = self.conv2(x)out = self.bn2(out)out = self.relu(out)out = self.conv3(x)out += resudialout = self.relu(out)return outclass Generator(nn.Module):"""生成模型(4x)"""def __init__(self):"""初始化模型配置"""super(Generator,self).__init__()#卷积模块1self.conv1 = nn.Conv2d(3,64,kernel_size=9,padding=4,padding_mode='reflect',stride=1)self.relu = nn.PReLU()#残差模块self.resBlock = self._makeLayer_(ResBlock,64,64,5)#卷积模块2self.conv2 = nn.Conv2d(64,64,kernel_size=1,stride=1)self.bn2 = nn.BatchNorm2d(64)self.relu2 = nn.PReLU()#子像素卷积self.convPos1 = nn.Conv2d(64,256,kernel_size=3,stride=1,padding=2,padding_mode='reflect')self.pixelShuffler1 = nn.PixelShuffle(2)self.reluPos1 = nn.PReLU()self.convPos2 = nn.Conv2d(64,256,kernel_size=3,stride=1,padding=1,padding_mode='reflect')self.pixelShuffler2 = nn.PixelShuffle(2)self.reluPos2 = nn.PReLU()self.finConv = nn.Conv2d(64,3,kernel_size=9,stride=1)def _makeLayer_(self,block,inChannals,outChannals,blocks):"""构建残差层"""layers = []layers.append(block(inChannals,outChannals))for i in range(1,blocks):layers.append(block(outChannals,outChannals))return nn.Sequential(*layers)def forward(self,x):"""前向传播过程"""x = self.conv1(x)x = self.relu(x)residual = xout = self.resBlock(x)out = self.conv2(out)out = self.bn2(out)out += residualout = self.convPos1(out)   out = self.pixelShuffler1(out)out = self.reluPos1(out)out = self.convPos2(out)   out = self.pixelShuffler2(out)out = self.reluPos2(out)out = self.finConv(out)return out

随后,我们初始化并构建可视化函数

device = torch.device("cpu")
net = Generator()
net.load_state_dict(torch.load("你的模型pth文件路径"))def imshow(path,sourceImg = True):"""展示结果"""preTransform = transforms.Compose([transforms.ToTensor()]) pilImg = Image.open(path)img = preTransform(pilImg).unsqueeze(0).to(device)source = net(img)[0,:,:,:]source = source.cpu().detach().numpy()  #转为numpysource = source.transpose((1,2,0)) #切换形状source = np.clip(source,0,1)  #修正图片if sourceImg:temp = np.clip(img[0,:,:,:].cpu().detach().numpy().transpose((1,2,0)),0,1)shape = temp.shapesource[-shape[0]:,:shape[1],:] = tempplt.imshow(source)img = Image.fromarray(np.uint8(source*255))img.save('./result/' + path.split('/')[-1][:-4] + '_result_with_source.jpg')  # 将数组保存为图片returnplt.imshow(source)img = Image.fromarray(np.uint8(source*255))img.save(path[:-4] + '_result.jpg')  # 将数组保存为图片

最后,只需要简单调用就好

imshow("你的图片路径",sourceImg = True)

以本次训练模型为例,拿一张从未见过的图片作为测试
在这里插入图片描述

能够看出细节问题还是很多的,因此可以考虑一下增加模型的训练时间,或者是修改一下模型的结构。


http://chatgpt.dhexx.cn/article/I5vxVUOf.shtml

相关文章

SRCNN神经网络

0 前言 超分辨率技术(Super Resolution,SR)是指从观测到的低分辨率图像重建出相应的高分辨率图像,在监控设备、卫星图像和医学影像等领域都拥有着重要的应用价值。 1 SRCNN SRCNN是深度学习用在超分辨率重建上的开山之作。 其结构十分简单&#xff0c…

SRGAN的理解

全文翻译见:https://blog.csdn.net/weixin_42113955/article/details/89001989 和https://blog.csdn.net/c2a2o2/article/details/78930865 1. ptrain是真正的HR图像,也就是data要预测的。 pG是生成的超分辨图像 好处在于:固定 G&#xff0c…

GANs综述

生成式对抗网络GANs及其变体 基础GAN 生成式对抗网络,是lan Goodfellow 等人在2014年开发的,GANs 属于生成式模型,GANs是基于最小值和最大值的零和博弈理论。 为此,GANs是由两个神经网络组成一个Generator。另一个是Discriminat…

图像的超分辨率重建SRGAN与ESRGAN

SRGAN 传统的图像超分辨率重建方法一般都是放大较小的倍数,当放大倍数在4倍以上时就会出现过度平滑的现象,使得图像出现一些非真实感。SRGAN借助于GAN的网络架构生成图像中的细节。 训练网络使用均方误差(MSE)能够获得较高的峰值…

SRGAN With WGAN

SRGAN With WGAN RGAN 是基于 GAN 方法进行训练的,有一个生成器和一个判别器,判别器的主体使用 VGG19,生成器是一连串的 Residual block 连接,同时在模型后部也加入了 subpixel 模块,借鉴了 Shi et al 的 Subpixel Ne…

SRGAN论文与ESRGAN论文总结

博客结构 SRGANContribution:Network Architecture:Generator NetworkDiscriminator Network Perceptual loss function:Experiments:Mean opinion score (MOS) testing: ESRGANContribution:Network Architecture:ESR…

SR-GNN

Session-based Recommendation with Graph Neural Networks 一、论文 1、理论 ​ SR-GNN是一种基于会话序列建模的推荐系统。会话序列专门表示一个用户过往一段时间的交互序列。 ​ 常用的会话推荐包括循环神经网络和马尔科夫链,但有两个缺点: 当一…

SRGAN(SRResNet)介绍

生成对抗网络GAN是由蒙特利尔大学Ian Goodfellow在2014年提出的机器学习架构。 生成式对抗网络(GAN, Generative Adversarial Networks )是一种深度学习模型,是近年来复杂分布上无监督学习最具前景的方法之一。模型通过框架中(至…

SRGAN

摘要: 尽管使用更快更深的卷积神经网络在单图像超分辨率的准确性和速度方面取得了突破,但一个核心问题仍然很大程度上未解决:当我们在大的升级因子上超分辨时,我们如何恢复更精细的纹理细节?基于优化的超分辨率方法的行…

深度学习计划(4)SRGan简析

SRGAN 一种用于图像超分辨率(SR)的生成对抗网络(GAN) 超分辨率:从低分辨率(LR)图像来估计其对应高分辨率(HR)图像的高挑战性任务被称作超分辨率(SR) 问题: 重建的SR图像中通常缺少纹理细节。有监督SR算法的优化目标通常是最小化恢复的HR图像和真实图像…

图像超分经典网络 SRGAN精确解析

SRGAN 核心思想 早期超分辨率方法的优化目标都是降低低清图像和高清图像之间的均方误差。降低均方误差,确实让增强图像和原高清图像的相似度更高。但是,图像的相似度指标高并不能代表图像的增强质量就很高。 为什么 SRGAN 的增强结果那么清楚呢&#x…

SRGAN简单了解

超分辨率问题的病态性质尤其表现在取较高的放大因子时,重构的超分辨率图像通常会缺失纹理细节。监督SR算法的优化目标函数通常取重建高分辨率图像和地面真值之间的均方误差,在减小均方误差的同时又可以增大峰值信噪比(PSNR),PSNR是评价和比较…

【超分辨】SRGAN详解及其pytorch代码解释

SRGAN详解 介绍网络结构损失函数数据处理网络训练 介绍 「2023年更新」本代码是学习参考代码,一般不能直接运行,想找现成能运行的建议看看其他的。 SRGAN是一个超分辨网络,利用生成对抗网络的方法实现图片的超分辨。 关于生成对抗网络&#…

超分之一文读懂SRGAN

这篇文章介绍SRResNet网络,以及将SRResNet作为生成网络的GAN模型用于超分,即SRGAN模型。这是首篇在人类感知视觉上进行超分的文章,而以往的文章以PSNR为导向,但那些方式并不能让人眼觉得感知到了高分辨率——Photo-Realistic。 参…

图像超分经典网络 SRGAN 解析 ~ 如何把 GAN 运用在其他视觉任务上

生成对抗网络(GAN)是一类非常有趣的神经网络。借助GAN,计算机能够生成逼真的图片。近年来有许多“AI绘画”的新闻,这些应用大多是通过GAN实现的。实际上,GAN不仅能做图像生成,还能辅助其他输入信息不足的视觉任务。比如SRGAN&…

Oracle常用函数汇总记录

Oracle常用函数汇总记录 一、SUBSTR 截取函数 用法:substr(字符串,截取开始位置,截取长度) //返回截取的字, 字符串的起始位置为1,截取时包含起始位置字符 1.SUBSTR( “Hello World”, 2 ) //返回结果为:ello World,从第二个字符开始截取至末位 2.SUBSTR( “Hello World”, -2…

oracle一些常用函数用法,Oracle常用函数及其用法

01、入门Oracle 本章目标: 掌握oracle安装、启动和关闭 基本管理以及常用工具 简单备份和恢复 熟练使用sql,掌握oracle常用对象 掌握数据库设计和优化基本方法 http://jingyan.baidu.com/article/5d6edee228308899eadeec3f.html oracle数据库&#xff1a…

oracle常用函数详解(详细)

Oracle SQL 提供了用于执行特定操作的专用函数。这些函数大大增强了 SQL 语言的功能。函数可以接受零个或者多个输入参数,并返回一个输出结果。 Oracle 数据库中主要使用两种类型的函数: 1. 单行函数:对每一个函数应用在表的记录中时&#…

event对象的offsetX、clientX、pageX、screenX及 window.innerWidth、outerWidth使用详解

目录 offset client screen page window.innerWidht offset offsetX、offsetY为当前鼠标点击位置距离当前元素参考原点(左上角)的距离,而不同浏览器参考原点的位置不尽相同,FF及Chrome中参考原点为内容区域左上角,不…

什么?你还不知道offsetX、offsetY和clientX、clientY和pageX、pageY和screenX、screenY的区别,进来唠唠

offsetX、offsetY: 鼠标相对于事件源元素的X,Y坐标。比如说,给黄色的盒子定义一个点击事件,则这个offset的坐标原点就在这个黄色盒子的左上角,offsetX、offsetY就是相对于这个盒子的x、y坐标 clientX、clientY: 鼠标相对于浏览器窗口可视区域…