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

·

目录

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

一、说明

  日常物体会产生人眼无法察觉的微妙运动。在视频中,这些运动的幅度小于一个像素,但是,我们可以使用基于相位的视频放大来揭示这些不可见的运动,在这篇文章中,我们将用 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.htmlksize = 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 indicesidx1 = levelidx2 = level + batch_size# get current filter batchfilter_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 framefor 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 spacephase_deltas = torch.fft.ifft(transfer_function \* torch.fft.fft(phase_deltas, dim=1),  dim=1).real## Apply Motion Magnificationsfor 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 Blurringif 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 Deltasdelta /= weight## Modify phase variationmodifed_phase = delta * phase_mag## Attenuate other frequencies by scaling magnitude by reference phaseif 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 levelsrecon_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 structurecurr_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 tensorcurr_pyr = build_level_batch(video_dft[vid_idx, :, :].unsqueeze(0), filter_batch)delta = phase_deltas[:, vid_idx, :, :]## Perform Amplitude Weighted Blurringif 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 Deltasdelta /= 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 Componentscurr_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 Componentscurr_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.hqwc.cn/news/499636.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2月28日做题总结(C/C++真题)

今天是2月28日,做题第三天。道阻且长,行则将至;行而不辍,则未来可期! 第一题 static char a[2]{1,2,3};说法是否正确? A---正确 B---错误 正确答案:B 解析:数组定义时&#xf…

LeetCode——栈和队列(Java)

栈和队列 简介[简单] 232. 用栈实现队列[简单] 225. 用队列实现栈[简单] 20. 有效的括号[简单] 1047. 删除字符串中的所有相邻重复项[中等] 150. 逆波兰表达式求值[困难] 239. 滑动窗口最大值[中等] 347. 前 K 个高频元素 简介 记录一下自己刷题的历程以及代码。写题过程中参考…

通过多进程并发方式(fork)实现服务器

以下内容为视频学习记录。 1、父进程accept后返回的文件描述符为cfd以及用于创建连接的lfd; 调用fork()创建子进程后,子进程继承cfd,lfd,通过该cfd与连接过来的客户端通信,lfd对子进程来说没用,可以直接close(lfd); 对于父进程来说&#x…

【MySQL | 第一篇】undo log、redo log、bin log三者之间的区分?

undo log、redo log、bin log三者之间的区分? 从 产生的时间点、日志内容、用途 三方面展开论述即可 1.undo log——撤销日志 时间点:事务开始之前产生,根据当前版本的数据生成一个undo log,也保存在事务开始之前 作用&#xf…

LeetCode:2867. 统计树中的合法路径数目(筛质数+ DFS Java)

目录 2867. 统计树中的合法路径数目 题目描述: 实现代码与思路: 筛质数 DFS 原理思路: 2867. 统计树中的合法路径数目 题目描述: 给你一棵 n 个节点的无向树,节点编号为 1 到 n 。给你一个整数 n 和一个长度为 …

Gophish+EwoMail 自建钓鱼服务器

GophishEwoMail 自建钓鱼服务器 文章目录 GophishEwoMail 自建钓鱼服务器1.前提准备2.搭建EwoMail邮件服务器1)Centos7 防火墙操作2)设置主机名3)host配置4)安装EwoMail5)获取DKIM6)端口服务介绍7&#xff…

1.2 debug的六种指令的使用,四个通用寄存器

汇编语言 首先进入环境 mount c d:masm //把c挂载在d盘中的masm当中 c: //进入c,进入到编译环境 dir //查看文件,可有可无Debug是DOS、Windows都提供的实模式(8086 方式)程序的调试工具。使用它可以查看CPU各种寄存器中的内容…

如何提取测试点

如何提取测试点 首先会想到从需求文档中提取测试点,每一次迭代之后,都会有需求,需求经理评审之后,我们要基于需求去写测试计划,包括梳理出来的测试点,梳理完测试点之后,编写对应的测试用例&…

Linux添加用户分组练习

一、复制/etc/skel目录为/home/tuser1(/home/tuser1及其内部文件的属组和其它用户均没有任何访问权限)。 cp -a /etc/skel /home/tuser1 chown -R tuser1:tuser1 /home/tuser1 chmod -R 700 /home/tuser1 二、编辑/etc/group文件,添加组h…

租赁小程序|租赁系统|租赁软件开发带来高效运营

