基于相位的运动放大:如何检测和放大难以察觉的运动(02/2)

news2025/1/16 8:07:26

·

目录

  • 一、说明
  • 二、算法
  • 三、准备处理
  • 四、高斯核
  • 五、带通滤波器
  • 六、复杂的可操纵金字塔
  • 七、最终预处理步骤
  • 八、执行处理
  • 九、金字塔的倒塌
  • 十、可视化结果
  • 十一、结论

一、说明

  日常物体会产生人眼无法察觉的微妙运动。在视频中,这些运动的幅度小于一个像素,但是,我们可以使用基于相位的视频放大来揭示这些不可见的运动,在这篇文章中,我们将用 Python 从头开始​​编码!如果您对理论感兴趣,我们在上一篇文章中对此进行了介绍。结果示例如图 6 所示。

  • 第 1 部分:背景和理论
  • 第 2 部分:Python 实现(这一部分)
    在这里插入图片描述

吉列尔莫·费拉 (Guillermo Ferla)在Unsplash上拍摄的照片

二、算法

我们在第 1 部分中介绍了高级算法,但它确实没有提供足够的实现细节。我们将使用的算法如下所示。

在这里插入图片描述

运动放大算法。资料来源:作者。
大部分处理发生在步骤 3 中,我们进行实际运动放大的部分是蓝色文本。相位变化对应于视频帧序列中的局部运动。在接下来的三节中,我们将分解该算法并从头开始实现它。

三、准备处理

在本节中,我们将介绍步骤 0-2。

第一个任务是获取视频帧,为了简单起见,我们将它们全部加载到内存中。我们还需要获取视频采样率,这对于 OpenCV 来说相当简单。在本教程中,我们将图像转换为 YIQ 颜色空间并仅处理 Luma 通道,请参阅笔记本以获取完整代码。

scale_factor = 1.0  # 可选:缩放图像,使其适合内存:)
 bgr_frames = [] 
frames = [] # 处理
帧 cap = cv2.VideoCapture(video_path) 

# 视频采样率
fs = cap.get(cv2.CAP_PROP_FPS) 

idx = 0 

