news 2026/10/2 2:34:26

手语识别系统全流程避坑:从zip伪加密到关键点提取与CNN+LSTM部署

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手语识别系统全流程避坑:从zip伪加密到关键点提取与CNN+LSTM部署

简介:手语识别是深度学习在视频理解中的典型应用。这套基于深度学习的手语识别系统,以Python编写,从数据预处理、模型构建到训练测试均提供完整代码,适合毕业设计、期末大作业及人工智能实践参考。系统核心网络融合多层卷积、池化与全连接结构,并引入序列学习机制处理动态手语时序问题。整体包含79个文件,其中35个py源码承担模型定义、序列学习、视频API与工具函数等核心逻辑;24个pyc为编译缓存;8个stm与7个npy分别提供数据标注与特征信息;另有yaml配置、shell脚本及说明文档。压缩包仅1.34MB,轻量成套,目录结构清晰。目前已有52人学习下载。借助该项目,可研读模型设计、数据预处理、训练评估等完整流程,并复用脚本快速构建自己的手语识别实验或答辩演示;模块化设计也便于按需替换数据集或调整网络参数。

1. 为什么一份打包成 zip 的手语识别系统,最容易栽在第一步

拿到「基于深度学习的手语识别系统.zip」这个名字,大多数人第一反应是解压、找代码、跑训练。但以我做过几个手势识别项目的经验,这类打包分发的项目最危险的反而不是模型精度,而是压缩包本身——伪加密、EOCD 记录损坏、分卷缺失,任何一个都能让几 GB 的数据集看起来像一堆乱码。zip 在这里不只是分发格式,它本身就是这套系统能不能落地的第一道关卡。

手语识别的技术栈并不冷门:先采集手部关键点坐标或原始图像帧,再用深度学习模型完成时序分类。真正劝退新手的不是算法选型,而是从压缩包到可用训练集之间的那段路——标注格式不统一、帧率不足、类别不均衡,每一个都足以让训练好的模型在实测时翻车。这篇笔记我按自己做过的手语识别方案来拆:从 zip 包检查开始,到数据预处理、模型选型、训练调参,最后收在验证与推理加速上。适合正在做相关毕设、或者想把手势识别接进实际产品的人照着复现。

2. 解压不是双击就完事:zip 包检查、伪加密与目录结构预判

手语识别项目的 zip 包通常不只是代码,里面还塞了数据集、预训练权重和标注文件。直接用 GUI 工具双击解压,遇到坏包或伪加密时往往只弹一个“密码错误”或“文件损坏”就中止了。我一般先写一段脚本把包体检一遍,再决定怎么解。

2.1 用 Python 检查 zip 的完整性与伪加密

zip 伪加密是这类项目包里很常见的情况:有人用工具把「加密标志位」改了但没真正加密文件,或者反过来,文件确实加密了但标志位没写对。解压工具看到标志位为 1 就要求输密码,输什么都报错。判断方法很简单——读取中央目录里每个条目的通用标志位,再做一次 CRC32 校验。

import zipfile import struct ZIP_PATH = "hand_sign_recognition.zip" with zipfile.ZipFile(ZIP_PATH) as zf: for info in zf.infolist(): # 通用位标志的第 0 位表示是否加密 flag = info.flag_bits is_encrypted = bool(flag & 0x1) # 伪加密:标志位显示加密,但文件名没有对应的加密头 if is_encrypted: with zf.open(info) as f: head = f.read(4) # 真加密的文件头应包含 0x5453 魔数(PKCS#7 的 Salt/校验头) if len(head) < 4 or head[0:2] != b"PK": print(f"[伪加密] {info.filename}") # CRC32 校验,能跑通说明文件内容没坏 try: zf.getinfo(info.filename).CRC except Exception as e: print(f"[损坏] {info.filename}: {e}")

这段代码的逻辑是逐条读取 zip 的中央目录条目,先看 flag_bits 的加密位,再看文件内容开头是否真的有加密结构。真加密文件的内容开头会有额外的头部(常见的是基于密码的派生数据),伪加密则没有。CRC32 校验是最后一道保险,它不能修复损坏,但能准确告诉你是哪几个文件出了问题,避免解压到一半才报错。

参数上需要注意:flag_bits是 int 类型,按位与操作不能省。有些项目包用 AES-256 加密(WinZip 格式),zipfile读不了这种,得换pyzipper。如果检测出伪加密,直接用pyzipper解压通常能绕过去,因为它不检查标志位,只用密码尝试解密,密码为空时能直接解开伪加密文件。