随着社会的不断发展和科技的不断进步,越来越多的企业开始关注设备租赁业务。设备租赁作为一种短期使用设备的方式,为企业提供了灵活和成本节约的优势。针对设备租赁业务的管理和提升企业竞争力的需求,很多企业选择定制开发设备租赁系统。本文…

抖音作品评论id提取工具|视频内容提取软件

抖音视频提取便捷高效,抖音作品评论id提取工具助您快速获取数据 针对抖音作品评论id提取的需求,我们推出了一款功能强大的工具,旨在帮助用户快速提取抖音作品的评论id。无论您是进行数据分析、社交媒体研究还是其他用途,我们的工…

Linux------进程地址空间

目录 一、进程地址空间 二、地址空间本质 三、什么是区域划分 四、为什么要有地址空间 1.让进程以统一的视角看到内存 2.进程访问内存的安全检查 3.将进程管理与内存管理进行解耦 一、进程地址空间 在我们学习C/C的时候,一定经常听到数据存放在堆区、栈区、…

Linux中如何在创建子线程的时候设置为分离属性

#include<stdio.h> #include<stdlib.h> #include<string.h> #include<sys/types.h> #include<unistd.h> #include <pthread.h> void *mythread(void *arg) {printf("id[%ld]\n",pthread_self()); } int main() { //定义pthread_…

【InternLM 实战营笔记】基于 InternLM 和 LangChain 搭建你的知识库

准备环境 bash /root/share/install_conda_env_internlm_base.sh InternLM升级PIP # 升级pip python -m pip install --upgrade pippip install modelscope1.9.5 pip install transformers4.35.2 pip install streamlit1.24.0 pip install sentencepiece0.1.99 pip install a…

定时任务框架

定时任务的框架有哪些 ● Timer&#xff0c;JDK自带的&#xff0c;比较简单&#xff0c;使用的时候&#xff0c;定义一个TimerTask&#xff0c;实现run方法&#xff0c;然后定义一个Timer类&#xff0c;调用timer.schedule(timerTask,1000,3000); ○ 缺点&#xff1a;单线程、…

发工资(个人学习笔记黑马学习)

某公司&#xff0c;账户余额有1W元&#xff0c;给20名员工发工资 员工编号从1到20&#xff0c;从编号1开始&#xff0c;依次领取工资&#xff0c;每人可领取1000元领工资时&#xff0c;财务判断员工的绩效分(1-10)(随机生成)&#xff0c;如果低于5&#xff0c;不发工资&#xf…

腾讯云优惠券领取的三个渠道,先领券再下单!

腾讯云代金券领取渠道有哪些&#xff1f;腾讯云官网可以领取、官方媒体账号可以领取代金券、完成任务可以领取代金券&#xff0c;大家也可以在腾讯云百科蹲守代金券&#xff0c;因为腾讯云代金券领取渠道比较分散&#xff0c;腾讯云百科txybk.com专注汇总优惠代金券领取页面&am…

计算机设计大赛 深度学习卷积神经网络垃圾分类系统 - 深度学习 神经网络 图像识别 垃圾分类 算法 小程序

文章目录 0 简介1 背景意义2 数据集3 数据探索4 数据增广(数据集补充)5 垃圾图像分类5.1 迁移学习5.1.1 什么是迁移学习&#xff1f;5.1.2 为什么要迁移学习&#xff1f; 5.2 模型选择5.3 训练环境5.3.1 硬件配置5.3.2 软件配置 5.4 训练过程5.5 模型分类效果(PC端) 6 构建垃圾…

Python算法100例-2.8 猜牌术

完整源代码项目地址&#xff0c;关注博主私信源代码后可获取 1.问题描述2.问题分析3.算法设计4.确定程序框架5.完整的程序6.运行结果 1&#xff0e;问题描述 魔术师利用一副牌中的13张黑桃&#xff0c;预先将它们排好后叠在一起&#xff0c;并使牌面朝下。然后他对观众说&am…

04 Opencv图像操作

文章目录 读写像素修改像素值Vec3b与Vec3F灰度图像增强获取图像通道bitwise_not 算子对图像非操作 读写像素 读一个GRAY像素点的像素值&#xff08;CV_8UC1&#xff09; Scalar intensity img.at(y, x); 或者 Scalar intensity img.at(Point(x, y)); 读一个RGB像素点的像素值…