while (cap.isOpened()): 
    ret, frame = cap.read() 
    # 如果帧读取正确 ret 为 True 
    if  not ret: 
        break 

    if idx == 0 : 
        og_h, og_w, _ = frame.shape 
        w = int (og_w*scale_factor) 
        h = int (og_h*scale_factor) 

    # 存储原始帧
    bgr_frames.append(frame) 

    # 获取归一化的 YIQ 帧
    rgb = np.float32(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)/ 255 ) 
    yiq = rgb2yiq(rgb) 

    # 追加亮度通道
    frames.append(cv2.resize(yiq[:, :, 0 ], (w, h)) 

    idx += 1

以下是我们将使用的 Luma 通道帧的示例:
在这里插入图片描述

图 1。右:帧 0 的亮度通道。左:帧 1 和 0 的亮度通道之间的差异。来源:作者。
我们可以在图 1 右侧的帧差异中看到起重机的微弱轮廓。这表明这些像素可能会发生一些有趣的事情,但这很可能归因于噪声,因为我们得到了类似的建筑物轮廓移动。该算法能够通过使用许多视频帧来提取小于像素的细微运动。下一个任务是设置超参数:

# 时间带通滤波器频率
f_lo = 0.2 
 f_hi = 0.25 

# 加权幅度模糊强度
sigma = 5.0  

# 衰减其他频率
attenuate = True 

# 相位放大因子
phase_mag = 25.0

四、高斯核

现在让我们继续预处理并获取幅度加权模糊的高斯核。如第 1 部分所述,这是可选的,没有它我们仍然可以获得良好的结果。

# 确保 ksize 为奇数,否则过滤将花费太长时间
# 请参阅以下警告:https://pytorch.org/docs/stable/ generated/torch.nn.function.conv2d.html
 ksize = np. max (( 3 , np.ceil( 4 *sigma) - 1 )).astype( int ) 
if ((ksize % 2 ) != 1 ): 
    ksize += 1 

# 获取高斯模糊内核仅供参考
gk = cv2. getGaussianKernel(ksize=ksize, sigma=sigma) 
gauss_kernel = torch.tensor(gk @ gk.T). 类型(torch.float32) \ 
                                      .to(设备) \ 
                                      .unsqueeze( 0 ) \ 
                                      .unsqueeze( 0 )

在这里插入图片描述

图 2. 用于幅度加权模糊的高斯核。资料来源:作者。

五、带通滤波器

对于带通滤波器,我们将使用scipy.signal构建FIR 滤波器。我们需要做的第一件事是将我们的频率标准化为奈奎斯特速率,这基本上意味着标准化后有效频率的范围将在 [0–1] 之间。任何大于 1 的归一化频率都会出现混叠。

from scipy import signal 

# 将频率标准化为奈奎斯特速率
norm_f_lo = f_lo / fs * 2norm_f_hi
 = f_hi / fs * 2 

# 获取带通脉冲响应
bandpass = signal.firwin(numtaps= len (frames), 
                         cutoff=[norm_f_lo,norm_f_hi] , 
                         pass_zero= False ) 

# 只要包含采样率 fs,我们就可以传递非标准化频率
# bandpass = signal.firwin(numtaps=len(frames), 
# cutoff=[f_lo, f_hi], 
# pass_zero=False, 
# fs =fs) 

# 获取频域传递函数
transfer_function = torch.fft.fft( 
    torch.fft.ifftshift(torch.tensor(bandpass))).to(device) \ 
                                                . 类型(torch.complex64) 
Transfer_function = torch.tile(transfer_function, 
                               [ 1 , 1 , 1 , 1 ]).permute( 0 , 3 , 1 , 2 )

在继续之前,让我们看一下滤波器的频域响应。

norm_freqs, response = signal.freqz(bandpass)
freqs = norm_freqs / np.pi * fs/ 2 

_, ax = plt.subplots(2, 1, figsize=(15, 7))
ax[0].plot(freqs, 20*np.log10(np.abs(response)));
ax[0].set_title("Frequency Response");

ax[1].plot(freqs, np.angle(response));
ax[1].set_title("Phase Response");

在这里插入图片描述

图 3. 带通 FIR 滤波器的频域响应。资料来源:作者。
上图显示了我们的频率响应,我们可以看到该频段大致对应于我们最初设置的 0.2–0.25 频率。

六、复杂的可操纵金字塔

现在我们可以获得复杂的可转向金字塔类,并且如第 1 部分中所述,亚倍频程滤波器可以实现更好的运动放大,因此我们选择半倍频程滤波器。

max_depth = int(np.floor(np.log2(np.min(np.array(ref_frame.shape)))) - 2)
csp = SteerablePyramid(depth=max_depth, 
                       orientations=8, 
                       filters_per_octave=2, 
                       twidth=0.75, 
                       complex_pyr=True)

这是频率空间中所有定向子带的划分,红色较高的值是由于滤波器的重叠造成的。可操纵金字塔分解过于完整,这意味着它们包含足够的信息来重建图像。复杂可操纵金字塔仅捕获正频率,因为负频率可以通过复杂共轭获得。
在这里插入图片描述

图 4. 频率空间中所有复杂可控滤波器的定向子带划分。左:是所有子带滤波器的总和,以显示重叠的强度。右:颜色编码的子带滤波器。资料来源:作者。

七、最终预处理步骤

接下来我们将过滤器和视频帧转换为 PyTorch 张量:

filters_tensor = torch.tensor(np.array(filters)).type(torch.float32).to(device)
frames_tensor = torch.tensor(np.array(frames)).type(torch.float32).to(device)

然后我们对视频帧进行离散傅里叶变换(DFT):

video_dft = torch.fft.fftshift(
  torch.fft.fft2(frames_tensor, dim=(1,2))).type(torch.complex64) \
                                           .to(device)

最后我们创建一个张量来存储运动放大帧的 DFT。

recon_dft = torch.zeros((len(frames), h, w), dtype=torch.complex64).to(device)

八、执行处理

本节介绍步骤 3

现在我们终于可以开始处理了,我们的外循环将包含金字塔过滤器。由于我们使用 PyTorch,我们可以方便地在 GPU 上并行化。我们要做的第一件事是创建一个张量来存储相位增量,即当前帧和参考帧的相位之间的差异。

Phase_deltas = torch.zeros((batch_size, len (frames), h, w), 
                           dtype=torch.complex64).to(device)

滤波器上的循环不包括高通和低通分量,我们将在主处理后将它们添加回来。这是第 3 步的完整代码块:

for level in range(1, len(filters) - 1, batch_size):

    # get batch indices
    idx1 = level
    idx2 = level + batch_size

    # get current filter batch
    filter_batch = filters_tensor[idx1:idx2]

    ## get reference frame pyramid and phase (DC)
    ref_pyr = build_level_batch(video_dft[ref_idx, :, :].unsqueeze(0), filter_batch)
    ref_phase = torch.angle(ref_pyr)

    ## Get Phase Deltas for each frame
    for vid_idx in range(num_frames):
        curr_pyr = build_level_batch(
                    video_dft[vid_idx, :, :].unsqueeze(0), filter_batch)

        # unwrapped phase delta
        _delta = torch.angle(curr_pyr) - ref_phase 

        # get phase delta wrapped to [-pi, pi]
        phase_deltas[:, vid_idx, :, :] = ((torch.pi + _delta) \
                                            % 2*torch.pi) - torch.pi
    
    ## Temporally Filter the phase deltas
    # Filter in Frequency Domain and convert back to phase space
    phase_deltas = torch.fft.ifft(transfer_function \
                                  * torch.fft.fft(phase_deltas, dim=1),  
                                  dim=1).real
    ## Apply Motion Magnifications
    for vid_idx in range(num_frames):

        curr_pyr = build_level_batch(video_dft[vid_idx, :, :].unsqueeze(0), filter_batch)
        delta = phase_deltas[:, vid_idx, :, :]

        ## Perform Amplitude Weighted Blurring
        if sigma != 0:
            amplitude_weight = torch.abs(curr_pyr) + eps
            
            # Torch Functional Approach (faster)
            weight = F.conv2d(input=amplitude_weight.unsqueeze(1), 
                              weight=gauss_kernel, 
                              padding='same').squeeze(1)
            
            delta = F.conv2d(input=(amplitude_weight * delta).unsqueeze(1), 
                              weight=gauss_kernel, 
                              padding='same').squeeze(1) 
  
            # get weighted Phase Deltas
            delta /= weight

        ## Modify phase variation
        modifed_phase = delta * phase_mag

        ## Attenuate other frequencies by scaling magnitude by reference phase
        if attenuate:
            curr_pyr = torch.abs(curr_pyr) * (ref_pyr/torch.abs(ref_pyr)) 


        ## apply modified phase to current level pyramid decomposition
        # if modified_phase = 0, then no change!
        curr_pyr = curr_pyr * torch.exp(1.0j*modifed_phase) # ensures correct type casting

        ## accumulate reconstruced levels
        recon_dft[vid_idx, :, :] += recon_level_batch(curr_pyr, filter_batch).sum(dim=0)

让我们逐步介绍这一点,我们要做的第一件事是获取当前批次的过滤器。所以我们一次只建造金字塔的一部分,而不是一次建造整个金字塔。

# get batch indices
idx1 = level
idx2 = level + batch_size

# get current filter batch
filter_batch = filters_tensor[idx1:idx2]

在下一步中,我们将构建参考金字塔并获取滤波器批次的参考相位。

## get reference frame pyramid and phase (DC)
ref_pyr = build_level_batch(video_dft[ref_idx, :, :].unsqueeze(0), 
                            filter_batch)
ref_phase = torch.angle(ref_pyr)

在下一步中,我们进入第一个内部循环,迭代所有视频帧。此步骤的主要目的是获取当前滤波器批次的所有帧的相位增量。

## Get Phase Deltas for each frame
for vid_idx in range(num_frames):
    # compute pyramid decomposition -> OPTIONALLY store in data structure
    curr_pyr = build_level_batch(
                video_dft[vid_idx, :, :].unsqueeze(0), filter_batch)

    # unwrapped phase delta
    _delta = torch.angle(curr_pyr) - ref_phase 

    # get phase delta wrapped to [-pi, pi]
    phase_deltas[:, vid_idx, :, :] = ((torch.pi + _delta) \
                                        % 2*torch.pi) - torch.pi

该循环的最后一行确保相位增量被包裹到[-π, π]。下一步是在频域中执行时间带通滤波,我们使用下面的单线来执行此操作,其中我们采用逆 DFT 返回到时域。

## Temporally Filter the phase deltas
# Filter in Frequency Domain and convert back to phase space
phase_deltas = torch.fft.ifft(transfer_function \
                              * torch.fft.fft(phase_deltas, dim=1),  
                              dim=1).real

现在我们准备开始修改运动,我们进入第二个循环,再次迭代视频帧。我们要做的第一件事是获取批量的金字塔级别及其相应的过滤相位增量。然后我们可以选择执行幅度加权模糊,这在第 1 部分中进行了解释。

## Apply Motion Magnifications
for vid_idx in range(num_frames):
    # rebuild level or grab from stored tensor
    curr_pyr = build_level_batch(video_dft[vid_idx, :, :].unsqueeze(0), 
                                 filter_batch)
    delta = phase_deltas[:, vid_idx, :, :]

    ## Perform Amplitude Weighted Blurring
    if sigma != 0:
        amplitude_weight = torch.abs(curr_pyr) + eps
        
        # Torch Functional Approach (faster)
        weight = F.conv2d(input=amplitude_weight.unsqueeze(1), 
                          weight=gauss_kernel, 
                          padding='same').squeeze(1)
        
        delta = F.conv2d(input=(amplitude_weight * delta).unsqueeze(1), 
                          weight=gauss_kernel, 
                          padding='same').squeeze(1) 

        # get weighted Phase Deltas
        delta /= weight

接下来,我们实际上可以修改当前滤镜批次中每一帧的运动。该片段的秒部分有一些我们尚未涉及的内容,那就是频率衰减。

## Modify phase variation
modifed_phase = delta * phase_mag

## Attenuate other frequencies by scaling magnitude by reference phase
if attenuate:
    curr_pyr = torch.abs(curr_pyr) * (ref_pyr/torch.abs(ref_pyr)) 

频率衰减消除了我们想要修改的频率之外的所有频率,但是如何呢?它通过首先获取当前金字塔分解的幅度来实现这一点,该分解消除了所有相位信息,但我们知道没有相位信息我们无法重建图像。因此,我们需要添加某种相位信息,我们选择添加直流相位(或参考帧相位),这是通过乘以归一化参考帧来完成的。

然后,我们可以将修改后的阶段添加到当前的金字塔批次中。我们通过将相位放入复指数中并将其与当前金字塔相乘来实现此目的。

## apply modified phase to current level pyramid decomposition
# if modified_phase = 0, then no change!
curr_pyr = curr_pyr * torch.exp(1.0j*modifed_phase) # ensures correct type casting

然后,我们重建金字塔以获得修改后的金字塔批次的 DFT 并将其累积到我们的结果张量中。

## accumulate reconstruced levels
recon_dft[vid_idx, :, :] += recon_level_batch(curr_pyr, filter_batch).sum(dim=0)

九、金字塔的倒塌

本节涵盖步骤 4 和 5

现在我们可以讨论重建,我们需要做的第一件事是重新合并高通和低通组件。我发现高通组件对于精确重建并不重要,因此您可以选择将其忽略。

# adding hipass component seems to cause bad artifacts and leaving
# it out doesn't seem to impact the overall quality
hipass = filters_tensor[0]
lopass = filters_tensor[-1]

## add back lo and hi pass components
for vid_idx in range(num_frames):
    # accumulate Lo Pass Components
    curr_pyr_lo = build_level(video_dft[vid_idx, :, :], lopass)
    dft_lo = torch.fft.fftshift(torch.fft.fft2(curr_pyr_lo))
    recon_dft[vid_idx, :, :] += dft_lo*lopass

    # OPTIONAL accumulate Lo Pass Components
    curr_pyr_hi = build_level(video_dft[vid_idx, :, :], hipass)
    dft_hi = torch.fft.fftshift(torch.fft.fft2(curr_pyr_hi)) 
    recon_dft[vid_idx, :, :] += dft_hi*hipass

现在我们可以在所有帧上执行逆 DFT,这是重建的最后一步。

result_video = torch.fft.ifft2(torch.fft.ifftshift(recon_dft, dim=(1,2)), dim=(1,2)).real
result_video = result_video.cpu() # Remove from CUDA

十、可视化结果

我们可以为每一帧创建并排比较图像。
在这里插入图片描述

图 5.每个修改后的视频帧的并排比较。资料来源:作者。
我们甚至可以创建一个 GIF,我们只处理与图像亮度相对应的 Luma 通道。这通常足以提供良好的结果,而不是处理所有通道。
在这里插入图片描述

图 6. 基于相位的运动处理结果的 GIF。资料来源:作者。
可视化细节
现在我们已经看到了实际结果,让我们看看到底发生了什么。这是一个执行此操作的图像,生成这些图像的代码位于笔记本的最后部分。
在这里插入图片描述

图 6. 所有帧的详细运动放大。资料来源:作者。
绿线显示了我们正在检查的单个像素线,我们可以可视化这些像素线在原始视频和处理后的视频中如何变化,并进行并排比较。在图6的右侧,我们可以看到原始视频中起重机的结构在整个序列中显然没有明显的运动,但放大的一侧确实显示出运动!更好的是,我们可以看到两个不同区域的放大运动是不同的,这表明该技术可以适应局部运动。换句话说,我们不仅仅是应用无意识的放大系数,而是根据物体的实际移动方式来放大运动。这表明该技术并不是一种奇特的视觉效果,但它确实是运动的视觉显微镜。

十一、结论

实现基于相位的运动放大是一种视频处理技术,能够放大看似静态视频中难以察觉的运动。由于金字塔分解量较大,该技术的计算成本很高,但 GPU 实现能够极大地加快该过程,从使用纯 numpy 实现的 5 分钟以上到 6GB GPU 上的不到 30 秒。

结果并不完美,但处理能够清楚地放大原本看不见的运动。有一些新的基于深度学习的方法能够提供更清晰的结果。

感谢您阅读到最后!如果您发现这篇文章有用,请考虑鼓掌👏它确实可以帮助其他可能也觉得有用的人

链接:
源视频可以在这里找到:http 😕/people.csail.mit.edu/nwadhwa/phase-video/
本教程的 Github 存储库:https 😕/github.com/itberrios/phase_based
参考
[1] 哈维尔·波蒂利亚和埃罗·西蒙切利。(2000)。基于复小波系数联合统计的参数化纹理模型。国际计算机视觉杂志。40. 10.1023/A:1026553619983。

[2] Wadhwa, N.、Rubinstein, M.、Durand, F. 和 Freeman, WT (2013)。基于相位的视频运动处理。基于相位的视频运动处理。https://people.csail.mit.edu/nwadhwa/phase-video/

[3] https://rxian.github.io/phase-video/

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

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

相关文章

【电子书】系统_网络_运维

资料 wx:1945423050 整理了一些互联网电子书,推荐给大家 系统_网络_运维 4G无线网络原理及优化.epubHyperledger Fabric 技术内幕:架构设计与实现原理.epubJSP应用与开发技术(第3版).epubKali Linux 2网络渗透测试实…

前后端分离Vue+node.js在线学习考试系统gqw7o

与其它应用程序相比,在线学习平台的设计主要面向于学校,旨在为管理员和学生、教师、院系提供一个在线学习平台。学生、教师、院系可以通过系统及时查看公告信息等。 在线学习平台是在Windows操作系统下的应用平台。为防止出现兼容性及稳定性问题&#xf…

深入理解指针2

各位小伙伴们,我们继续来学习指针,指针和结构体以及动态内存管理对后面的数据结构学习有非常大的帮助,所有我们一定要把这些知识点学会。OK,正式进入学习之旅吧 1.数组名的理解 在上⼀个章节我们在使⽤指针访问数组的内容时,有这…

【RN】学习使用 Reactive Native内置UI组件

简言 当把导航处理好后,就可以学习使用ui组件了(两者没有先后关系,个人习惯)。 在 Android 和 iOS 开发中,一个视图是 UI 的基本组成部分:屏幕上的一个小矩形元素、可用于显示文本、图像或响应用户输入。甚…

300分钟吃透分布式缓存-14讲:大数据时代,MC如何应对新的常见问题?

大数据时代 Memcached 经典问题 随着互联网的快速发展和普及,人类进入了大数据时代。在大数据时代,移动设备全面融入了人们的工作和生活,各种数据以前所未有的 速度被生产、挖掘和消费。移动互联网系统也不断演进和发展,存储、计…

低代码与大语言模型的探索实践

低代码系列文章: 可视化拖拽组件库一些技术要点原理分析可视化拖拽组件库一些技术要点原理分析(二)可视化拖拽组件库一些技术要点原理分析(三)可视化拖拽组件库一些技术要点原理分析(四)低代码…

推荐系统经典模型YouTubeDNN

文章目录 YouTubeDNN概念YouTubeDNN模型架构图YouTubeDNN召回阶段YouTubeDNN层级介绍 YouTubeDNN排序阶段YoutubeDNN模型中的一些Trick负采样问题特征构造上下文选择 总结 YouTubeDNN概念 YouTubeDNN是YouTube用于做视频推荐的落地模型,其大体思路就是召回阶段使用…

【MySQL面试复习】什么是聚簇索引(聚集索引)和非聚簇索引(二级索引)/什么是回表?

系列文章目录 在MySQL中,如何定位慢查询? 发现了某个SQL语句执行很慢,如何进行分析? 了解过索引吗?(索引的底层原理)/B 树和B树的区别是什么? 系列文章目录什么是聚簇索引(聚集索引&#xff09…

逆向茶话会笔记

安卓逆向 用用burp设置代理或者用charles抓包 windows httpopen 类比web站点渗透测试 推荐书 飞虫 安卓大佬不怎么打ctf 喜欢在看雪和吾爱破解 提问环节 q websocket grpc抓包有什么推荐的工具? a 不太了解 游戏安全和llvm 既要逆游戏也要逆外挂 逆游戏入…

分布式知识整理

分布式锁 以商场系统超卖现象举例 超卖现象一 现象: 商品卖出数量超出了库存数量。 产生原因: 扣减库存的动作在程序中进行,在程序中计算剩余库存,在并发场景下,导致库存计算错误。 代码复现 es.shutdown(); cycl…

笔记本hp6930p安装Android-x86避坑日记

一、序言 农历癸卯年前大扫除,翻出老机hp6930p,闲来无事,便安装Android-x86玩玩,期间多次入坑,随手记之以避坑。 笔记本配置:T9600,4G内存,120G固态160G机械硬盘 二、Android-x86系统简介 官…

第二节:Vben Admin 登录逻辑梳理和对接后端准备

系列文章目录 上一节:第一节:Vben Admin介绍和初次运行 文章目录 系列文章目录前言项目路径的概述一、登录逻辑梳理loginApi接口查看Mock 二、后端程序对接准备关闭Mock 总结 前言 第一节,我们已经配置了前端环境,运行起来了我们…

vue基础操作(vue基础)

想到多少写多少把&#xff0c;其他的想起来了在写。也写了一些css的 input框的双向数据绑定 html <input value"123456" type"text" v-model"account" input"accou" class"bottom-line bottom" placeholder"请输入…

数据库安全性与完整性设计

文章标签集合[数据库安全,数据敏感,通信安全,MD5,盐加密] 1 系统设计 1.1设计目标 &#xff08;1&#xff09;确定系统中需要保护的敏感数据和通信内容&#xff1b; &#xff08;2&#xff09;设计合适的签名、加密和解密算法&#xff1b; &#xff08;3&#xff09;实现…

遗传算法(Genetic Algorithm,GA)求解不闭合多旅行商问题(提供MATLAB代码)

一、遗传算法&#xff08;GA&#xff09;介绍 遗传算法&#xff08;Genetic Algorithm&#xff0c;GA&#xff09;是一种模拟自然界生物进化过程的优化算法。它通过模拟生物的遗传、变异和选择等机制&#xff0c;来搜索问题的最优解。 遗传算法的基本思想是通过对候选解进行编…

matlab绘制雷达图和二维FFT变换图

1、内容简介 略 49-可以交流、咨询、答疑 matlab绘制雷达图和二维FFT变换图 NMO组及NORMAL组 RNFL层、GCL层、IPL层、GCC层、ORL层做雷达图&#xff08;共10张&#xff09; 2、内容说明 略 NMO组及NORMAL组 RNFL层、GCL层、IPL层、GCC层、ORL层请分别做雷达图&#xff08…

CAS5.3使用JPA实现动态注册服务

cas同时支持cas协议和OAuth2协议,官方默认是通过扫描json文件的形式注册客户端服务,但是此种方式需要重启服务才能生效,此次我们将使用JPA来完美实现动态注册服务,如果不知道cas如何部署,可以擦看之前的文章 cas-client基于CAS协议客户端搭建-CSDN博客 cas-server5.3自定义密…

Vue 卸载eslint

卸载依赖 npm uninstall eslint --save 然后 进入package.json中&#xff0c;删除残留信息。 否则在执行卸载后&#xff0c;运行会报错。 之后再起项目。

HTTPS对HTTP的加密过程

1、HTTPS是在HTTP的基础上&#xff0c;引入了一个加密层&#xff08;SSL&#xff09;&#xff0c;对数据进行保护&#xff0c;HTTP 是明文传输的&#xff08;不安全&#xff0c;很可能会被运营商通过referer劫持&#xff0c;或者黑客通过修改链接来窃数据&#xff09; 2、加密…

Typora结合PicGo + 使用Github搭建个人免费图床

文章目录 一、国内图床比较二、使用Github搭建图床三、PicGo整合Github图床1、下载并安装PicGo2、设置图床3、整合jsDelivr具体配置介绍 4、测试5、附录 四、Typora整合PicGo实现自动上传 每次写博客时&#xff0c;我都会习惯在Typora写好&#xff0c;然后再复制粘贴到对应的网…