2.2 判断数据集规模与预训练权重是否完整

解压前还有个大事:确认包里的预训练权重和数据集是否被裁剪过。很多分发的 zip 为了体积会删掉 .pth、.h5 这类权重文件,或者只给一部分样本。我习惯解压前先打印文件清单和每个目录的大小,一眼就能看出缺什么。

# 列出 zip 内所有文件及大小,按目录聚合 python -c " import zipfile, collections z = zipfile.ZipFile('hand_sign_recognition.zip') agg = collections.defaultdict(int) for i in z.infolist(): top = i.filename.split('/')[0] agg[top] += i.file_size for k, v in sorted(agg.items(), key=lambda x: -x[1]): print(f'{k:30s} {v/1024/1024:8.2f} MB') "

我一般要求weights/目录下至少有一个模型权重文件,体积在 50 MB 以上(手语模型以 CNN+LSTM 或 3D-CNN 为主,太小说明是随机初始化或只训了几轮);dataset/目录里至少要有 train 和 val 两个子目录。如果这两样缺了,这项目就只能用来读代码,不能直接复现,做毕设的话要提前有心理准备。

2.3 解压后先修三个环境级问题

解压完成后,最常见的问题有三个:路径含中文导致 OpenCV 读不到文件、数据集编码不是 UTF-8、Python 版本与依赖不匹配。前两个在 Windows 上尤其常见。我现在的习惯是一解压完就先做两件事:把整个项目移动到纯英文路径下,再把数据集里的 JSON/CSV 标注文件统一转成 UTF-8 编码。

import os, json, chardet ANNOT_DIR = "dataset/annotations" for name in os.listdir(ANNOT_DIR): path = os.path.join(ANNOT_DIR, name) with open(path, "rb") as f: raw = f.read() encoding = chardet.detect(raw)["encoding"] if encoding and encoding.lower() != "utf-8": with open(path, "r", encoding=encoding, errors="ignore") as f: text = f.read() with open(path, "w", encoding="utf-8") as f: f.write(text) print(f"converted: {name} ({encoding} -> utf-8)")

这段脚本用chardet检测编码,把非 UTF-8 的标注文件转码。手语识别项目里标注文件常常来自不同采集工具,GBK、Latin-1 混着来,不统一的话后续在 DataFrame 里做 join 或 groupby 时会出现乱码键,浪费大量排查时间。

3. 手语数据从哪里来:关键点坐标还是原始图像帧

预处理是整个手语识别系统里最容易被低估的环节。模型选得再漂亮,输入数据如果是抖动、缺帧、背景杂乱的视频帧,精度照样上不去。手语识别的数据形态分成两派:以 MediaPipe 或 OpenPose 提取的手部关键点坐标,以及原始 RGB 视频帧。两者我都跑过,结论是:关键点坐标适合快速出效果、资源占用低;原始帧适合复杂背景和精细手型,但训练成本和推理延迟都高一个量级。

3.1 关键点方案的坐标格式与归一化坑

MediaPipe Hands 是当前做手语识别最常见的前处理方案。它输出 21 个手部关键点,每个点包含 x、y、z 三个坐标。关键点方案的第一个坑就是归一化:x、y 已经按图像宽高归一化到 [0,1],但 z 是相对于手腕的深度估计,单位不是像素,范围也跟 x/y 不一致。直接拼成 63 维向量丢进网络,z 维度可能会主导损失函数。

import mediapipe as mp import cv2 import numpy as np mp_hands = mp.solutions.hands hands = mp_hands.Hands(static_image_mode=False, max_num_hands=2, min_detection_confidence=0.5) def extract_landmarks(video_path): frames = [] cap = cv2.VideoCapture(video_path) while cap.isOpened(): ret, frame = cap.read() if not ret: break rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results = hands.process(rgb) if results.multi_hand_landmarks: # 每只手取前21个关键点的 x, y;z 轴只保留相对变化 hand_pts = [] for lm in results.multi_hand_landmarks[0].landmark: hand_pts.extend([lm.x, lm.y, lm.z * 0.1]) # z 缩到 0.1 倍 frames.append(np.array(hand_pts, dtype=np.float32)) cap.release() return np.stack(frames) # (T, 63)

注意这里有两个参数很关键:max_num_hands=2是因为很多手语词汇需要双手配合,但两只手各自独立提取关键点后,需要手动匹配左右手(MediaPipe 不保证多手顺序的一致性,这是后话);min_detection_confidence=0.5在背景复杂时建议提到 0.7,否则频繁丢帧导致时序不连续。

z 轴乘以 0.1 是我在多个项目里试出来的经验值,不是标准做法。如果你跑出来的模型对 z 轴特别敏感(表现为验证集精度高、实测抖动大),正确的做法是改用关键点之间的角度特征和速度特征,而不是原始坐标。手语识别本质上更依赖运动轨迹,而不是某一帧的静态手型。

3.2 如果只有图像帧:光流法与时序采样策略

有些数据集不提供关键点,只有连续帧图片。这种时候有两种做法:一是自己用 MediaPipe 提取关键点后按上面流程走;二是直接用 3D-CNN 吃 RGB 帧序列。第二种做法对视频帧的时序采样非常敏感,不能简单地等间隔抽帧。

def sample_frames(video_path, num_frames=16): cap = cv2.VideoCapture(video_path) total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) # 均匀采样,但从第 10 帧开始,跳过视频前几帧的过渡 indices = np.linspace(10, total - 5, num_frames, dtype=int) frames = [] for idx in indices: cap.set(cv2.CAP_PROP_POS_FRAMES, idx) ret, frame = cap.read() if ret: frame = cv2.resize(frame, (224, 224)) frames.append(frame) cap.release() return np.stack(frames)

这里用linspace(10, total-5, num_frames)而不是range(0, total, step)的原因是:手语动作的一个完整词可能只占视频中段,开头和结尾经常是空档,直接把首尾帧丢掉能显著减少无效计算。num_frames=16是 I3D、SlowFast 等视频模型的常见输入长度,再长收益下降明显,再短则动作语义不完整。

3.3 标注格式不统一:JSON、CSV、文件夹名的相互转换

手语识别数据集的标注方式五花八门。有的按「每类一个文件夹」组织,有的给 CSV 标了 start_frame 和 end_frame,有的直接给 JSON 标注关键点序列。模型训练前必须统一成分类标签和序列的对应关系。我一般把一切先转成 CSV 标准格式。

import pandas as pd import os def folder_to_csv(root): rows = [] for label in sorted(os.listdir(root)): label_dir = os.path.join(root, label) if not os.path.isdir(label_dir): continue for f in sorted(os.listdir(label_dir)): if f.endswith((".mp4", ".avi", ".mov")): rows.append({"video_path": os.path.join(label_dir, f), "label": label}) return pd.DataFrame(rows) df = folder_to_csv("dataset/train") df.to_csv("dataset/train.csv", index=False) print(df["label"].value_counts())

这段代码最后一行value_counts()非常关键。手语数据集最大的问题就是类别不均衡——常见词「谢谢」「你好」的样本可能是生僻词的十倍。如果不做平衡就直接训练,模型会对高频词过拟合,对低频词完全失灵。处理方式要么在DataLoader里按权重采样,要么对低频词做数据增强(翻转、加噪、随机裁剪)。

4. 模型选型与训练:CNN+LSTM、3D-CNN 还是 Transformer

手语识别的时序模型框架不多,选型的核心矛盾是:关键点坐标序列适合用轻量时序模型,原始帧则需要空间编码与时序建模解耦。我把三个主流方案的适用场景和参数列出来,再展开训练细节。

方案输入形态模型结构适合场景单卡训练耗时(约)
CNN + LSTM关键点序列 (T, 63)1D-CNN 特征提取 + LSTM 分类快速原型、资源受限1-2 小时
3D-CNNRGB 帧序列 (16, 3, 224, 224)ResNet3D / I3D复杂背景、高精度6-12 小时
双流 + Transformer关键点 + 光流TCN / Transformer Encoder长时序动作、学术研究3-6 小时

对绝大多数做毕设或落地项目的读者,我推荐第一条路:MediaPipe 提取关键点 + 1D-CNN + LSTM。原因是它把空间理解交给了 MediaPipe(这种方案最接近当前工业界的做法),模型只学时间维度的动作变化,数据量需求小、收敛快、好解释。

4.1 最小可跑的 CNN + LSTM 训练代码

import torch import torch.nn as nn class HandSignModel(nn.Module): def __init__(self, num_classes=20, input_dim=63, hidden_size=128): super().__init__() # 1D-CNN 负责局部时序特征,感受野小,降低关键点抖动的干扰 self.conv1 = nn.Conv1d(input_dim, 64, kernel_size=3, padding=1) self.conv2 = nn.Conv1d(64, 128, kernel_size=3, padding=1) self.relu = nn.ReLU() self.pool = nn.MaxPool1d(2) # LSTM 负责长程依赖,手语词的时长为 1~3 秒,序列长度 30~90 self.lstm = nn.LSTM(128, hidden_size, batch_first=True, bidirectional=True) self.dropout = nn.Dropout(0.3) self.fc = nn.Linear(hidden_size * 2, num_classes) def forward(self, x): # 输入 x: (B, T, 63),需要转成 (B, 63, T) 给 Conv1d x = x.transpose(1, 2) x = self.relu(self.conv1(x)) x = self.relu(self.conv2(x)) x = self.pool(x) x = x.transpose(1, 2) # 转回 (B, T', 128) x, _ = self.lstm(x) x = self.dropout(x[:, -1, :]) # 取最后一步输出 return self.fc(x)

这段代码里有两个最影响精度的参数:bidirectional=True和x[:, -1, :]。双向 LSTM 在手语识别里基本是标配,因为一个手势词的语义可能由前后的微动作共同决定;但取最后一步输出时,双向结构意味着你只用了反向的末步,真正有效的是把最后一步输出和第一步输出拼接。一个更稳的做法是取所有时间步的均值池化(x.mean(dim=1)),能显著降低对序列对齐的敏感性。

4.2 训练参数设置的三个必调参数

学习率、批量大小和序列长度是手语识别训练里最值得花时间的三个参数。我的经验值如下:

  • learning_rate = 1e-3:Adam 优化器下这个值安全;但如果用了预训练的 CNN 骨干(比如 ResNet 前几层),骨干层学习率建议设1e-4,新加的全连接层设1e-3。手语数据量通常不大,骨干层学太快容易灾难性遗忘。
  • batch_size = 32:关键点序列的样本很小,32 是起步值。显存不够时先降到 16,不要动序列长度。
  • sequence_length = 45:手语词的平均时长在 1.5 秒左右,如果按 30 fps 采样就是 45 帧。短于 30 帧会截断动作的起势和收势,长于 60 帧则引入大量无效帧。

4.3 时间维度对齐:为什么你的 loss 下降但验证集精度不动

这是手语识别训练里最常见的翻车现象。原因有两个:一是训练集和验证集的视频帧率不同(摄像头采集可能是 25 fps,数据集可能是 30 fps),导致同样的动作在两种数据里长度不一样;二是关键点缺失时用前向填充(ffill)补的帧在训练集里被当成了真实数据。

def pad_or_trim(seq, target_len=45): if len(seq) < target_len: # 只在尾部重复最后一帧,不能用 0 填充,否则 LSTM 学到的全是"手消失" pad = np.repeat(seq[-1:], target_len - len(seq), axis=0) return np.concatenate([seq, pad], axis=0) else: # 从中间段截取,保留起势和收势 start = (len(seq) - target_len) // 2 return seq[start:start + target_len]

特别注意:要用重复最后一帧的方式补短序列,而不是用 0 填充。用 0 填充等于告诉模型「手消失了」,LSTM 会学着在序列末尾输出一个默认分类,短序列样本多的类别会被系统性误判。

5. 避坑指南:手语识别项目从解压到部署的 5 个典型翻车现场

以下五条都是我在跑手语识别项目中实际踩过的坑,按出现频率排序。每条都按「现象 → 原因 → 解决」写,方便你对照排查。

5.1 zip 伪加密导致数据集解不出来

现象:解压时提示输入密码,输入空密码或常见密码都报错。原因:压缩包制作者用第三方工具误改了加密标志位,文件本身没加密或密码已丢失。解决:用第 2.1 节的脚本检测标志位,确认是伪加密后用 pyzipper 直接解压;如果确认真加密且密码未知,别浪费时间,联系分发者或换数据源。

5.2 MediaPipe 对深色皮肤和暗光环境检测率骤降

现象:训练集精度高,但用摄像头实测时关键点频繁丢失,模型输出乱跳。原因:MediaPipe Hands 的训练数据以亮肤色和均匀光照为主,暗光、逆光条件下手部检测置信度低于阈值被丢弃。解决:实测时把min_detection_confidence降到 0.3,同时预处理加一条自适应直方图均衡化;更彻底的方案是采集一批暗光样本做微调.

5.3 类别不均衡造成的精度虚高

现象:验证集 accuracy 达到 95%,但分类报告显示生僻词全部预测错。原因:数据集中「你好」「谢谢」这类高频词占了 80% 以上,模型学会了偷懒。解决:在训练时用WeightedRandomSampler按类别权重采样;验证时同时看 macro-F1 而不是只看 accuracy。

from torch.utils.data import WeightedRandomSampler labels = df["label"].values class_counts = np.bincount(labels) weights = 1.0 / class_counts[labels] sampler = WeightedRandomSampler(weights, num_samples=len(labels), replacement=True) # 在 DataLoader 里传入 sampler,batch_size 不变 dataloader = DataLoader(dataset, batch_size=32, sampler=sampler)

这个采样器的逻辑是每个样本的采样概率与其类别样本数成反比,低频词被抽到的概率上来了,模型才被迫去学它的特征。注意replacement=True是必须的,否则低频词可能根本抽不满一个 epoch。

5.4 训练和推理时的预处理不一致

现象:训练时 loss 正常下降,推理时同一个视频识别结果完全不对。原因:训练时做了随机裁剪、翻转、时间抖动等增强,但推理时忘了关掉;或者训练时输入是浮点数归一化到 [0,1],推理时直接传了 0-255 的整数。解决:把预处理逻辑封装成同一个函数,训练和推理共用一份代码;增强操作只在训练分支执行,推理分支只做 resize 和 normalize。

5.5 tensorboard 里 loss 在下降但验证集 loss 在上升

现象:训练到第 20 个 epoch 左右开始过拟合,但很多人误以为是学习率问题,继续降 lr 反而加剧。原因:手语数据集通常很小(几千个样本),模型容量大时 20 轮左右就过拟合,比图像分类来得快。解决:不要只用 loss 判断,每个 epoch 记录 macro-F1;过拟合出现时先加 dropout 到 0.5,再做时间维度的随机裁剪增强;若还不行再降模型复杂度(隐藏层 128→64)。

6. 验证与推理加速:从离线精度到实时视频流的最后一公里

模型训练完只是开始。手语识别的落地场景几乎都是实时摄像头或视频流推理,这时候推理延迟比离线精度更重要。我的做法分三步:先用标准指标验证模型真实水平,再做模型压缩和推理加速,最后在实时流上做帧级滑窗。

6.1 验证指标:别只看 accuracy,重点看 macro-F1

手语识别任务中类别不均衡是常态,accuracy 会被高频词主导。正确做法是输出每个类别的 precision、recall、F1,并计算 macro-F1(所有类别的 F1 取平均)。此外加一个「时序平滑度」指标:连续帧的预测标签不能频繁跳变,否则用户看到的就是识别结果在乱闪。

from sklearn.metrics import classification_report, f1_score preds, labels = [], [] for batch in val_loader: x, y = batch with torch.no_grad(): out = model(x) preds.extend(torch.argmax(out, dim=1).cpu().numpy()) labels.extend(y.numpy()) report = classification_report(labels, preds, zero_division=0) print(report) macro_f1 = f1_score(labels, preds, average="macro", zero_division=0) print(f"macro-F1: {macro_f1:.4f}")

注意zero_division=0参数必须加,否则某些类别在验证集里一个都没预测对时,sklearn 会抛警告并返回 nan。我的标准是 macro-F1 低于 0.8 就不建议上实时系统,因为实际环境比验证集更恶劣。

6.2 ONNX 导出与推理延迟优化

手语识别要跑实时,PyTorch 的动态图推理太慢。我一般导出 ONNX 并用 ONNX Runtime 推理。关键点方案的模型很小(几十 MB),导出后 CPU 单帧推理能压到 5 ms 以下。如果是 3D-CNN 方案,则建议用 TensorRT 导出 FP16,否则很难达到实时。

import torch.onnx import onnxruntime as ort # 导出 ONNX,固定序列长度可以大幅提升推理速度 dummy_input = torch.randn(1, 45, 63) torch.onnx.export( model, dummy_input, "hand_sign_model.onnx", input_names=["keypoints"], output_names=["logits"], dynamic_axes={"keypoints": {0: "batch"}} ) # ONNX Runtime 推理 sess = ort.InferenceSession("hand_sign_model.onnx") result = sess.run(None, {"keypoints": seq.reshape(1, 45, 63).astype("float32")})

参数说明:dynamic_axes只放开 batch 维,序列长度固定为 45,这样 ONNX 可以做维度专一化优化。不要动态化时间维,否则每帧序列长度不一致时 ONNX Runtime 会反复做 shape 推导,延迟反而更高。

6.3 实时推理的滑窗设计

最后是实时手语识别的收尾技巧。摄像头输入是连续视频流,不能每一帧都跑一次完整序列。常见做法是滑窗:每 3 帧推进一步,取最近 45 帧的关键点序列做一次预测,并对最近 5 次预测结果做多数投票。这样可以平滑跳变,又不至于让延迟累积。

我自己的教训是:一开始图省事每帧都推理,结果识别结果乱跳不说,CPU 占用还拉满;后来改成上述滑窗,延迟从 90 ms 降到 30 ms,稳定性也好了很多。做手语识别这类时序任务,永远先想清楚「什么时候触发一次推理」,再想「用模型怎么推理」。希望这篇笔记能帮你少走这几步弯路,把精力花在真正决定识别效果的数据和特征上。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 2:33:27

揭秘当下知名的SEO优化渠道,你知道几个?

痛点深度剖析我们团队在实践中发现&#xff0c;当下SEO优化领域存在诸多痛点。在流量获取方面&#xff0c;SEO见效慢&#xff0c;很多企业做了半年优化&#xff0c;关键词排名却毫无变化&#xff1b;SEM则烧钱快&#xff0c;谷歌广告点击成本不断攀升&#xff0c;投资回报率难以…

作者头像 李华
网站建设 2026/10/2 2:33:05

美国载人飞船Crew-13明晚发射!

美国载人飞船Crew-13明晚发射&#xff01;计划7小时50分对接&#xff0c;冲刺空间站最快纪录 摘要&#xff1a;NASA 的 Crew-13 载人飞船计划于北京时间 10 月 1 日 23:10 发射&#xff0c;目标在 7 小时 50 分内与国际空间站完成对接&#xff0c;有望刷新美国载人飞船最快对接…

作者头像 李华
网站建设 2026/10/2 2:32:42

C++ Qt6 雷达模拟器实战(四):使用 QPainter 绘制 PPI 雷达界面

前面三篇分别介绍了 RadarSimulator 的项目架构、TCP 通信&#xff0c;以及雷达目标和坐标模型。 到了这一篇&#xff0c;前面的数据终于要真正“画”到屏幕上了。 整个显示链路可以概括为&#xff1a; RadarTarget / RadarTrack↓ Range / Azimuth↓ 雷达坐标↓ Qt 屏幕坐标↓…

作者头像 李华
网站建设 2026/10/2 2:32:42

DeepSeek LeetCode 139.单词拆分 Rust实现

LeetCode 139「单词拆分」的 Rust 实现。提供动态规划&#xff08;推荐&#xff09;和记忆化 DFS 两种写法。思路&#xff1a;动态规划 定义 dp[i] 表示字符串 s 的前 i 个字符&#xff08;即 s[0…i]&#xff09;能否被拆分成字典中的单词。初始状态&#xff1a;dp[0] true…

作者头像 李华
网站建设 2026/10/2 2:30:35

从NAS自带笔记迁移到Markdown:踩坑记录与完整实操指南

用 NAS 的人&#xff0c;十有八九都动过“自带笔记应用”的念头。群晖叫 Note Station&#xff0c;威联通叫 Notes Station&#xff0c;这两年流行的绿联、飞牛也都有类似套件。我也是从 Note Station 用起的&#xff0c;身边还有朋友为了笔记功能专门选了某个品牌的 NAS。但用…

作者头像 李华
网站建设 2026/10/2 2:29:01

Linux驱动阻塞与非阻塞访问的理解

一、阻塞I/O应用程序的阻塞访问方式int fd; int data 0; fd open("/dev/xxx_dev", O_RDWR); ret read(fd, &data, sizeof(data));阻塞操作是指在执行设备操作时&#xff0c;若不能获得资源&#xff0c;则挂起进程直到满足可操作的条件后再进行操作。被挂起的进…

作者头像 李华