<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/scripts/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:h="http://www.w3.org/TR/html4/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"><channel><title>JiaXin‘s Blog</title><description>分享一些技术思考和记录成长的点滴</description><link>https://jiaxin404.top</link><language>zh-CN</language><copyright>Copyright © 2026 嘉心糖</copyright><managingEditor>嘉心糖 (https://jiaxin404.top)</managingEditor><webMaster>https://jiaxin404.top</webMaster><image><url>https://jiaxin404.top/images/avatar.jpg</url><title>JiaXin‘s Blog</title><link>https://jiaxin404.top</link></image><dc:creator>嘉心糖</dc:creator><itunes:author>嘉心糖</itunes:author><itunes:summary>分享一些技术思考和记录成长的点滴</itunes:summary><item><title>记录MaixCAM2将YOLO11模型转换</title><link>https://jiaxin404.top/tech/maix_yolo</link><guid isPermaLink="true">https://jiaxin404.top/tech/maix_yolo</guid><description>本文参考官方教程,导出模型</description><pubDate>Wed, 15 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;前几天买了一个maixcan2，作为今年电赛的使用，本文记录使用maixcam2跑yolo的过程，仅供参考，以&lt;a href=&quot;https://wiki.sipeed.com/maixpy/doc/zh/ai_model_converter/maixcam2.html&quot;&gt;官方教程&lt;/a&gt;为主。&lt;/p&gt;
&lt;p&gt;有开源在maixhub上：&lt;a href=&quot;https://maixhub.com/share/125&quot;&gt;具体可以看&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;一、烧录镜像&lt;/h2&gt;
&lt;p&gt;这个下面是官方的要求，可见yolo模型和镜像的版本相关，具体烧录可以看这里&lt;a href=&quot;https://wiki.sipeed.com/maixpy/doc/zh/basic/upgrade.html&quot;&gt;MaixCAM MaixPy 升级和烧录系统&lt;/a&gt;,我这次烧录的是4.12.5。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MaixPy 默认提供了 YOLOv5 , YOLOv8 , YOLO11 , YOLO26 模型，可以直接使用：
YOLOv8 需要 MaixPy &gt;= 4.3.0。
YOLO11 需要 MaixPy &gt;= 4.7.0。
YOLO26 需要 MaixPy &gt;= 4.12.5。
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;步骤1：ONNX 导出&lt;/h2&gt;
&lt;p&gt;电脑上训练的模型后的模型是best.pt不能直接给 MaixCAM2 使用，硬件性能有限，需要将模型进行INT8量化以减少计算量，并且转换为 MaixCAM2 支持的模型格式MUD。&lt;/p&gt;
&lt;p&gt;MUD是 MaixPy 支持的一种模型描述文件，用来统一不同平台的模型文件，方便 MaixPy 代码跨平台，本身是一个 ini格式的文本文件，可以使用文本编辑器编辑。&lt;/p&gt;
&lt;h3&gt;方法一：完整 ONNX 导出&lt;/h3&gt;
&lt;p&gt;这个代码是简单的转换，读者可以自行修改参数。笔者开始用的是这个方法，后来发现需要裁剪节点，读者可以看下面的方法二。我用的是方法1，后面在转换脚本那里裁剪。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from ultralytics import YOLO

model = YOLO(&apos;runs/detect/train9/weights/best.pt&apos;)

model.export(
    format=&apos;onnx&apos;,
    imgsz=640,        # 和训练时一致
    opset=12,         # OpenCV 兼容性最好的 opset 版本
    simplify=True     # 简化模型，推理更快
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;方法二：ONNX 裁剪节点&lt;/h3&gt;
&lt;p&gt;这里是从官方贴过来的，&lt;a href=&quot;https://wiki.sipeed.com/maixpy/doc/zh/ai_model_converter/onnx_export.html&quot;&gt;自行跳转&lt;/a&gt;，核心就是用 &lt;code&gt;extract_onnx.py&lt;/code&gt; 提取节点：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import onnx
import sys

input_path = sys.argv[1]
output_path = sys.argv[2]
input_names_str = sys.argv[3]
output_names_str = sys.argv[4]
input_names = []
for s in input_names_str.split(&quot;,&quot;):
    input_names.append(s.strip())
output_names = []
for s in output_names_str.split(&quot;,&quot;):
    output_names.append(s.strip())
onnx.utils.extract_model(input_path, output_path, input_names, output_names)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用法：&lt;code&gt;python extract_onnx.py $model_path $onnx_extracted $input_names $output_names&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;或者直接一行命令：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python -c &quot;import onnx,sys; onnx.utils.extract_model(sys.argv[1], sys.argv[2], [s.strip() for s in sys.argv[3].split(&apos;,&apos;)], [s.strip() for s in sys.argv[4].split(&apos;,&apos;)])&quot; yolo11n.onnx export.onnx &quot;images&quot; &quot;/model.23/Concat_output_0,/model.23/Concat_1_output_0,/model.23/Concat_2_output_0&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 &lt;code&gt;yolo11n.onnx&lt;/code&gt;、&lt;code&gt;export.onnx&lt;/code&gt;、&lt;code&gt;&quot;images&quot;&lt;/code&gt;、&lt;code&gt;&quot;/model.23/Concat_output_0,...&quot;&lt;/code&gt; 依次替换成你自己的 &lt;code&gt;$model_path&lt;/code&gt;、&lt;code&gt;$onnx_extracted&lt;/code&gt;、&lt;code&gt;$input_names&lt;/code&gt;、&lt;code&gt;$output_names&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;此时得到的 &lt;code&gt;export.onnx&lt;/code&gt; 就是裁剪后的新 ONNX 文件，可以继续下一步了。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;步骤2：把用到的文件cp到 ubuntu20&lt;/h2&gt;
&lt;p&gt;校准图片还需要 ≥200 张训练样本（.jpg/.png），记得cp过去，我传是测试集val。&lt;/p&gt;
&lt;p&gt;另外就是转好的best.onnx镜像也cp过去，一共两个&lt;/p&gt;
&lt;h2&gt;步骤3：安装模型转换环境&lt;/h2&gt;
&lt;p&gt;这里我选用的是ubuntu20上根据官方教程使用pulsar2 Docker 镜像，用的是虚拟机，读者自行判断选择。&lt;/p&gt;
&lt;h3&gt;下载 pulsar2 Docker 镜像&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.modelscope.cn/models/AXERA-TECH/Pulsar2/files&quot;&gt;下载地址modelscope&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;滑到下面选择最新的版本，我选这个的是6.0.我虚拟机没啥内存选择安装&lt;code&gt;ax_pulsar2_6.0_lite.tar.gz&lt;/code&gt;有1G多,后面补了几个包才能转，读者有内存推荐安装&lt;code&gt;ax_pulsar2_6.0.tar.gz&lt;/code&gt;更好。&lt;/p&gt;
&lt;p&gt;我缺的包一条命令补上：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install onnx onnxsim -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;Docker 环境搭建&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 首次加载镜像
sudo docker load -i /home/uu20/下载/ax_pulsar2_6.0_lite.tar.gz
sudo docker images | grep pulsar2

# 启动容器（取名，去掉 --rm 保留环境）
cd /home/uu20/桌面/yolo/yolo11
sudo docker run -it --net host --name yolo_build -v ./:/data pulsar2:6.0-lite
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;注意：&lt;/strong&gt;
我用完不小心就把容器删除了。第一次没加 &lt;code&gt;--name&lt;/code&gt;，也没去掉 &lt;code&gt;--rm&lt;/code&gt;。退出容器后环境全丢——装的 &lt;code&gt;onnxsim&lt;/code&gt; 没了，中间产物也没了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决：&lt;/strong&gt; 去掉 &lt;code&gt;--rm&lt;/code&gt;，加 &lt;code&gt;--name yolo_build&lt;/code&gt; 命名。下次用 &lt;code&gt;docker start -ai yolo_build&lt;/code&gt; 回来，环境完好：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo docker start -ai yolo_build
cd /data &amp;#x26;&amp;#x26; bash build_yolo11.sh best
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;步骤4：ONNX → axmodel 转换&lt;/h2&gt;
&lt;p&gt;进入容器后：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd /data

# 首次安装依赖（仅一次）
pip install onnx onnxsim -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com

# 一键转换（约 15-20 分钟）
bash build_yolo11.sh best
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;build_yolo11.sh best&lt;/code&gt; 做了五件事：&lt;/p&gt;
&lt;p&gt;| 步骤 | 说明 | 产物 | 耗时 |
|------|------|------|------|
| Step 0 | &lt;code&gt;restructure_onnx.py&lt;/code&gt; 重构：6 Conv → 3 per-scale 4D Concat | &lt;code&gt;tmp1/best_restructured.onnx&lt;/code&gt; | &amp;#x3C; 1 min |
| Step 1 | &lt;code&gt;extract_onnx.py&lt;/code&gt; 提取输出节点 | &lt;code&gt;tmp1/best_extracted.onnx&lt;/code&gt; | &amp;#x3C; 1 min |
| Step 2 | &lt;code&gt;onnxsim&lt;/code&gt; 简化 | &lt;code&gt;tmp1/best.onnx&lt;/code&gt; | &amp;#x3C; 1 min |
| Step 3 | 打包校准图片 | &lt;code&gt;tmp_images/images.tar&lt;/code&gt; | &amp;#x3C; 1 min |
| Step 4 | &lt;code&gt;pulsar2 build&lt;/code&gt; NPU1 (vnpu) | &lt;code&gt;out/best_vnpu.axmodel&lt;/code&gt; | ~8 min |
| Step 5 | &lt;code&gt;pulsar2 build&lt;/code&gt; NPU2 (npu) | &lt;code&gt;out/best_npu.axmodel&lt;/code&gt; | ~8 min |&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;转换成功标志：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;========================================
转换完成! 输出文件在 out/ 目录:
  out/best_npu.axmodel
  out/best_vnpu.axmodel
========================================
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;6 个输出余弦相似度均 &gt; 0.999 表示 INT8 量化精度接近无损。&lt;/p&gt;
&lt;p&gt;脚本代码 &lt;code&gt;build_yolo11.sh&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# !/bin/bash

set -e

############# 改成你的参数 ####################
model_name=$1                    # 运行: ./build_yolo11.sh best
model_path=./${model_name}.onnx  # best.onnx
images_dir=./val                 # 校准图片目录
images_num=200                   # 用200张校准
input_names=images               # ONNX 输入节点名

config_path=yolo11_build_config.json

output_nodes=(
    &quot;/model.23/per_scale_concat_0_output_0&quot;   # scale0 80x80 (96ch, 4D)
    &quot;/model.23/per_scale_concat_1_output_0&quot;   # scale1 40x40 (96ch, 4D)
    &quot;/model.23/per_scale_concat_2_output_0&quot;   # scale2 20x20 (96ch, 4D)
)
#############################################

# 拼接输出节点字符串

onnx_output_names=&quot;&quot;
json_outputs=&quot;&quot;

for node in &quot;${output_nodes[@]}&quot;; do
    if [ -n &quot;$onnx_output_names&quot; ]; then
        onnx_output_names=&quot;${onnx_output_names},&quot;
    fi
    onnx_output_names=&quot;${onnx_output_names}${node}&quot;

    json_outputs=&quot;${json_outputs}
    {
      \&quot;tensor_name\&quot;: \&quot;${node}\&quot;,
      \&quot;dst_perm\&quot;: [0, 2, 3, 1]
    },&quot;
done

json_outputs=&quot;${json_outputs%,}&quot;

# 生成 pulsar2 config.json

cat &gt; $config_path &amp;#x3C;&amp;#x3C; EOF
{
  &quot;model_type&quot;: &quot;ONNX&quot;,
  &quot;npu_mode&quot;: &quot;NPU1&quot;,
  &quot;quant&quot;: {
    &quot;input_configs&quot;: [
      {
        &quot;tensor_name&quot;: &quot;${input_names}&quot;,
        &quot;calibration_dataset&quot;: &quot;tmp_images/images.tar&quot;,
        &quot;calibration_size&quot;: ${images_num},
        &quot;calibration_mean&quot;: [0, 0, 0],
        &quot;calibration_std&quot;: [255, 255, 255]
      }
    ],
    &quot;calibration_method&quot;: &quot;MinMax&quot;,
    &quot;precision_analysis&quot;: true
  },
  &quot;input_processors&quot;: [
    {
      &quot;tensor_name&quot;: &quot;${input_names}&quot;,
      &quot;tensor_format&quot;: &quot;RGB&quot;,
      &quot;tensor_layout&quot;: &quot;NCHW&quot;,
      &quot;src_format&quot;: &quot;RGB&quot;,
      &quot;src_dtype&quot;: &quot;U8&quot;,
      &quot;src_layout&quot;: &quot;NHWC&quot;,
      &quot;csc_mode&quot;: &quot;NoCSC&quot;
    }
  ],
  &quot;output_processors&quot;: [${json_outputs}
  ],
  &quot;compiler&quot;: {
    &quot;check&quot;: 3,
    &quot;check_mode&quot;: &quot;CheckOutput&quot;,
    &quot;check_cosine_simularity&quot;: 0.9
  }
}
EOF

echo -e &quot;\e[32m已生成配置文件: ${config_path}\e[0m&quot;

# 创建校准图片打包脚本

cat &gt; gen_cali_images_tar.py &amp;#x3C;&amp;#x3C; &apos;PYTHON_SCRIPT&apos;
import sys, os, random, shutil

images_dir = sys.argv[1]
images_num = int(sys.argv[2])

files = os.listdir(images_dir)
valid = []
for name in files:
    path = os.path.join(images_dir, name)
    ext = os.path.splitext(name)[1]
    if ext.lower() not in [&quot;.jpg&quot;, &quot;.jpeg&quot;, &quot;.png&quot;]:
        continue
    valid.append(path)

print(f&quot;images dir {images_dir} have {len(valid)} images&quot;)
if len(valid) &amp;#x3C; images_num:
    print(f&quot;not enough images in {images_dir}, have: {len(valid)}, need {images_num}&quot;)
    sys.exit(1)

idxes = random.sample(range(len(valid)), images_num)
shutil.rmtree(&quot;tmp_images&quot;, ignore_errors=True)
os.makedirs(&quot;tmp_images/images&quot;)
for i in idxes:
    target = os.path.join(&quot;tmp_images&quot;, &quot;images&quot;, os.path.basename(valid[i]))
    shutil.copyfile(valid[i], target)
os.chdir(&quot;tmp_images/images&quot;)
os.system(&quot;tar -cf ../images.tar *&quot;)
print(&quot;calibration tar created: tmp_images/images.tar&quot;)
PYTHON_SCRIPT

# 创建 onnx 提取脚本

cat &gt; extract_onnx.py &amp;#x3C;&amp;#x3C; &apos;PYTHON_SCRIPT&apos;
import onnx, sys

input_path = sys.argv[1]
output_path = sys.argv[2]
input_names_str = sys.argv[3]
output_names_str = sys.argv[4]
input_names = [s.strip() for s in input_names_str.split(&quot;,&quot;)]
output_names = [s.strip() for s in output_names_str.split(&quot;,&quot;)]

onnx.utils.extract_model(input_path, output_path, input_names, output_names)
print(f&quot;extracted onnx saved to: {output_path}&quot;)
PYTHON_SCRIPT

# 创建 ONNX 重构脚本（6 Conv → 3 per-scale Concat）

cat &gt; restructure_onnx.py &amp;#x3C;&amp;#x3C; &apos;PYTHON_SCRIPT&apos;
import onnx
from onnx import helper, TensorProto
import sys

input_path = sys.argv[1]
output_path = sys.argv[2]

m = onnx.load(input_path)
graph = m.graph

all_outputs = {o for n in graph.node for o in n.output}

for i in range(3):
    cv2_name = f&quot;/model.23/cv2.{i}/cv2.{i}.2/Conv_output_0&quot;
    cv3_name = f&quot;/model.23/cv3.{i}/cv3.{i}.2/Conv_output_0&quot;
    if cv2_name not in all_outputs or cv3_name not in all_outputs:
        print(f&quot;ERROR: scale {i} nodes not found&quot;)
        sys.exit(1)

    concat_out = f&quot;/model.23/per_scale_concat_{i}_output_0&quot;
    graph.node.append(helper.make_node(
        &quot;Concat&quot;, inputs=[cv2_name, cv3_name], outputs=[concat_out],
        name=f&quot;/model.23/per_scale_concat_{i}&quot;, axis=1
    ))
    graph.output.append(helper.make_tensor_value_info(
        concat_out, TensorProto.FLOAT, [1, 96, None, None]
    ))
    print(f&quot;Scale {i}: {cv2_name} + {cv3_name} -&gt; {concat_out}&quot;)

del graph.output[:3]  # 去掉旧输出，保留新 3 个
onnx.save(m, output_path)
print(f&quot;Saved restructured ONNX to: {output_path}&quot;)
PYTHON_SCRIPT

# ========== 开始转换 ==========

mkdir -p tmp1
onnx_extracted=tmp1/${model_name}_extracted.onnx
onnxsim_path=tmp1/${model_name}.onnx

# Step 0: 重构 ONNX（将 6 个 Conv 按尺度合并为 3 个 4D Concat）

echo -e &quot;\e[32mStep 0: 重构 ONNX 输出节点（per-scale Concat）\e[0m&quot;
restructured_onnx=tmp1/${model_name}_restructured.onnx
python restructure_onnx.py $model_path $restructured_onnx

# Step 1: 提取输出节点

echo -e &quot;\e[32mStep 1: 提取 ONNX 输出节点\e[0m&quot;
python extract_onnx.py $restructured_onnx $onnx_extracted $input_names &quot;$onnx_output_names&quot;

# Step 2: onnxsim 简化

echo -e &quot;\e[32mStep 2: ONNX 简化\e[0m&quot;
onnxsim $onnx_extracted $onnxsim_path

# Step 3: 打包校准图片

echo -e &quot;\e[32mStep 3: 打包校准图片\e[0m&quot;
python gen_cali_images_tar.py $images_dir $images_num

mkdir -p out
tmp_config_path=tmp/$config_path

# Step 4: vnpu (NPU1, 一半算力)

echo -e &quot;\e[32mStep 4: Building ${model_name}_vnpu.axmodel\e[0m&quot;
rm -rf tmp
mkdir tmp
cp $config_path $tmp_config_path
sed -i &apos;/npu_mode/c\&quot;npu_mode&quot;: &quot;NPU1&quot;,&apos; $tmp_config_path
pulsar2 build --target_hardware AX620E --input $onnxsim_path --output_dir tmp --config $tmp_config_path
cp tmp/compiled.axmodel out/${model_name}_vnpu.axmodel

# Step 5: npu (NPU2, 全部算力)

echo -e &quot;\e[32mStep 5: Building ${model_name}_npu.axmodel\e[0m&quot;
rm -rf tmp
mkdir tmp
cp $config_path $tmp_config_path
sed -i &apos;/npu_mode/c\&quot;npu_mode&quot;: &quot;NPU2&quot;,&apos; $tmp_config_path
pulsar2 build --target_hardware AX620E --input $onnxsim_path --output_dir tmp --config $tmp_config_path
cp tmp/compiled.axmodel out/${model_name}_npu.axmodel

rm -rf tmp tmp1 tmp_images

echo -e &quot;\e[32m========================================\e[0m&quot;
echo -e &quot;\e[32m转换完成! 输出文件在 out/ 目录:\e[0m&quot;
echo -e &quot;\e[32m  out/${model_name}_npu.axmodel\e[0m&quot;
echo -e &quot;\e[32m  out/${model_name}_vnpu.axmodel\e[0m&quot;
echo -e &quot;\e[32m========================================\e[0m&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;注意：&lt;/strong&gt;
MaixPy YOLO11 解码器需要 &lt;strong&gt;3 个 4D Concat 输出&lt;/strong&gt;（每个尺度合并 bbox+cls），但 ultralytics 导出的 Concat 是跨尺度全局合并的 3D 输出。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决：&lt;/strong&gt; &lt;code&gt;restructure_onnx.py&lt;/code&gt; 在编译前将 6 个 Conv 输出按尺度合并为 3 个 4D Concat（&lt;code&gt;per_scale_concat_0/1/2&lt;/code&gt;，axis=1，输出 &lt;code&gt;[N, 96, H, W]&lt;/code&gt;），再送入 pulsar2。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;步骤5：mud 配置文件&lt;/h2&gt;
&lt;p&gt;一共需要三个文件yolo11n.mud， yolo11n_npu.axmodel和yolo11n_vnpu.axmodel&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[basic]
type = axmodel
model_npu = best_npu.axmodel
model_vnpu = best_vnpu.axmodel

[extra]
model_type = yolo11
type = detector
input_type = rgb
labels = R_R1, B_R1, T03, T04, T05, T06, T07, T08, T09, T10, T11, T12, T13, T14, T15, T16, T17, F18, F19, F20, F21, F22, F23, F24, F25, F26, F27, F28, F29, F30, F31, F32

input_cache = true
output_cache = true
input_cache_flush = false
output_cache_inval = true

mean = 0, 0, 0
scale = 0.00392156862745098, 0.00392156862745098, 0.00392156862745098
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到， 指定了模型类别为axmodel, 模型路径为相对mud文件的路径下的*.axmodel文件；&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;字段说明：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;| 字段 | 说明 |
|------|------|
| &lt;code&gt;type = axmodel&lt;/code&gt; | MaixCAM2 模型格式 |
| &lt;code&gt;model_type = yolo11&lt;/code&gt; | MaixPy YOLO11 解码器 |
| &lt;code&gt;model_npu&lt;/code&gt; | 全 NPU 算力模型 |
| &lt;code&gt;model_vnpu&lt;/code&gt; | AI-ISP 模式下半算力模型 |
| &lt;code&gt;labels&lt;/code&gt; | 32 个类名，逗号分隔，顺序必须和训练一致 |
| &lt;code&gt;mean / scale&lt;/code&gt; | 预处理参数（&lt;code&gt;1/255 ≈ 0.00392&lt;/code&gt;，已在模型中集成） |&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;步骤6：部署到 MaixCAM2&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;scp out/best_npu.axmodel root@maixcam2:/root/
scp out/best_vnpu.axmodel root@maixcam2:/root/
scp best.mud root@maixcam2:/root/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或用 ADB / U 盘。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;步骤7：MaixPy 运行&lt;/h2&gt;
&lt;p&gt;main.py&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from maix import camera, display, image, nn, app, comm
import struct, os, time

report_on = True
APP_CMD_DETECT_RES = 0x02
CONF_TH = 0.50
IOU_TH = 0.45


def encode_objs(objs):
    body = b&apos;&apos;
    for obj in objs:
        body += struct.pack(&quot;&amp;#x3C;hhHHHf&quot;, obj.x, obj.y, obj.w, obj.h, obj.class_id, obj.score)
    return body


def find_model():
    try:
        base_dir = os.path.dirname(os.path.abspath(__file__))
    except Exception:
        base_dir = &quot;.&quot;
    candidates = [
        os.path.join(base_dir, &quot;best.mud&quot;),
        &quot;best.mud&quot;,
        &quot;/root/best.mud&quot;,
        &quot;/root/models/best/best.mud&quot;,
    ]
    for path in candidates:
        if os.path.exists(path):
            return path
    raise RuntimeError(&quot;best.mud not found&quot;)


model_path = find_model()
detector = nn.YOLO11(model=model_path, dual_buff=True)

cam = camera.Camera(detector.input_width(), detector.input_height(), detector.input_format())
dis = display.Display()
p = comm.CommProtocol(buff_size=1024)

last_heartbeat_ms = 0

while not app.need_exit():
    try:
        img = cam.read()
        if img is None:
            continue

        objs = detector.detect(img, conf_th=CONF_TH, iou_th=IOU_TH)

        if len(objs) &gt; 0 and report_on:
            body = encode_objs(objs)
            p.report(APP_CMD_DETECT_RES, body)

        for obj in objs:
            img.draw_rect(obj.x, obj.y, obj.w, obj.h, color=image.COLOR_RED)
            label = detector.labels[obj.class_id] if obj.class_id &amp;#x3C; len(detector.labels) else str(obj.class_id)
            msg = f&apos;{label}: {obj.score:.2f}&apos;
            y = obj.y if obj.y &gt; 12 else 12
            img.draw_string(obj.x, y - 12, msg, color=image.COLOR_RED)
        dis.show(img)

        # 每秒心跳
        now_ms = time.ticks_ms()
        if now_ms - last_heartbeat_ms &gt; 1000:
            p.report(0x01, struct.pack(&quot;&amp;#x3C;I&quot;, now_ms))
            last_heartbeat_ms = now_ms

    except Exception as e:
        print(f&quot;loop error: {e}&quot;)
        continue
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;就可以成功运行了&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;快速命令速查&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# === 从零开始 ===
cd /home/uu20/桌面/yolo/yolo11

# 进容器（首次）
sudo docker run -it --net host --name yolo_build -v ./:/data pulsar2:6.0-lite

# 容器内
pip install onnx onnxsim -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com
bash build_yolo11.sh best
exit

# 以后再进
sudo docker start -ai yolo_build

# 部署
scp out/best_npu.axmodel out/best_vnpu.axmodel best.mud root@maixcam2:/root/
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;写在最后&lt;/h2&gt;
&lt;p&gt;虽然跑通了一个教程，但是要明白&lt;strong&gt;跑通过程中每一步都知道为什么&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;接下来的可以做：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;替换 MaixCAM2 内置模型&lt;/strong&gt;——用自己的 32 类模型跑实时检测，对比内置模型的精度和速度&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;量化感知训练（QAT）&lt;/strong&gt;——pulsar2 的 INT8 量化余弦相似度 &gt; 0.999 已经很好，但如果某些类别精度掉得厉害，QAT 是下一道防线&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;模型剪枝 + 知识蒸馏&lt;/strong&gt;——YOLO11 在 640×480 上跑 NPU 够用，但如果要上更高分辨率或更低延迟，需要更小的 backbone&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;完成&lt;/h2&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;MaixPy YOLO11 文档：https://wiki.sipeed.com/maixpy/doc/cn/course/basic/ai/yolo11.html&lt;/li&gt;
&lt;li&gt;Pulsar2 使用指南：https://pulsar2-docs.readthedocs.io/&lt;/li&gt;
&lt;li&gt;Sipeed 模型转换教程：https://wiki.sipeed.com/maixpy/doc/cn/course/ai/deploy/maixcam2.html&lt;/li&gt;
&lt;li&gt;Sipeed 下载站：https://dl.sipeed.com/shareURL/MaixCAM/MaixCAM2/tools&lt;/li&gt;
&lt;li&gt;ONNX 模型可视化：https://netron.app/&lt;/li&gt;
&lt;li&gt;YOLO11 ultralytics 文档：https://docs.ultralytics.com/&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="/_astro/cover.MxWoVcR9.jpg"/><enclosure url="/_astro/cover.MxWoVcR9.jpg"/><dc:creator>嘉心糖</dc:creator><author>嘉心糖</author></item><item><title>搭建 VitePress 站点教程</title><link>https://jiaxin404.top/tech/about-my-notes</link><guid isPermaLink="true">https://jiaxin404.top/tech/about-my-notes</guid><description>记录我用 VitePress 搭建个人知识库的过程</description><pubDate>Mon, 06 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;继&lt;a href=&quot;/tech/about-my-blog&quot;&gt;个人博客&lt;/a&gt;之后，就想做一个搭建部署教程，同时记录一下以免自己忘记了，但总被各种事情耽搁。最近，看到有佬的个人笔记网站，萌生有搭一个人知识库的想法，趁现在期末周，腾出些时间弄了一下。
可见https://notes.jiaxin404.top/。&lt;/p&gt;
&lt;p&gt;对于个人博客，网上有很多模版，选一个自己喜欢的，在此基础上开发，前人栽树后人乘凉嘛，但更多的人卡在了配置和部署上面，本文搭建 VitePress 过程和这些blog站点类似，可作为教程参考。&lt;/p&gt;
&lt;p&gt;于是我决定搭一个&lt;strong&gt;个人知识库 / 笔记站&lt;/strong&gt;，和博客分开：
| | 博客 | 知识库 |
|------|------|------|
| &lt;strong&gt;定位&lt;/strong&gt; | 长文分享、思考感悟 | 学习笔记、技术备忘 |
| &lt;strong&gt;内容&lt;/strong&gt; | 完整文章 | 知识点整理、Cheatsheet |
| &lt;strong&gt;更新&lt;/strong&gt; | 不定期发文 | 边学边更新 |
| &lt;strong&gt;风格&lt;/strong&gt; | 个人风格、有故事性 | 结构化、便于查阅 |&lt;/p&gt;
&lt;h2&gt;框架选择&lt;/h2&gt;
&lt;p&gt;因为我的博客用的是 Astro，笔记站不想重复造轮子。这次目标很明确：&lt;strong&gt;开箱即用、专注内容&lt;/strong&gt;。
| 框架 | 特点 | 适合 |
|------|------|------|
| &lt;strong&gt;VitePress&lt;/strong&gt; | Vite + Vue，极简，启动快 | 文档站、知识库 |
| &lt;strong&gt;Docusaurus&lt;/strong&gt; | Facebook 出品，功能全 | 大型项目文档 |
| &lt;strong&gt;VuePress&lt;/strong&gt; | VitePress 的前身 | 偏旧的文档项目 |
| &lt;strong&gt;Hexo&lt;/strong&gt; | 老牌博客框架 | 博客 |&lt;/p&gt;
&lt;p&gt;最终选了 &lt;strong&gt;VitePress&lt;/strong&gt;，原因很简单：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;轻量&lt;/strong&gt; — &lt;code&gt;npm init vitepress@latest&lt;/code&gt; 就完事了&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自动侧边栏&lt;/strong&gt; — 写 Markdown 自动生成导航，不用手动配路由&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内置搜索&lt;/strong&gt; — 开箱即用，不用额外接 Algolia&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;长得好看&lt;/strong&gt; — 默认主题干净&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;VitePress搭建过程&lt;/h2&gt;
&lt;h3&gt;初始化项目&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir my-notes &amp;#x26;&amp;#x26; cd my-notes
npm init vitepress@latest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;初始化完就一个空壳，核心文件就两个：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docs/
├── .vitepress/
│   └── config.ts    # 侧边栏、导航、主题配置
└── index.md         # 首页
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;配置侧边栏&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;config.ts&lt;/code&gt; 里配置 &lt;code&gt;sidebar&lt;/code&gt;，这就是文档站的灵魂——左侧那一列分类导航：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;sidebar: {
  &apos;/notes/&apos;: [
    {
      text: &apos;学习笔记&apos;,
      items: [
        { text: &apos;xxx&apos;, link: &apos;/notes/xxx&apos; },

      ],
    },
    {
      text: &apos;工具&apos;,
      items: [
        { text: &apos;xxx&apos;, link: &apos;/notes/xxx&apos; },
      ],
    },
  ],
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样就分好了目录，之后每次新增笔记，在这里加一行链接就行，目录结构自动就有了。&lt;/p&gt;
&lt;p&gt;日常使用：就是往 &lt;code&gt;docs/notes/&lt;/code&gt; 下丢 Markdown 文件：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd docs/notes/
vim react-hooks.md    # 写内容
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在 &lt;code&gt;config.ts&lt;/code&gt; 的 sidebar 里补一条链接，&lt;code&gt;git push&lt;/code&gt;，结束。&lt;/p&gt;
&lt;h2&gt;部署&lt;/h2&gt;
&lt;p&gt;这次没选 GitHub Pages，选了 &lt;strong&gt;Vercel&lt;/strong&gt; 来部署：&lt;/p&gt;
&lt;p&gt;| 对比 | GitHub Pages | Vercel |
|------|------|------|
| 配置 | 需要写 Actions YAML | &lt;strong&gt;零配置&lt;/strong&gt;，自动识别 VitePress |
| 访问速度 | 国内偶尔不稳 | 全球 CDN，更快 |
| HTTPS | 手动配 | 自动 |
| 适合场景 | 一个账号一个站 | 多项目多站 |&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在部署之前，先把本地代码推到 GitHub 仓库：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 在 my-notes 目录下
git init
git add .
git commit -m &quot;init vitepress&quot;

# 去 GitHub 新建一个空仓库，然后推送
git remote add origin git@github.com:你的用户名/仓库名.git
git branch -M main
git push -u origin main
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;
&lt;p&gt;GitHub 授权 Vercel，登录后，在主面板点击 Add New… → Project&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Import 仓库，找到你刚刚创建的网站仓库，点击旁边的 Import 按钮
&lt;img src=&quot;./images/about-my-notes/1.png&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Deploy，在Vercel 会自动识别你的项目是什么框架（Astro, Next.js, etc.），并帮你填好所有构建设置。你什么都不用改，直接点击 Deploy 按钮
&lt;img src=&quot;./images/about-my-notes/2.png&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;稍等片刻，Vercel网站就已经上线了！Vercel 会提供一个 .vercel.app 结尾的免费域名供你访问。
&lt;img src=&quot;./images/about-my-notes/3.png&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;全程不需要写一行配置，Vercel 会自动检测到 VitePress。后面只需要在本地修改代码，然后 git push 到 GitHub，Vercel 就会自动拉取最新代码，重新构建和部署你的网站。完全自动化！&lt;/p&gt;
&lt;h2&gt;自定义子域名&lt;/h2&gt;
&lt;p&gt;1.购买域名
前往 NameSilo、GoDaddy 等域名注册商，购买一个你喜欢的域名。
我是在 NameSilo 购买的，后面以它为例：
&lt;img src=&quot;./images/about-my-notes/4.png&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;p&gt;我之前博客用的是 &lt;code&gt;jiaxin404.top&lt;/code&gt;，所以这次笔记站用子域名 &lt;code&gt;notes.jiaxin404.top&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;买好域名后，只需要两步：&lt;/p&gt;
&lt;h3&gt;1. DNS 添加 CNAME 记录&lt;/h3&gt;
&lt;p&gt;虽然域名商也提供 DNS 解析，但 Cloudflare 提供免费的全球 CDN 加速和更强大的安全防护，所以我用的是 Cloudflare。在域名 DNS 后台添加
| 类型 | 名称 | 目标 |
|------|------|------|
| CNAME | &lt;code&gt;notes&lt;/code&gt; | Vercel 分配的地址（如 &lt;code&gt;xxx.vercel.app&lt;/code&gt;） |&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/about-my-notes/5.png&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：如果开启了 Cloudflare 代理（橙色云），这里要先把代理关掉（灰色云），否则 Vercel 没法验证。验证通过后再开回去即可。&lt;/p&gt;
&lt;p&gt;这里我之前弄过 Cloudflare，所以只需要加一条 DNS 记录就行。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果你是从零开始，完整流程如下：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;注册 &lt;a href=&quot;https://dash.cloudflare.com/sign-up&quot;&gt;Cloudflare&lt;/a&gt; 账号&lt;/li&gt;
&lt;li&gt;登录后点击 &lt;strong&gt;Add a domain&lt;/strong&gt;，输入你购买的域名，选择 &lt;strong&gt;Free&lt;/strong&gt; 方案（个人用完全够）&lt;/li&gt;
&lt;li&gt;Cloudflare 会扫描现有 DNS 记录（新域名是空的），然后提示你更改 NameServer&lt;/li&gt;
&lt;li&gt;记下 Cloudflare 提供的两个 NameServer 地址&lt;/li&gt;
&lt;li&gt;回到域名注册商（如 NameSilo）的后台，找到 NameServer 设置，替换为 Cloudflare 提供的那两个地址
&lt;ul&gt;
&lt;li&gt;例如 NameSilo：My Account → Domain Manager → 你的域名 → NameServers，删除默认的，填入 Cloudflare 的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;回到 Cloudflare，在 &lt;strong&gt;SSL/TLS&lt;/strong&gt; 页面，加密模式设为 &lt;strong&gt;Full (strict)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;2. Vercel 绑定域名&lt;/h3&gt;
&lt;p&gt;进入 Vercel 项目 → Settings → Domains → 输入 &lt;code&gt;notes.jiaxin404.top&lt;/code&gt; → Add。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/about-my-notes/6.png&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;h3&gt;3. Cloudflare 加 CNAME 指向 Vercel&lt;/h3&gt;
&lt;p&gt;在 Cloudflare 的 DNS → Records，添加 CNAME 记录：
| 字段 | 值 |
|------|------|
| Type | &lt;code&gt;CNAME&lt;/code&gt; |
| Name | &lt;code&gt;notes&lt;/code&gt;（代表子域名前缀） |
| Target | Vercel 分配的地址（如 &lt;code&gt;xxx.vercel.app&lt;/code&gt;） |
| Proxy status | Proxied（橙色云朵亮起） |&lt;/p&gt;
&lt;p&gt;稍等片刻，DNS 生效后就能通过 &lt;code&gt;https://notes.jiaxin404.top&lt;/code&gt; 访问了。&lt;/p&gt;
&lt;p&gt;这跟 &lt;code&gt;juniortree.com&lt;/code&gt; 分出 &lt;code&gt;note.juniortree.com&lt;/code&gt; 是一个道理——子域名随便加，不需要额外买域名，一个域名可以分出无数个子域名。&lt;/p&gt;
&lt;h2&gt;域名到底怎么管&lt;/h2&gt;
&lt;p&gt;这里遇到一个小坑：我的 &lt;code&gt;jiaxin404.top&lt;/code&gt; 是在 &lt;strong&gt;NameSilo&lt;/strong&gt; 买的，但 DNS 解析托管在 &lt;strong&gt;Cloudflare&lt;/strong&gt;。所以我在 NameSilo 加 DNS 记录没生效，因为实际解析权在 Cloudflare。&lt;/p&gt;
&lt;p&gt;怎么判断 DNS 在哪管？随便查一个你的域名的 DNS 记录，看 NS（Name Server）指向哪家就知道。大部分人会选择把域名买和 DNS 解析分开——NameSilo 负责续费，Cloudflare 负责解析，各干各的。&lt;/p&gt;
&lt;h2&gt;最后的感受&lt;/h2&gt;
&lt;p&gt;博客 + 知识库，两个站加起来：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;jiaxin404.top          → 博客（Astro + GitHub Pages）
notes.jiaxin404.top    → 知识库（VitePress + Vercel）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样分工很清晰：博客写长文分享，知识库记学习笔记，互不干扰。&lt;/p&gt;
&lt;p&gt;这次从搭框架到上线，加上绑域名，总共花了不到2个小时，毕竟之前VitePress 确实省心——没有花里胡哨的配置，打开就能写，写完 push 就更新。&lt;/p&gt;
&lt;p&gt;希望这篇文章对你搭建自己的知识库有所帮助。&lt;/p&gt;
&lt;h2&gt;小结&lt;/h2&gt;
&lt;p&gt;技术是骨架，内容才是灵魂。先让它跑起来，坚持记录才是最重要的。希望我能继续坚持更新写下去。&lt;/p&gt;</content:encoded><h:img src="/_astro/cover.m2elBpfm.jpg"/><enclosure url="/_astro/cover.m2elBpfm.jpg"/><dc:creator>嘉心糖</dc:creator><author>嘉心糖</author></item><item><title>相机标定教程</title><link>https://jiaxin404.top/tech/camera-calibration</link><guid isPermaLink="true">https://jiaxin404.top/tech/camera-calibration</guid><description>基于棋盘格，使用 camera_calibration 包获取相机内参和畸变系数</description><pubDate>Sat, 27 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;相机标定是视觉 SLAM、深度估计、目标检测等任务的前置步骤——未标定的相机会引入桶形/枕形畸变，导致 3D 点云变形、建图歪斜。&lt;/p&gt;
&lt;p&gt;常见的相机标定方法有几种：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;OpenCV 标定&lt;/strong&gt;：使用 &lt;code&gt;cv2.calibrateCamera()&lt;/code&gt;，灵活但需要手动写采集脚本，适合 Python/C++ 项目&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;张正友标定法（Zhang&apos;s Calibration）&lt;/strong&gt;：只需一张打印的棋盘格，就能获取相机的&lt;strong&gt;内参矩阵&lt;/strong&gt;和&lt;strong&gt;畸变系数&lt;/strong&gt;，是目前最通用的标定方法&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kalibr&lt;/strong&gt;：支持多相机 + IMU 联合标定，适用 SLAM/VIO 等高精度场景&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Matlab Camera Calibrator&lt;/strong&gt;：图形化操作，适合快速验证和教学&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本文记录张正友标定法在 ROS 2 Humble 下用 &lt;code&gt;camera_calibration&lt;/code&gt; 包完成标定的全过程，底层实际调用的是 OpenCV 的同款算法。&lt;/p&gt;
&lt;h2&gt;原理简述&lt;/h2&gt;
&lt;p&gt;棋盘格上的方格边长已知，每个黑白交叉点的 3D 世界坐标也是已知的。摄像头从不同角度拍摄棋盘格 → 检测每张图片中的角点 → 建立 3D→2D 对应关系 → 解算相机内参和畸变参数。&lt;/p&gt;
&lt;h2&gt;1. 环境准备&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 安装依赖
sudo apt install ros-humble-camera-calibration \
  ros-humble-camera-calibration-parsers \
  ros-humble-camera-info-manager

# 安装 USB 摄像头驱动
sudo apt install ros-humble-usb-cam

# 如果 NumPy 版本冲突（2.x 与 cv_bridge 不兼容）
pip uninstall opencv-python -y
pip install &quot;numpy&amp;#x3C;2&quot; -i https://pypi.tuna.tsinghua.edu.cn/simple
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. 制作棋盘格标定板&lt;/h2&gt;
&lt;p&gt;打印一张黑白棋盘格。关键参数：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;格子数&lt;/strong&gt;：7 列 × 5 行&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;方格边长&lt;/strong&gt;：2cm（20mm）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--size&lt;/code&gt; 参数填的是&lt;strong&gt;内角点数&lt;/strong&gt;，不是格子数——内角点 = (列-1) × (行-1) = &lt;strong&gt;6×4&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;内角点是黑白格子交叉的顶点，从外侧数：7 列 5 行的棋盘格，内角点为 6×4。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;./images/camera_calibration/1.png&quot; alt=&quot;标定图形&quot;&gt;&lt;/p&gt;
&lt;h2&gt;3. 启动摄像头&lt;/h2&gt;
&lt;p&gt;确认摄像头设备号：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ls /dev/video*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;选择 USB 摄像头对应的设备（本例为 &lt;code&gt;/dev/video4&lt;/code&gt;）：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;source /opt/ros/humble/setup.bash
ros2 run usb_cam usb_cam_node_exe --ros-args -p video_device:=/dev/video4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;摄像头正常启动后发布话题 &lt;code&gt;/image_raw&lt;/code&gt;。验证：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ros2 topic hz /image_raw
# 输出约 30 Hz 即正常
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4. 标定过程&lt;/h2&gt;
&lt;p&gt;打开&lt;strong&gt;新终端&lt;/strong&gt;，启动标定 GUI：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;source /opt/ros/humble/setup.bash
ros2 run camera_calibration cameracalibrator \
  --size 6x4 --square 0.02 \
  --ros-args -r image:=/image_raw -p camera:=/my_camera
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;参数说明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--size 6x4&lt;/code&gt;：内角点数（7 列 5 行棋盘格 → 6×4 内角）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--square 0.02&lt;/code&gt;：每格边长 0.02 米（2cm）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--pattern&lt;/code&gt;：标定板类型，可选 &lt;code&gt;chessboard&lt;/code&gt;（默认）、&lt;code&gt;circles&lt;/code&gt;、&lt;code&gt;acircles&lt;/code&gt;、&lt;code&gt;charuco&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-r image:=/image_raw&lt;/code&gt;：订阅摄像头话题&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-p camera:=/my_camera&lt;/code&gt;：服务命名空间&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;可选优化参数：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;| 参数 | 说明 |
|------|------|
| &lt;code&gt;--fix-principal-point&lt;/code&gt; | 固定光心到图像中心（cx, cy = 宽/2, 高/2） |
| &lt;code&gt;--fix-aspect-ratio&lt;/code&gt; | 强制 fx = fy，适合像素近似正方形的相机 |
| &lt;code&gt;--zero-tangent-dist&lt;/code&gt; | 切向畸变系数 p1, p2 归零，适合镜头对中较好的场景 |
| &lt;code&gt;-k N&lt;/code&gt; | 径向畸变系数个数（默认 2，最多 6，需要更精确的畸变模型时可提高） |&lt;/p&gt;
&lt;h2&gt;5. 采集与进度条&lt;/h2&gt;
&lt;p&gt;将棋盘格在摄像头前&lt;strong&gt;缓慢移动&lt;/strong&gt;，每类动作停留几秒等样本累积。&lt;/p&gt;
&lt;p&gt;| 进度条 | 含义 | 操作方法 |
|------|------|------|
| &lt;strong&gt;X Bar&lt;/strong&gt; | 水平方向覆盖 | 移到画面最左、最右 |
| &lt;strong&gt;Y Bar&lt;/strong&gt; | 垂直方向覆盖 | 移到画面顶部、底部 |
| &lt;strong&gt;Size Bar&lt;/strong&gt; | 远近覆盖 | 贴脸 → 拉到满屏 |
| &lt;strong&gt;Skew Bar&lt;/strong&gt; | 倾斜角度覆盖 | 大幅度倾斜 30°+ |&lt;/p&gt;
&lt;p&gt;采集约 40-50 个有效样本后，四根进度条全部变绿 → &lt;strong&gt;CALIBRATE&lt;/strong&gt; 按钮亮起。&lt;/p&gt;
&lt;h2&gt;6. 保存结果&lt;/h2&gt;
&lt;p&gt;点击 &lt;strong&gt;CALIBRATE&lt;/strong&gt; → 等待计算完成 → 点击 &lt;strong&gt;SAVE&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;结果保存在 &lt;code&gt;/tmp/calibrationdata.tar.gz&lt;/code&gt;，提取：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;tar -xvf /tmp/calibrationdata.tar.gz -C ~/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;得到 &lt;code&gt;ost.yaml&lt;/code&gt;，包含完整的相机内参和畸变系数。&lt;/p&gt;
&lt;h2&gt;7. 标定结果解读&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;image_width: 640
image_height: 480
camera_name: narrow_stereo
camera_matrix:
  data: [390.09,   0.   , 322.58,
           0.   , 520.27, 236.14,
           0.   ,   0.   ,   1.  ]
distortion_coefficients:
  data: [-0.4515, 0.1797, -0.0016, -0.0045, 0.0]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;相机矩阵&lt;/h3&gt;
&lt;p&gt;| 参数 | 值 | 说明 |
|------|------|------|
| &lt;code&gt;fx&lt;/code&gt; | 390.09 | X 轴焦距（像素单位） |
| &lt;code&gt;fy&lt;/code&gt; | 520.27 | Y 轴焦距（像素单位） |
| &lt;code&gt;cx&lt;/code&gt; | 322.58 | 光心 X 坐标 |
| &lt;code&gt;cy&lt;/code&gt; | 236.14 | 光心 Y 坐标 |&lt;/p&gt;
&lt;p&gt;&lt;code&gt;fx ≠ fy&lt;/code&gt; 表明相机像素不是正方形（X/Y 缩放比不同），属于正常光学特性。&lt;/p&gt;
&lt;p&gt;像素坐标与 3D 世界点的映射关系：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;u = fx × X/Z + cx
v = fy × Y/Z + cy
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;畸变系数&lt;/h3&gt;
&lt;p&gt;| 系数 | 值 | 类型 |
|------|------|------|
| &lt;code&gt;k1&lt;/code&gt; | -0.4515 | 二阶径向畸变 |
| &lt;code&gt;k2&lt;/code&gt; | 0.1797 | 四阶径向畸变 |
| &lt;code&gt;p1&lt;/code&gt; | -0.0016 | 切向畸变 |
| &lt;code&gt;p2&lt;/code&gt; | -0.0045 | 切向畸变 |&lt;/p&gt;
&lt;p&gt;&lt;code&gt;k1 = -0.45&lt;/code&gt;（负值）说明相机有&lt;strong&gt;明显的桶形畸变&lt;/strong&gt;——图像边缘的直线会向画面中心弯曲。这是广角镜头常见的特征。&lt;/p&gt;
&lt;h2&gt;8. 使用校准文件&lt;/h2&gt;
&lt;h3&gt;方式一：自动加载&lt;/h3&gt;
&lt;p&gt;将校准文件放到 ROS 默认路径，&lt;code&gt;usb_cam&lt;/code&gt; 启动时自动读取：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cp ost.yaml ~/.ros/camera_info/default_cam.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;方式二：显式指定&lt;/h3&gt;
&lt;p&gt;通过参数传入校准文件路径：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ros2 run usb_cam usb_cam_node_exe --ros-args \
  -p video_device:=/dev/video4 \
  -p camera_info_url:=file:///path/to/ost.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;底层实现：OpenCV 张正友标定法&lt;/h2&gt;
&lt;p&gt;ROS 2 的 &lt;code&gt;camera_calibration&lt;/code&gt; 包内部调用的是 OpenCV 的同款标定函数。以下从零实现，展示完整的标定流程。&lt;/p&gt;
&lt;h3&gt;1. 寻找棋盘格角点 —— &lt;code&gt;findChessboardCorners()&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;bool cv::findChessboardCorners(
    InputArray  image,        // 输入图像
    Size        patternSize,  // 内角点数，如 (6, 4) 表示 7×5 的棋盘格
    OutputArray corners,      // 输出的角点坐标向量
    int         flags = CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;首帧检测到棋盘格后，提取黑白交叉点的像素坐标，存入 &lt;code&gt;corners&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;2. 亚像素精化 —— &lt;code&gt;find4QuadCornerSubpix()&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;bool cv::find4QuadCornerSubpix(
    InputArray       img,         // 灰度图
    InputOutputArray corners,     // 输入粗糙角点，输出亚像素精化角点
    Size             region_size  // 搜索窗口大小，如 (5, 5)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;findChessboardCorners&lt;/code&gt; 返回的角点精度在像素级，&lt;code&gt;find4QuadCornerSubpix&lt;/code&gt; 在角点周围 &lt;code&gt;region_size&lt;/code&gt; 区域内以亚像素精度重新逼近，显著提升标定精度。&lt;/p&gt;
&lt;h3&gt;3. 标定求解 —— &lt;code&gt;calibrateCamera()&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;double cv::calibrateCamera(
    InputArrayOfArrays  objectPoints,  // 世界坐标系中的角点 3D 位置
    InputArrayOfArrays  imagePoints,   // 像素坐标系中的角点 2D 位置
    Size                imageSize,     // 图像尺寸
    InputOutputArray    cameraMatrix,  // 输出：3×3 相机内参矩阵
    InputOutputArray    distCoeffs,    // 输出：1×5 畸变系数
    OutputArrayOfArrays rvecs,         // 各视角的旋转向量
    OutputArrayOfArrays tvecs,         // 各视角的平移向量
    int  flags = 0,
    TermCriteria criteria = TermCriteria(COUNT + EPS, 30, DBL_EPSILON)
);
// 返回值为重投影误差（RMS，越小越好）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;已知 N 张图片中棋盘格角点的 3D→2D 对应关系，通过最小化重投影误差求解相机内参和畸变系数。&lt;strong&gt;返回值就是要交作业的&quot;重投影误差&quot;。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;4. 完整实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;opencv2/opencv.hpp&gt;

using namespace cv;

int main() {
    // === 修改这里：匹配你的棋盘格 ===
    const int   board_w = 6, board_h = 4;              // 内角点数（7×5 格子 → 6×4 内角）
    const int   board_n = board_w * board_h;           // 总内角数 24
    const float square_w = 20.0f, square_h = 20.0f;    // 单格边长 20mm
    const int   img_count = 40;                        // 标定图片数量
    // ===============================

    Size board_size(board_w, board_h);
    std::vector&amp;#x3C;Point2f&gt; corners_buf;
    std::vector&amp;#x3C;std::vector&amp;#x3C;Point2f&gt;&gt; image_points;

    Mat gray_img, drawn_img;
    Size img_size;
    int successes = 0;

    // 步骤 1：逐张检测角点 + 亚像素精化
    for (int i = 0; i &amp;#x3C; img_count; i++) {
        Mat src = imread(std::to_string(i) + &quot;.jpg&quot;);
        if (src.empty()) continue;

        if (successes == 0) img_size = src.size();

        bool found = findChessboardCorners(src, board_size, corners_buf);
        if (found &amp;#x26;&amp;#x26; (int)corners_buf.size() == board_n) {
            successes++;
            cvtColor(src, gray_img, COLOR_BGR2GRAY);
            // 亚像素精化
            find4QuadCornerSubpix(gray_img, corners_buf, Size(5, 5));
            image_points.push_back(corners_buf);

            // 可视化角点标注
            drawn_img = src.clone();
            drawChessboardCorners(drawn_img, board_size, corners_buf, found);
            imshow(&quot;corners&quot;, drawn_img);
            waitKey(50);
        } else {
            std::cout &amp;#x3C;&amp;#x3C; &quot;image &quot; &amp;#x3C;&amp;#x3C; i &amp;#x3C;&amp;#x3C; &quot; failed to find all corners&quot; &amp;#x3C;&amp;#x3C; std::endl;
        }
        corners_buf.clear();
    }
    std::cout &amp;#x3C;&amp;#x3C; successes &amp;#x3C;&amp;#x3C; &quot; useful images&quot; &amp;#x3C;&amp;#x3C; std::endl;

    // 步骤 2：构造棋盘格 3D 物理坐标（所有 Z=0，格子依次排列）
    std::vector&amp;#x3C;std::vector&amp;#x3C;Point3f&gt;&gt; object_points;
    std::vector&amp;#x3C;Point3f&gt; obj_buf;

    for (int i = 0; i &amp;#x3C; successes; i++) {
        for (int j = 0; j &amp;#x3C; board_h; j++) {
            for (int k = 0; k &amp;#x3C; board_w; k++) {
                obj_buf.push_back(Point3f(k * square_w, j * square_h, 0));
            }
        }
        object_points.push_back(obj_buf);
        obj_buf.clear();
    }

    // 步骤 3：标定
    Mat camera_matrix(3, 3, CV_32FC1, Scalar::all(0));
    Mat dist_coeffs(1, 5, CV_32FC1, Scalar::all(0));
    std::vector&amp;#x3C;Mat&gt; rvecs, tvecs;

    double rms = calibrateCamera(object_points, image_points, img_size,
                                 camera_matrix, dist_coeffs, rvecs, tvecs);

    std::cout &amp;#x3C;&amp;#x3C; &quot;重投影误差(RMS): &quot; &amp;#x3C;&amp;#x3C; rms  &amp;#x3C;&amp;#x3C; std::endl;
    std::cout &amp;#x3C;&amp;#x3C; &quot;相机内参矩阵:&quot;    &amp;#x3C;&amp;#x3C; std::endl &amp;#x3C;&amp;#x3C; camera_matrix &amp;#x3C;&amp;#x3C; std::endl;
    std::cout &amp;#x3C;&amp;#x3C; &quot;畸变系数:&quot;        &amp;#x3C;&amp;#x3C; std::endl &amp;#x3C;&amp;#x3C; dist_coeffs   &amp;#x3C;&amp;#x3C; std::endl;
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编译运行：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 将标定图片放到同一目录，命名为 0.jpg ~ 39.jpg
g++ calibrate.cpp -o calibrate $(pkg-config --cflags --libs opencv4)
./calibrate
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. ROS 2 vs 手动 OpenCV 对比&lt;/h3&gt;
&lt;p&gt;| | &lt;code&gt;camera_calibration&lt;/code&gt;（ROS 2） | &lt;code&gt;calibrateCamera()&lt;/code&gt;（手动 OpenCV） |
|------|------|------|
| 采集方式 | 实时视频流自动采集，GUI 进度条反馈 | 手动拍照片，存成 &lt;code&gt;0.jpg ~ N.jpg&lt;/code&gt; |
| 角点检测 | 内部调用 &lt;code&gt;findChessboardCorners&lt;/code&gt; | 直接调用，完全控制参数 |
| 亚像素精化 | 内部做 | 显式调用 &lt;code&gt;find4QuadCornerSubpix&lt;/code&gt; |
| 标定求解 | 同款 &lt;code&gt;calibrateCamera&lt;/code&gt; | 同款 &lt;code&gt;calibrateCamera&lt;/code&gt; |
| I/O | 自动保存 &lt;code&gt;ost.yaml&lt;/code&gt; | 自己写文件输出 |
| 重投影误差 | 终端 &lt;code&gt;ost.yaml&lt;/code&gt; 里 | 返回值 &lt;code&gt;rms&lt;/code&gt; |
| 适用场景 | ROS 项目快速标定 | 独立项目、作业、嵌入式 |
| 底层本质 | &lt;strong&gt;都是 OpenCV 张正友标定法&lt;/strong&gt; | |&lt;/p&gt;
&lt;p&gt;学好手动版能真正理解标定内核：角点检测 → 亚像素精化 → 3D/2D 对应 → 最小化重投影误差求解。ROS 2 版就是把这套流程封装成了工程化工具。&lt;/p&gt;
&lt;h2&gt;小结&lt;/h2&gt;
&lt;p&gt;标定方法选型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ROS 2 &lt;code&gt;camera_calibration&lt;/code&gt;&lt;/strong&gt;：适合 ROS 项目，开箱即用，一秒上手&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;手动 OpenCV&lt;/strong&gt;：适合学习原理、独立项目、作业交付&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kalibr&lt;/strong&gt;：适合多相机 + IMU 联合标定&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Matlab&lt;/strong&gt;：适合快速验证和教学演示&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;无论哪种工具，底层都是张正友标定法——打印棋盘格 → 多角度拍摄 → 3D/2D 对应 → OpenCV &lt;code&gt;calibrateCamera()&lt;/code&gt; 求解。这个标定文件后续在视觉 SLAM、点云投影、双目深度估计等任务中都会用到——它是相机视觉任务的&quot;出厂参数&quot;。&lt;/p&gt;
&lt;p&gt;参考：
&lt;a href=&quot;https://docs.nav2.org/tutorials/docs/camera_calibration.html&quot;&gt;相机校准&lt;/a&gt;&lt;/p&gt;</content:encoded><h:img src="/_astro/cover.gKnzqsNV.jpg"/><enclosure url="/_astro/cover.gKnzqsNV.jpg"/><dc:creator>嘉心糖</dc:creator><author>嘉心糖</author></item><item><title>Astro 搭建 Waline 评论区</title><link>https://jiaxin404.top/tech/waline_jia</link><guid isPermaLink="true">https://jiaxin404.top/tech/waline_jia</guid><description>从零搭建 Waline 评论系统，支持邮件提醒</description><pubDate>Sat, 30 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;在搭建博客的时候，一个评论系统可以帮助你和读者进行互动。Waline 是一个轻量、功能丰富的评论系统，支持 Markdown、邮件提醒、表情包等功能。&lt;/p&gt;
&lt;p&gt;Waline 可以基于多种数据库以及 Vercel 搭建，虽然需要后端，但项目本身做了很好的封装，用户不需要了解任何内容，只需要跟着流程走下来就好。其安装教程为 &lt;a href=&quot;https://waline.js.org/guide/get-started/&quot;&gt;官方文档&lt;/a&gt;，全部手把手进行即可。&lt;/p&gt;
&lt;p&gt;也可以使用 Giscus 作为评论系统，见 &lt;a href=&quot;https://vitepress.yiov.top/plugin&quot;&gt;这个教程&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;本教程主要是根据官网教程来，这里只是大概步骤，想要搭建的读者看这个教程，自行跳转（&lt;a href=&quot;https://waline.js.org/guide/get-started/&quot;&gt;官方文档&lt;/a&gt;），建议看 B 站视频有手把手更好。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;提示&lt;/strong&gt;：LeanCloud 已于 2026 年 1 月停止新用户注册，2027 年 1 月将正式关闭服务。本教程推荐使用 &lt;a href=&quot;https://tidbcloud.com&quot;&gt;TiDB Cloud&lt;/a&gt; 作为替代方案。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;第一步：创建 TiDB 数据库&lt;/h2&gt;
&lt;p&gt;Waline 需要一个数据库来存储评论数据。本教程使用 &lt;a href=&quot;https://tidbcloud.com&quot;&gt;TiDB Cloud&lt;/a&gt;，免费提供 5GB 额度。基于这个 &lt;a href=&quot;https://waline.js.org/guide/deploy/tidb.html&quot;&gt;教程&lt;/a&gt;。&lt;/p&gt;
&lt;h3&gt;注册并创建实例&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;访问 &lt;a href=&quot;https://tidbcloud.com&quot;&gt;TiDB Cloud&lt;/a&gt; 注册登录&lt;/li&gt;
&lt;li&gt;登录后会自动创建 TiDB 实例，点击 &lt;strong&gt;cluster0&lt;/strong&gt; 进入实例&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;创建数据库表&lt;/h3&gt;
&lt;p&gt;进入实例后，左侧选择 &lt;strong&gt;SQL Editor&lt;/strong&gt;，依次将以下 SQL 语句粘贴并执行（每贴一句点运行按钮 Run 一下或 &lt;code&gt;Ctrl + Enter&lt;/code&gt;），源码见 &lt;a href=&quot;https://github.com/walinejs/waline/blob/main/assets/waline.tidb&quot;&gt;waline.tidb&lt;/a&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE DATABASE IF NOT EXISTS waline DEFAULT CHARACTER SET utf8mb4;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;USE waline;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE `wl_Comment` (
  `id` int NOT NULL AUTO_INCREMENT,
  `commentId` varchar(255) DEFAULT NULL,
  `userId` varchar(255) DEFAULT NULL,
  `comment` text,
  `ip` varchar(100) DEFAULT NULL,
  `userAgent` text,
  `url` varchar(255) DEFAULT NULL,
  `nick` varchar(255) DEFAULT NULL,
  `mail` varchar(255) DEFAULT NULL,
  `link` varchar(255) DEFAULT NULL,
  `pid` varchar(255) DEFAULT NULL,
  `rid` varchar(255) DEFAULT NULL,
  `sticky` tinyint DEFAULT NULL,
  `status` varchar(50) NOT NULL DEFAULT &apos;&apos;,
  `createdAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updatedAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `isAdmin` tinyint DEFAULT &apos;0&apos;,
  PRIMARY KEY (`id`),
  KEY `wl_Comment_commentId_index` (`commentId`),
  KEY `wl_Comment_userId_index` (`userId`),
  KEY `wl_Comment_status_index` (`status`),
  KEY `wl_Comment_url_index` (`url`)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE `wl_Counter` (
  `id` int NOT NULL AUTO_INCREMENT,
  `userId` varchar(255) DEFAULT NULL,
  `ip` varchar(100) DEFAULT NULL,
  `url` varchar(255) NOT NULL DEFAULT &apos;&apos;,
  `createdAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updatedAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `times` int NOT NULL DEFAULT &apos;0&apos;,
  PRIMARY KEY (`id`),
  KEY `wl_Counter_url_index` (`url`)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE `wl_Users` (
  `id` int NOT NULL AUTO_INCREMENT,
  `display_name` varchar(255) NOT NULL DEFAULT &apos;&apos;,
  `email` varchar(255) NOT NULL DEFAULT &apos;&apos;,
  `password` varchar(255) NOT NULL DEFAULT &apos;&apos;,
  `type` varchar(50) NOT NULL DEFAULT &apos;&apos;,
  `label` varchar(255) DEFAULT NULL,
  `url` varchar(255) DEFAULT NULL,
  `avatar` varchar(255) DEFAULT NULL,
  `github` varchar(255) DEFAULT NULL,
  `twitter` varchar(255) DEFAULT NULL,
  `facebook` varchar(255) DEFAULT NULL,
  `google` varchar(255) DEFAULT NULL,
  `weibo` varchar(255) DEFAULT NULL,
  `qq` varchar(255) DEFAULT NULL,
  `createdAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updatedAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `2fa` varchar(32) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `wl_Users_email_index` (`email`),
  KEY `wl_Users_display_name_index` (`display_name`)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;获取连接信息&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;点击左侧 &lt;strong&gt;Overview&lt;/strong&gt; 进入首页&lt;/li&gt;
&lt;li&gt;点击右上角 &lt;strong&gt;Connect&lt;/strong&gt; 获取连接信息&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Connect with&lt;/strong&gt; 选择 &lt;strong&gt;General&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;点击 &lt;strong&gt;Reset password&lt;/strong&gt; 生成新密码&lt;/li&gt;
&lt;li&gt;记住以下信息：&lt;code&gt;Host&lt;/code&gt;、&lt;code&gt;Port&lt;/code&gt;、&lt;code&gt;Database&lt;/code&gt;、&lt;code&gt;User&lt;/code&gt;、&lt;code&gt;Password&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;第二步：部署 Waline 到 Vercel&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;在 GitHub 上 Fork &lt;a href=&quot;https://github.com/walinejs/waline&quot;&gt;Waline 仓库&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;登录 &lt;a href=&quot;https://vercel.com&quot;&gt;Vercel&lt;/a&gt;，点击 &lt;strong&gt;Add New → Project&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;导入你 Fork 的 Waline 仓库&lt;/li&gt;
&lt;li&gt;在 &lt;strong&gt;Environment Variables&lt;/strong&gt; 中添加 TiDB 连接信息：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;TIDB_HOST=你的TiDB Host
TIDB_PORT=4000
TIDB_DB=waline
TIDB_USER=你的TiDB User
TIDB_PASSWORD=你的TiDB Password
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;点 &lt;strong&gt;Deploy&lt;/strong&gt;，等待部署完成&lt;/li&gt;
&lt;li&gt;部署完成后，访问 &lt;code&gt;https://你的服务地址/ui/&lt;/code&gt; 注册第一个账号（&lt;strong&gt;第一个注册的自动成为管理员&lt;/strong&gt;，务必在部署后直接先行登录）&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;第三步：在 Astro 中集成&lt;/h2&gt;
&lt;h3&gt;安装客户端库&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install @waline/client
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;创建 Comment 组件&lt;/h3&gt;
&lt;p&gt;创建 &lt;code&gt;src/components/waline/Comment.astro&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-astro&quot;&gt;---
import &apos;@waline/client/style&apos;
const { class: className } = Astro.props
---

&amp;#x3C;comment-component&gt;
  &amp;#x3C;div id=&apos;waline&apos; class={`not-prose ${className}`}&gt;
    Comment seems to stuck. Try to refresh?✨
  &amp;#x3C;/div&gt;
&amp;#x3C;/comment-component&gt;

&amp;#x3C;script&gt;
  import { init as walineInit } from &apos;@waline/client&apos;

  const walineConfig = {
    server: &apos;https://你的waline服务地址.vercel.app&apos;,
    emoji: [&apos;weibo&apos;, &apos;alus&apos;, &apos;bilibili&apos;, &apos;qq&apos;],
    additionalConfigs: {
      locale: {
        placeholder: &apos;欢迎留言~(支持Markdown语法)&apos;
      }
    }
  }

  class Comment extends HTMLElement {
    constructor() {
      super()
    }

    connectedCallback() {
      const walineInstance = walineInit({
        el: &apos;#waline&apos;,
        serverURL: walineConfig.server,
        reaction: [],
        dark: &apos;html.dark&apos;,
        pageview: true,
        comment: true,
        ...walineConfig.additionalConfigs
      })
      walineInstance?.update()
    }
  }

  customElements.define(&apos;comment-component&apos;, Comment)
&amp;#x3C;/script&gt;

&amp;#x3C;style&gt;
  :global(.dark) #waline {
    --waline-bg-color: var(--card-bg);
    --waline-bg-color-light: var(--btn-plain-bg-hover);
  }
  :global(.wl-count) { color: var(--waline-dark-grey); }
  :global(.wl-editor) { padding: 0.35em 0.5em; width: calc(100% - 2em); }
  :global(.wl-edit) { color: var(--waline-dark-grey); }
  :global(#wl-edit)::placeholder { color: var(--waline-dark-grey); }
  :global(.wl-panel) { margin: .5em 0; border: none; }
&amp;#x3C;/style&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;配置 site.config.ts&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;site.config.ts&lt;/code&gt; 中启用 Waline 并填入你的服务地址：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;waline: {
  enable: true,
  server: &apos;https://你的waline服务地址.vercel.app&apos;,
  showMeta: false,
  emoji: [&apos;bilibili&apos;, &apos;weibo&apos;],
  additionalConfigs: {
    pageview: true,
    comment: true,
    locale: {
      placeholder: &apos;欢迎评论！(留下邮箱可以收到回复通知，无需登录)&apos;,
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;在布局中引入&lt;/h3&gt;
&lt;p&gt;在需要评论的布局组件中引入 Comment 组件：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-astro&quot;&gt;import { Comment } from &apos;@/components/waline&apos;

// 在模板中使用
&amp;#x3C;Comment class=&apos;mt-3 sm:mt-6&apos; /&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;第四步：配置邮件提醒&lt;/h2&gt;
&lt;p&gt;Waline 支持邮件提醒，当有人回复你的评论时会收到邮件通知。参考 &lt;a href=&quot;https://waline.js.org/guide/features/notification.html&quot;&gt;Waline 文档&lt;/a&gt; 搭建。&lt;/p&gt;
&lt;p&gt;在 Waline 的 Vercel 项目中添加环境变量，以 QQ 邮箱为例：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;SMTP_HOST=smtp.qq.com
SMTP_PORT=465
SMTP_USER=你的QQ邮箱@qq.com
SMTP_PASS=你的QQ邮箱授权码
SMTP_SECURE=TRUE
SITE_NAME=你的博客名
SITE_URL=https://你的博客地址
AUTHOR_EMAIL=你的QQ邮箱@qq.com
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：&lt;code&gt;SMTP_PASS&lt;/code&gt; 不是 QQ 邮箱密码，需要在 QQ 邮箱 → 设置 → 账户 → 开启 SMTP 服务后生成的&lt;strong&gt;授权码&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;添加环境变量后，重新部署 Waline 项目即可。&lt;a href=&quot;https://waline-jiaxin.vercel.app/ui/&quot;&gt;我的管理界面 &lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;后记&lt;/h2&gt;
&lt;p&gt;以上就是完整的 Waline 集成过程，从 TiDB 数据库创建到邮件提醒配置一共四步。评论系统上线后，读者不需要注册任何账号就可以留言，体验还是比较友好的。&lt;/p&gt;
&lt;p&gt;如果在搭建过程中遇到问题，欢迎在评论区留言交流。&lt;/p&gt;</content:encoded><h:img src="/_astro/cover.DLL1oD3e.jpg"/><enclosure url="/_astro/cover.DLL1oD3e.jpg"/><dc:creator>嘉心糖</dc:creator><author>嘉心糖</author></item><item><title>GitHub 个人主页美化指南</title><link>https://jiaxin404.top/tech/about_me_git</link><guid isPermaLink="true">https://jiaxin404.top/tech/about_me_git</guid><description>从零打造个性化的 GitHub 个人主页</description><pubDate>Fri, 15 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;今天花了点时间优化了 GitHub 个人主页，操作也就几步不麻烦，分享记录下来方便以后查阅，也希望对你有帮助。&lt;a href=&quot;https://github.com/jiaxintang-xiang&quot;&gt;我的主页&lt;/a&gt;。&lt;/p&gt;
&lt;h2&gt;1. 创建同名仓库&lt;/h2&gt;
&lt;p&gt;新建一个仓库，&lt;strong&gt;仓库名必须和你的 GitHub 用户名完全一致&lt;/strong&gt;。比如我的用户名是 &lt;code&gt;JiaXinTang-Xiang&lt;/code&gt;，仓库名就填 &lt;code&gt;JiaXinTang-Xiang&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;记得勾选 &lt;strong&gt;Add a README file&lt;/strong&gt;，创建成功后 GitHub 会提示这是一个个人主页专属仓库。&lt;/p&gt;
&lt;h2&gt;2. 编辑 README.md 美化主页&lt;/h2&gt;
&lt;p&gt;用 Markdown 写自我介绍、技能标签、项目展示：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;# Hi there, I&apos;m JiaXinTang-Xiang 
About Me
你好，我是xxx
欢迎来我的主页~

---

## 🛠️ Tech Stack

- **语言**: Python, TypeScript, C
- **前端**: React, Astro, UnoCSS
- **工具**: VS Code, Neovim, Git, Docker
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;多用 emoji、分割线、链接，让页面整洁好看。&lt;/p&gt;
&lt;h2&gt;3. 常用美化组件&lt;/h2&gt;
&lt;h3&gt;GitHub 统计卡片&lt;/h3&gt;
&lt;p&gt;使用 &lt;a href=&quot;https://github.com/anuraghazra/github-readme-stats&quot;&gt;github-readme-stats&lt;/a&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;![GitHub Stats](https://github-readme-stats.vercel.app/api?username=JiaXinTang-Xiang&amp;#x26;show_icons=true&amp;#x26;theme=transparent)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;常用语言统计&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=JiaXinTang-Xiang&amp;#x26;layout=compact&amp;#x26;theme=transparent)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;动态活动图&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;![GitHub Activity Graph](https://github-readme-activity-graph.vercel.app/graph?username=JiaXinTang-Xiang&amp;#x26;theme=github)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;记得把 &lt;code&gt;JiaXinTang-Xiang&lt;/code&gt; 换成你自己的 GitHub 用户名。&lt;/p&gt;
&lt;h2&gt;4. 保存生效&lt;/h2&gt;
&lt;p&gt;提交修改后回到个人首页，刷新即可看到美化效果 ✅&lt;/p&gt;
&lt;h2&gt;5. 制作🐍 贪吃蛇贡献图&lt;/h2&gt;
&lt;p&gt;原理：用 GitHub Actions 自动生成贪吃蛇 SVG 动画，显示在你的个人首页 README 里。&lt;/p&gt;
&lt;h3&gt;创建配置文件&lt;/h3&gt;
&lt;p&gt;在你的主页仓库中创建路径 &lt;code&gt;.github/workflows/snake.yml&lt;/code&gt;，填入以下内容：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: Generate Snake

on:
  schedule:
    - cron: &quot;0 0 * * *&quot;   # 每天 UTC 0 点自动运行
  workflow_dispatch:        # 允许手动触发

jobs:
  generate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Generate Snake
        uses: Platane/snk@v3
        with:
          github_user_name: ${{ github.repository_owner }}
          outputs: |
            dist/github-contribution-grid-snake.svg
            dist/github-contribution-grid-snake-dark.svg?palette=github-dark

      - name: Push to output branch
        uses: crazy-max/ghaction-github-pages@v3
        with:
          target_branch: output
          build_dir: dist
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;提交保存后，进入仓库 &lt;strong&gt;Actions&lt;/strong&gt; 标签 → 左侧点击 &lt;strong&gt;Generate Snake&lt;/strong&gt; → 右侧 &lt;strong&gt;Run workflow&lt;/strong&gt; → &lt;strong&gt;Run workflow&lt;/strong&gt;。等约 1 分钟，运行变绿 ✅成功。&lt;/p&gt;
&lt;h3&gt;放到 README.md 显示&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;&amp;#x3C;picture&gt;
  &amp;#x3C;source media=&quot;(prefers-color-scheme: dark)&quot; srcset=&quot;https://raw.githubusercontent.com/JiaXinTang-Xiang/JiaXinTang-Xiang/output/github-contribution-grid-snake-dark.svg&quot;&gt;
  &amp;#x3C;source media=&quot;(prefers-color-scheme: light)&quot; srcset=&quot;https://raw.githubusercontent.com/JiaXinTang-Xiang/JiaXinTang-Xiang/output/github-contribution-grid-snake.svg&quot;&gt;
  &amp;#x3C;img alt=&quot;github contribution grid snake&quot; src=&quot;https://raw.githubusercontent.com/JiaXinTang-Xiang/JiaXinTang-Xiang/output/github-contribution-grid-snake.svg&quot;&gt;
&amp;#x3C;/picture&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;把 &lt;code&gt;JiaXinTang-Xiang&lt;/code&gt; 换成你自己的用户名。&lt;/p&gt;
&lt;h3&gt;完成效果&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;☀️ 白天模式：浅色贪吃蛇&lt;/li&gt;
&lt;li&gt;🌙 黑夜模式：深色贪吃蛇&lt;/li&gt;
&lt;li&gt;⏰ 每天自动更新，吃掉你的贡献格子 🐍&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;小结&lt;/h2&gt;
&lt;p&gt;本文，从基础准备到具体组件配置，提供了简单易操作的完整指南，无需复杂技术，新手也能快速上手，轻松打造专属的个性化 GitHub 个人主页。贪吃蛇贡献图看着也是比较好玩，更多的组件感兴趣的朋友可以自行探索。&lt;/p&gt;
&lt;p&gt;总之，整个流程梳理下来就是：建同名仓库 → 写 Markdown → 加统计卡片/徽章 → 配 GitHub Actions 贪吃蛇。一个小时就能搞定一个漂亮的个人主页。&lt;/p&gt;</content:encoded><h:img src="/_astro/cover.CI7g3B1L.jpg"/><enclosure url="/_astro/cover.CI7g3B1L.jpg"/><dc:creator>嘉心糖</dc:creator><author>嘉心糖</author></item><item><title>Git的常见使用</title><link>https://jiaxin404.top/tech/use-git</link><guid isPermaLink="true">https://jiaxin404.top/tech/use-git</guid><description>介绍一些常见的git命令</description><pubDate>Tue, 28 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;本篇内容写作的初衷，是记录下平常git的使用操作。帮助 Git 初学者，了解关于 Git 的大多数的基本操作的，正常的 git clone 之后的add, commit, push 操作对于个人开发已经够用了。但是遇到了更加复杂的需求，难免会束手无策。&lt;/p&gt;
&lt;h2&gt;初始化 Github SSH&lt;/h2&gt;
&lt;p&gt;Github 更新后，Github 的不支持使用账号密码的身份验证，转为了使用个人访问令牌或者 SSH 的方式，使用 SSH 是非常方便的解决。SSH 生效的原理是，在本地生成的公钥私钥对，其中的公钥被上传至 Github，而在 SSH 之后，本地与 Github 建立安全连接，从而进行相关的操作。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh-keygen -t rsa -b 4096 -C &quot;your_email@example.com&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;根据提示点击即可&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cat ~/.ssh/id_rsa.pub
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里查看密钥，复制到github上面就行了。Ctrl + H显示隐藏文件&lt;/p&gt;
&lt;p&gt;​&lt;/p&gt;
&lt;h2&gt;常用小连招&lt;/h2&gt;
&lt;h3&gt;1. 基础工作流&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 1. 先拉取远程最新代码，避免冲突
git pull
# 2. 本地修改代码
git add .
git commit -m &quot;本次修改描述&quot;
# 3. 推送到远程仓库
git push
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 完整命令集合&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 1. 配置身份（只做一次）
git config --global user.name &quot;你的名字&quot;
git config --global user.email &quot;你的邮箱&quot;

# 2. 初始化 / 克隆
git init          # 新建仓库
git clone 地址     # 拉别人仓库

# 3. 日常三件套（最常用）
git status        # 看改了啥
git add .         # 加到暂存区
git commit -m &quot;说明&quot;  # 提交到本地

# 4. 远程同步
git push          # 上传
git pull          # 下载

# 5. 看历史
git log --oneline
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;创建仓库&lt;/h2&gt;
&lt;h3&gt;方式一：git init —— 从零创建本地仓库&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git init
git remote add origin 远程仓库地址  # 关联远程仓库
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;方式二：git clone —— 克隆已有远程仓库&lt;/h3&gt;
&lt;p&gt;已经有本地项目，还没有远程仓库，要关联到 GitHub/Gitee。如&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone https://github.com/用户名/仓库名.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;自己的远程仓库已经创建好，换电脑时直接克隆下来。参与开源项目，需要把别人的代码拉到本地修改。每次记得更新一下，在开始加内容&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git pull   # 拉取当前分支对应的远程分支
git pull origin main  # 明确拉取远程叫 origin 的 main 分支，不管你当前在哪个分支，都强行拉 main
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;一些可能用的命令&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git status # 查看仓库状态
git log    # 查看提交历史
git diff   # 默认对比：工作区 VS 暂存区

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;.&lt;/code&gt;代表当前目录下全部添加，也可以特指文件&lt;/p&gt;
&lt;h2&gt;撤销提交&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git reset --soft   # 不碰暂存区和工作区
git reset --mixed  # 清空暂存区
git checkout -- &amp;#x3C;file&gt;  # 撤销工作区修改
git reset HEAD &amp;#x3C;file&gt;   # 撤销暂存区文件
git checkout &amp;#x3C;commit_id&gt; -- &amp;#x3C;file_path&gt;  # 恢复误删的文件
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;分支管理&lt;/h2&gt;
&lt;p&gt;| 操作 | 命令 | 说明 |
|------|------|------|
| 查看本地分支列表 | &lt;code&gt;git branch&lt;/code&gt; | 带*标记的是当前所在分支 |
| 创建新分支 | &lt;code&gt;git branch branch-name&lt;/code&gt; | 仅创建，不会自动切换 |
| 切换分支（旧语法） | &lt;code&gt;git checkout branch-name&lt;/code&gt; | 传统切换命令 |
| 切换分支（官方推荐新语法） | &lt;code&gt;git switch branch-name&lt;/code&gt; | 语义清晰、不易出错 |
| 合并指定分支到当前分支 | &lt;code&gt;git merge branch-name&lt;/code&gt; | 将目标分支代码合并进来 |
| 安全删除已合并分支 | &lt;code&gt;git branch -d branch-name&lt;/code&gt; | 防止误删未合并代码 |
| 强制删除未合并分支 | &lt;code&gt;git branch -D branch-name&lt;/code&gt; | 丢弃该分支所有未合并改动 |&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git branch branch-name   # 创建新分支
git switch branch-name   # 切换分支，checkout也可以切换
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;无冲突合并&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git merge branch-name    # 合并指定分支到当前分支
git merge --abort        # 放弃合并、回退操作
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;有冲突合并&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git branch -d branch-name   # 安全删除已合并分支
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;.gitignore文件&lt;/h2&gt;
&lt;p&gt;.gitignore 是 Git 仓库里的一个忽略配置文件，就是告诉 Git：哪些文件 / 文件夹不需要纳入版本管理、不会被提交到远程仓库。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;文件名      # 忽略某个文件
文件夹/     # 忽略整个文件夹
*.后缀      # 忽略所有该后缀的文件
!文件       # 不忽略这个文件（例外）
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# 忽略所有日志文件
*.log

# 忽略所有class编译文件
*.class

# 忽略.env环境配置
.env

# 忽略整个target缓存目录
target/

# 递归忽略所有文件夹下的tmp文件
**/*.tmp

# 例外：不忽略config目录下的保留文件
!config/keep.ini


&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;放弃弃当前 Github 仓库分支并更新 main 分支#&lt;/h2&gt;
&lt;p&gt;对于部分的仓库的重构需求，例如需要将本仓库的完全的重建，同时出于保险起见，还需要将之前的分支进行备份，也就是将其置入一个废弃分支。&lt;/p&gt;
&lt;p&gt;首先先备份当前的分支：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git checkout main
git checkout -b deprecated-main
git push origin deprecated-main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重建当前的 main 分支：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git branch -D main
git checkout --orphan main
git rm -rf .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将新的文件添加到当前目录&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git add .
git commit -m &quot;Rebuild main branch&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后在 push 的时候使用 -f，也就是 force，进行强制推送：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git push -f origin main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;假如说本仓库存在一个 gh-pages 分支，有可能会需要删除这个分支，使用以下指令：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git push origin --delete gh-pages
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;以上整理了笔者日常开发中使用的 Git 实用技巧，也是仓库维护过程中最常用的基础操作。除此之外，还需要养成良好的使用习惯，例如在修改仓库代码前先执行 git pull 拉取远端更新，以此保证本地与远端仓库内容保持同步一致，还有尽量提交信息写规范，个人敏感信息写进 .gitignore。&lt;/p&gt;</content:encoded><h:img src="/_astro/cover.bCuu8Rjh.jpg"/><enclosure url="/_astro/cover.bCuu8Rjh.jpg"/><dc:creator>嘉心糖</dc:creator><author>嘉心糖</author></item><item><title>Ubuntu 常用命令大全</title><link>https://jiaxin404.top/tech/About-Ubuntu-command</link><guid isPermaLink="true">https://jiaxin404.top/tech/About-Ubuntu-command</guid><description>整理日常使用中最常用的 Ubuntu 命令，方便查阅和使用</description><pubDate>Mon, 08 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;本篇内容写作的初衷，是因为一些指令比较常见，所以写一篇博客记录一下，方便自己回看使用。&lt;/p&gt;
&lt;h2&gt;基础命令&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;df -h /       #查看系统内存使用情况
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;！！  # 重复上一级命令
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;pwd  # 显示当前工作目录的绝对路径
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;ls -al  # 以列表形式显示当前目录的所有文件和目录，包括隐藏的
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;cd ..  # 回到当前目录的上一级目录
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;cd -   # 回到上一次所在的目录
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;cd ~   # 回到当前用户的家目录
cd     # 回到当前用户的家目录
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;rm -rf # 强制删除整个文件夹及其内容
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;touch filename  # 创建空文件
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;cp -r source destination  # 递归复制文件夹
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;mv source destination  # 移动或重命名文件
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;find . -type f -name &quot;*.conf&quot;  # 在当前目录查找类型为文件(f)的.conf文件
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;ifconfig    # 显示网络接口参数（新版本Ubuntu可能需要安装net-tools）
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;lsb_release -a   #​ 获取Ubuntu版本号
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;arch  # 或者
uname -m  #查看Ubuntu机器的处理器架构
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;shutdown  # 关机
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;小鱼大佬的一键安装&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;wget http://fishros.com/install -O fishros &amp;#x26;&amp;#x26; . fishros
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Git 安装和配置&lt;/h2&gt;
&lt;h4&gt;安装 Git&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;sudo apt install git
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;配置用户信息&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;git config --global user.name &quot;Your Name&quot;
git config --global user.email &quot;your.email@example.com&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;配置 SSH&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;ssh-keygen -t ed25519 -C &quot;your.email@example.com&quot;
cat ~/.ssh/id_ed25519.pub
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Python 清华源配置&lt;/h2&gt;
&lt;p&gt;直接执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;终端工具&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sudo apt install terminator
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;显卡驱动安装&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sudo apt update
sudo ubuntu-drivers autoinstall
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;nvidia-smi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;cha&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; sudo dpkg --get-selections | grep linux
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;网络代理设置&lt;/h2&gt;
&lt;p&gt;Linux / Mac 终端&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export http_proxy=http://127.0.0.1:7890
export https_proxy=http://127.0.0.1:7890
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Windows PowerShell&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$env:http_proxy = &quot;http://127.0.0.1:7890&quot;
$env:https_proxy = &quot;http://127.0.0.1:7890&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;取消代理命令&lt;/h2&gt;
&lt;p&gt;Linux / Mac 终端&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;unset http_proxy
unset https_proxy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Windows PowerShell&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Remove-Item Env:http_proxy
Remove-Item Env:https_proxy
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;查看当前代理&lt;/h2&gt;
&lt;p&gt;Linux / Mac 终端&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;echo $http_proxy
echo $https_proxy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Windows PowerShell&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$env:http_proxy
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;图形窗口能显示在桌面&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;export DISPLAY=:0 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:0 = 本机第一个桌面显示器&lt;/p&gt;
&lt;h2&gt;安装cutecom&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sudo apt update
sudo apt install cutecom -y
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用cutecom&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo cutecom
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Jetson&lt;/h2&gt;
&lt;p&gt;1.启用MAX功率模式&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo nvpmodel -m 2 # Jetson Orin Nano
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2.启用Jetson时钟：CPU、GPU内核都以最大频率运行&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo jetson_clocks
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;软件包管理&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sudo dpkg -i package.deb
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Windows 时间同步&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sudo apt install ntpdate
sudo ntpdate time.windows.com
sudo hwclock --localtime --systohc
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;下载ssh&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sudo apt install openssh-server
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;systemctl status ssh
systemctl start ssh
systemctl enable ssh
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;下载文件树&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sudo apt-get install tree

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;安装 cutecom 串口工具&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sudo apt update
sudo apt install cutecom -y
sudo cutecom
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用 cutecom 点击 open 打开 /dev/ttyTHS1 串口号：在Input输入串口要发送的数据，上面窗口显示 /dev/ttyTHS1 串口发送的数据，下面窗口显示 /dev/ttyTHS1 接收的数据。&lt;/p&gt;
&lt;h2&gt;安装minicom&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sudo apt-get install minicom
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装完成后启动minicom&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;minicom -D /dev/ttyAMA0 -b 9600
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中-D表示选择串口/dev/ttyAMA0，-b 设置波特率为9600，此参数可以不用设置，默认115200。&lt;/p&gt;
&lt;h2&gt;cmake&lt;/h2&gt;
&lt;p&gt;安装&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt update
sudo apt install cmake
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一般使用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cmake_minimum_required (VERSION 3.10)
project(project3)

# bin 目录下存最后生成的可执行文件
set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

# 将源码文件.cpp的目录保存在SRC_LIST变量
aux_source_directory (src SRC_LIST)

# 指定头文件路径
include_directories(include)

add_executable(main3 ${SRC_LIST})

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编译&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir build
cd build
cmake ..
make -j8
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;中文语言包安装&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sudo apt update
sudo apt install language-pack-zh-hans
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;中文输入法安装&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;sudo apt install ibus ibus-libpinyin
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在终端执行以下代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ibus-setup
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;进入到此界面，在ibus首选项中，点击“输入法”，添加“中文”-“智能拼音”，然后关闭。&lt;/p&gt;
&lt;h3&gt;磁盘管理工具&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;sudo apt install gparted
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;装完运行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo gparted
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;matplotlib&lt;/h2&gt;
&lt;p&gt;Matlab 是一个常用的数据分析和绘图软件，在 Python 我们也可以使用 matplotlib 库绘制图形。在进行可视化时，我们使用的是 matplotlib.pyplot 子库&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install matplotlib
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;安装OpenCV&lt;/h2&gt;
&lt;h1&gt;Python版&lt;/h1&gt;
&lt;p&gt;(官网)[https://pypi.org/project/opencv-python/]&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install opencv-python
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;指定版本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install opencv-python==4.5.x.xx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装完虚拟机之后，就可以正式开始安装 OpenCV 了。&lt;/p&gt;
&lt;p&gt;首先，确保系统安装了基本依赖库：&lt;/p&gt;
&lt;h3&gt;C++ 版&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;必装依赖：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo apt install git gcc g++ ffmpeg cmake cmake-gui make \
  python3-dev python3-numpy python3-pip \
  libavcodec-dev libavformat-dev libswscale-dev \
  libgstreamer-plugins-base1.0-dev libgstreamer1.0-dev \
  libgtk-3-dev libpng-dev libjpeg-dev libopenexr-dev \
  libtiff-dev libwebp-dev libtbb-dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;（可选）安装 aptitude：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo apt install aptitude
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;（可选）安装 Boost 和 Eigen3：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo apt install libboost-all-dev libeigen3-dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;（可选）安装 Ceres Solver：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;没装成功，待看&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 安装依赖
sudo apt update
sudo apt install liblapack-dev libsuitesparse-dev \
  libgflags-dev libgoogle-glog-dev libgtest-dev

# 编译安装
git clone https://github.com/ceres-solver/ceres-solver.git
cd ceres-solver
mkdir build &amp;#x26;&amp;#x26; cd build
cmake ..
make -j$(nproc)
sudo make install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;（可选）安装 Qt5：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo apt install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools qtcreator

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;MiniConda&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.anaconda.com/docs/main&quot;&gt;Minoconda&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;我使用 MiniConda 来管理 Python 版本、环境。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir -p ~/.miniconda3
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/.miniconda3/miniconda.sh
bash ~/.miniconda3/miniconda.sh -b -u -p ~/.miniconda3
rm ~/.miniconda3/miniconda.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;串口权限配置&lt;/h2&gt;
&lt;p&gt;查看&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ls -la /dev/ttyUSB* /dev/ttyACM*
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt; ls -la /dev  # 或者
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;进入udev主目录&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;cd /etc/udev/rules.d/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这一步是导航到存放udev规则文件的目录，通常自定义的设备规则会放在这里。&lt;br&gt;
2. 创建并编辑规则文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo gedit tty_All.rules
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;sudo touch tty_All.rules
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;添加设备权限规则&lt;br&gt;
在打开的tty_All.rules文件中，添加类似以下的规则（根据你的设备实际情况调整KERNEL和GROUP等信息）：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;KERNEL==&quot;ttyUSB*&quot;, MODE=&quot;0777&quot;, GROUP=&quot;dialout&quot;
KERNEL==&quot;ttyACM*&quot;, MODE=&quot;0777&quot;, GROUP=&quot;dialout&quot;
KERNEL==&quot;ttyTHS*&quot;, MODE=&quot;0777&quot;, GROUP=&quot;dialout&quot;
KERNEL==&quot;tty*&quot;, MODE=&quot;0777&quot;, GROUP=&quot;dialout&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个可选，不一定全加，一般1-2个，看你需求加。&lt;/p&gt;
&lt;p&gt;设置文件权限&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo chmod 777 tty_All.rules
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;查看当前用户&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;whoami 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将用户加入dialout组&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo usermod -a -G dialout $USER
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;重新加载规则&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;执行完上述步骤后，&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo udevadm control --reload-rules
sudo udevadm trigger
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编辑&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo gedit ~/.bashrc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;设置开机自启动文件&lt;/p&gt;
&lt;p&gt;建立并打开一个“.sh”脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gedit ~/self.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输入 #! /bin/bash 并保存&lt;/p&gt;
&lt;p&gt;添加权限&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo chmod 777 self.sh 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用gnome-session-properties 打开启动应用程序首选项，点击添加进行添加新的启动程序，输入对应的名称，选择文件路径，添加后，每一次开机都会调用&lt;/p&gt;
&lt;p&gt;打开启动脚本添加需要的启动命令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gnome-terminal -- bash -c &quot;roslaunch xxx.launch;exec bash&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结束。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;echo 欢迎归来，我伟大的主人！您忠诚的,卑微的，谦虚的仆人阿罗德斯应您召唤而来，能追随您的步伐，是我至高无上的荣耀，您终将归于那至高的位置，让整个世界在您的注视下变得平静。
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;猫猫&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/MetaCubeX/mihomo&quot;&gt;原始 Mihomo&lt;/a&gt;,&lt;a href=&quot;https://github.com/SaladDay/clash-for-lab&quot;&gt;clash-for-lab&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/Zephyruso/zashboard&quot;&gt;zashboard&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;GUI，考虑 &lt;a href=&quot;https://github.com/LibNyanpasu/clash-nyanpasu&quot;&gt;clash-nyanpasu&lt;/a&gt;
&lt;a href=&quot;https://github.com/mihomo-party-org/clash-party&quot;&gt;mihomo-party&lt;/a&gt;,&lt;a href=&quot;https://github.com/chen08209/FlClash&quot;&gt;FlClash&lt;/a&gt;我目前用的是这两个&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;以上内容总结了部分的笔者在日常使用中经常会用到的 命令，记录下来，方便回看。
​&lt;/p&gt;
&lt;h2&gt;资料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.seeedstudio.com/NVIDIA_Jetson/&quot;&gt;NVIDIA® Jetson™ Powered Edge AI Devices Guide&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.nvidia.com/jetson/archives/r36.4.3/DeveloperGuide/index.html&quot;&gt;NVIDIA Jetson Linux Developer Guide&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.nvidia.com/en-us/autonomous-machines/embedded-systems/jetson-orin/nano-super-developer-kit/&quot;&gt;Jetson Orin Nano Super Developer Kit&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.nvidia.com/blog/nvidia-jetson-orin-nano-developer-kit-gets-a-super-boost/&quot;&gt;NVIDIA Jetson Orin Nano Developer Kit Gets a “Super” Boost&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/Seeed-Projects/reComputer-Jetson-for-Beginners/tree/main/3-Basic-Tools-and-Getting-Started/3.8-OpenCV-with-CUDA&quot;&gt;OpenCV with CUDA&lt;/a&gt;: 带cuda加速的opencv：&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/Lain-Ego0/Typst-resume&quot;&gt;模版简历-Lain-Ego0&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://harry-hhj.github.io/posts/RM-Tutorial-4-Camera/&quot;&gt;相机&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://harry-hhj.github.io/posts/Numpy-Tutorial/#%E4%B8%80python-%E5%9F%BA%E7%A1%80&quot;&gt;Numpy 的使用&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://harry-hhj.github.io/posts/RM-Tutorial-5-Monocular-Vision/&quot;&gt;单目视觉&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://harry-hhj.github.io/posts/PPT-tools/&quot;&gt;PPT 制作常用免费工具&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenCV文档yyds&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://locowiki.github.io/&quot;&gt;locowiki0&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://robotics-tutorial.dmbot.cn/&quot;&gt;机器人体系化 教学文档&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://firefly-7a0.pages.dev/posts/leetcode_notes/leetcode-index/&quot;&gt;LeetCode 笔记-孔&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://arthals.ink/blog/initialize-windows#flow-launcher&quot;&gt;从零开始配置 Windows-软件下载&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://arthals.ink/blog/initialize-linux&quot;&gt;从零开始配置 Linux &lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.zql404.top/blog/imgbed&quot;&gt; 创建图床 &lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://note.juniortree.com/&quot;&gt;JuniorTreeA Note Site&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/673155133&quot;&gt;我们的工科教育，问题出在了哪里&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="/_astro/cover.DZUBJQid.jpg"/><enclosure url="/_astro/cover.DZUBJQid.jpg"/><dc:creator>嘉心糖</dc:creator><author>嘉心糖</author></item><item><title>关于我的博客</title><link>https://jiaxin404.top/tech/about-my-blog</link><guid isPermaLink="true">https://jiaxin404.top/tech/about-my-blog</guid><description>记录我搭建个人博客的过程、技术选型和心得体会</description><pubDate>Tue, 07 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;随着时间过去，接触的东西越来越多，之前做过的技术和有的想法慢慢的陌生，每次回开始都要花时间在寻找之前做过的回忆，我有时候会用我手机的备忘录进行记录，但内容量相当有限。去年10月份刷到一个博主介绍自己静态博客的视频，能在互联网上面有自己的小空间，当然也可以分享自己的生活与感受，并且可以很方便记录与回看，在寒假的时候我就有想去弄一下，可是放假嘛，都想玩，不想去搞，但也是逼着自己了解了一些内容，把我的博客网站有了个大致规划了，于是开学后我就开始慢慢做，在课余时间就弄一下，搭建我的静态网站，打算写博客记录下来有意义的内容，。由于不想在这个方面花太多时间在前端和配置上面，更想专注于内容，本人也还没到手搓Vue的能力，但得益于网上有很多成熟的博客框架，可以非常方便的搭建和部署，感谢大佬分享。
做静态网站也是有几个麻烦，一是你需要一个域名（有特点需求还需要备案流程），还有公网IP，还有一个尽量24小时都开着的稳定性能足够的电脑等等麻烦的事情。
于是我在网上找了一些资料，看了很多大佬的博客，比如[《如何用Hexo搭建博客》]等等。我了解到可以部署在github或者其他平台上，这样就解决了域名和服务器费用，真是令人兴奋，现在就差自己把框架搭好了。&lt;/p&gt;
&lt;h3&gt;我需要什么？&lt;/h3&gt;
&lt;p&gt;我需要的是一个相当于备忘录pro或者日记，我随时可以查看，并且其他人也能看，而且我希望我不会因此花费太多钱和时间，重在内容。但是我又想可以做点美观个性东西，所以我的博客风格是简洁的，但是又有一点温柔，有些许多彩的小东西点缀，看着舒服点。&lt;/p&gt;
&lt;h3&gt;技术路线&lt;/h3&gt;
&lt;p&gt;根据前言，我为什么选择了Astro和静态网站了呢？我找了一些博客框架，分析他们之间都有什么优缺点，什么最适合我。下面是询问ai的介绍。&lt;/p&gt;
&lt;p&gt;| 类型 | 代表框架 | 渲染方式 | 速度 | 安全性 | 部署成本 | 动态功能 | 适合人群 |
|------|----------|----------|------|--------|----------|----------|----------|
| 静态站点生成器 SSG | Hugo、Hexo、Astro、Jekyll、VuePress | 预编译生成纯 HTML | 极快 | 极高（无数据库） | 极低（可免费部署） | 弱，需借助第三方 | 程序员常用、追求稳定简洁 |
| 动态 CMS | WordPress、Typecho、Ghost | 数据库 + 服务端渲染 | 一般 | 一般（需维护安全） | 较高（需服务器/域名） | 强，原生支持评论、用户 | 非技术、需要丰富插件 |
| 全栈现代框架 | Next.js、Nuxt.js、SvelteKit | 混合渲染（SSR/SSG/CSR） | 快 | 高 | 中 | 极强，可做完整应用 | 前端开发高度自定义 |&lt;/p&gt;
&lt;h4&gt;决策导向&lt;/h4&gt;
&lt;p&gt;| 框架 | 开发语言 | 构建速度 | 上手难度 | 主题生态 | 中文生态 | 扩展性 | 部署难度 | 典型特点 |
|------|----------|----------|----------|----------|----------|--------|----------|----------|
| &lt;strong&gt;Hugo&lt;/strong&gt; | Go | 极快 | 中等 | 丰富 | 一般 | 高 | 极低 | 编译快、多语言、文档友好 |
| &lt;strong&gt;Hexo&lt;/strong&gt; | Node.js | 较快 | 极低 | 极丰富 | 顶级 | 中 | 极低 | 中文教程最多、新手友好 |
| &lt;strong&gt;Astro&lt;/strong&gt; | TypeScript/JS | 快 | 中高 | 中等 | 较好 | 极高 | 低 | 现代架构、支持多框架零 JS shipped |
| &lt;strong&gt;Jekyll&lt;/strong&gt; | Ruby | 较慢 | 低 | 丰富 | 一般 | 中 | 低 | GitHubPages 支持、历史久 |
| &lt;strong&gt;VuePress&lt;/strong&gt; | Node.js + Vue | 中 | 中 | 一般 | 好 | 中 | 低 | 技术文档为主、Vue 生态 |
| &lt;strong&gt;VitePress&lt;/strong&gt; | Node.js + Vue | 快 | 中 | 较少 | 好 | 中 | 低 | Vue 官方文档工具、简洁现代 |&lt;/p&gt;
&lt;h4&gt;部署与成本&lt;/h4&gt;
&lt;p&gt;| 项目 | 现实情况 |
|------|----------|
| 服务器费用 | GitHub Pages / Cloudflare Pages / Vercel 基本为 0 |
| 域名费用 | 默认可用 username.github.io（免费）；自定义域名通常需要付费 |
| 运维成本 | 静态站点基本为 0 运维 |&lt;/p&gt;
&lt;p&gt;虽然Astro自由度高，需要自己决策可能是一个坑，但对我反而或许是一个优势了，我可以挑选我喜欢的组件，至于域名和服务器问题，一开始想的是 Cloudflare，但是既然项目要上传github，那么github pages反而相当的方便，因为我设置完后，每次只要push推送上去就能更新了，相当的方便。&lt;/p&gt;
&lt;h4&gt;开发环境&lt;/h4&gt;
&lt;p&gt;我选用的是用 &lt;strong&gt;VS Code&lt;/strong&gt; 作为我的&lt;strong&gt;文本编辑器&lt;/strong&gt;进行开发，并且已经安装了 Astro 的扩展。推荐安装的 VS Code 扩展：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Astro&lt;/strong&gt; - 官方扩展，提供语法高亮和代码补全&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Markdown All in One&lt;/strong&gt; - Markdown 编辑增强&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prettier&lt;/strong&gt; - 代码格式化&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitLens&lt;/strong&gt; - Git 功能增强&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;关于 Github Pages&lt;/h2&gt;
&lt;p&gt;首先需要提及的是 Github Pages，简单来说，这是一种 Github 提供的静态网站托管服务，至于什么是静态网站，一种简易的理解是，至少上面不会有一个数据库，网站也不存在任何对数据的读写操作，一切的风格化变化只来自于网站提前搭建好的框架。&lt;/p&gt;
&lt;p&gt;在提供静态网页这件事情上，Github 可以说是十分慷慨的，每一个用户可以创建近乎无限的仓库，而每一个仓库都可以对应一个 Github Pages。&lt;/p&gt;
&lt;h2&gt;关于 Github Actions&lt;/h2&gt;
&lt;p&gt;Github Actions 是一个持续集成和持续部署（CI/CD）平台，它允许用户在 Github 上创建自动化流程，用于构建、测试和部署项目，其支持使用 YAML 文件定义工作流程，并且可以与 Github 上的其他服务进行交互，换句话来说，Github Actions 支持我们在 Github 这个理论来说静态的平台上面运行一个脚本。&lt;/p&gt;
&lt;p&gt;通过上述的内容，细心的读者应当不难发现，Pages 提供静态服务，而 Actions 则可以运行脚本，二者的互补，因此这一流程也也就不难想象了，创建一个 &lt;code&gt;index.html&lt;/code&gt;，在其中写入一些内容，然后将这个文件部署到 Github Pages 中，便可以得到一个网站。&lt;/p&gt;
&lt;p&gt;Github Pages 与 Github Actions 可以帮助开发者构建个人网站，更重要的是，完全免费，真好。&lt;/p&gt;
&lt;h1&gt;关于Obsidian&lt;/h1&gt;
&lt;p&gt;Obsidian 是一款本地笔记软件，全部内容可以均保存在本地，保证安全性，也可以通过 Github 等方式进行备份与同步，另外Obsidian 使用 Markdown 语言进行笔记的撰写，上手难度较低。&lt;/p&gt;
&lt;p&gt;因为文章是用Markdown 编辑来写的，Obsidian 不同于专业的 Markdown 编辑器，如 Typora 等软件的最大特点便是其使用的双向链接功能，可以非常方便管理文章。
Obsidian 的安装与下载十分简单，你可以在 &lt;a href=&quot;https://obsidian.md/&quot;&gt;Obsidian 的官网 ↗︎&lt;/a&gt; 进行下载，之后按照提示进行安装即可。里面也可以安装一些插件。&lt;/p&gt;
&lt;p&gt;这样框架搭好了，网站部署好，内容用Obsidian来管理，就只剩下创作内容了。&lt;/p&gt;
&lt;h1&gt;关于我的感受&lt;/h1&gt;
&lt;p&gt;前面几天框架都弄好了，当时非常想写一篇分享我当时的感受，因为一些事情耽搁了，没有正常更新第一篇文章，现在没当时的感觉了。种一棵树最好的时间是十年前，其次是现在。总体来说，其实也不错，当一个起点吧，看看一年，两个或者几年都回看这些会不会有些伤感呢，哈哈哈。现在刚开始，更新频率可能会多些，后面尽量保持每月至少1-2个吧，写写月记感觉也不错。&lt;/p&gt;
&lt;p&gt;在我看来，记笔记是一种正反馈&lt;/p&gt;
&lt;p&gt;记笔记是我推荐的一种极佳的学习方法，我不了解读者在高中期间对于记笔记的看法，例如是否会在复习的时候回顾这些笔记，或者认为记笔记是否是一种累赘，但是在高中的生活中，我从来没有记过一行笔记，主要还是懒，嘿嘿。&lt;/p&gt;
&lt;p&gt;尽管如此，笔者依然推荐在大学期间使用记笔记作为最核心的学习方法。大学的学习以自学为主，这一点是不可置否的，而在自学的过程中，能否让自己获得持续学习的动力，这成为了自学过程中最关键的一环。&lt;/p&gt;
&lt;p&gt;有时候学习的难点并不在于如何高效的学习，反而在于更为基础的一点：如何坚持学下去。&lt;/p&gt;
&lt;p&gt;假如并非十分自律的人，在学习的过程中，毫无疑问，正反馈是十分重要的。适当的正反馈不但可以让读者有学习的动力，也可以让读者保持学习的节奏。&lt;/p&gt;
&lt;p&gt;令人遗憾的是，在大学中，正反馈往往不是如此的常见。在不经常存在考试，甚至课程质量良莠不齐的大学当下，在学习中获得的反馈是十分难得的。更加经常的情况是，你有数不尽的内容可以学习，但是每每学习结束之后你并不知道自己的学习是否为自己带来了改变，这与高中阶段学习少量的内容，但是在重复性的刷题以验证自己的学习成果不同，而一味地向前冲有时会带来迷茫，从而失去兴趣。&lt;/p&gt;
&lt;p&gt;关于笔记的用处，不排除一些人通过记笔记起到加深记忆，以及获得一手的复习资料的作用，但是在此之外，记笔记也是一种记录，如日记一般，可以告诉我们：“我今天学习了很多内容”。&lt;/p&gt;
&lt;p&gt;总之，在我看来，记笔记是十分有必要的，之前我也有在cdsn上面写了一些踩坑的分享，或者在笔记软件什么写日记心得什么的，现在可以在这里写，熟人看到会有点尴尬哈哈哈，习惯就好了，时间有限今天就这么多吧，写的也不是很好，笔者写作水平有限，有什么问题也可以留言或者私信我。&lt;/p&gt;
&lt;h2&gt;小结&lt;/h2&gt;
&lt;p&gt;以上便是本次介绍的全部内容了，到这一步你们应该了解了大致的流程：
本地写作 -&gt; &lt;code&gt;git push&lt;/code&gt; -&gt; GitHub 自动构建并发布。&lt;/p&gt;
&lt;p&gt;或许改天会开一个合集，讲一下踩过的坑。
后面就创作一些内容，然后慢慢把样式打磨成喜欢的样子就好。&lt;br&gt;
技术是骨架，内容才是灵魂。先让它跑起来，再让它变好看。&lt;/p&gt;
&lt;p&gt;现在使用 AI Code 来协助开发，工作效率大大提升。博客网站的核心目标是实现正常的发布和阅读功能，而增加其他花样功能则是个人情趣的体现 😄&lt;/p&gt;
&lt;p&gt;希望我的经验对你有所帮助！&lt;/p&gt;
&lt;hr&gt;</content:encoded><h:img src="/_astro/cover.Ce8a08Gs.jpg"/><enclosure url="/_astro/cover.Ce8a08Gs.jpg"/><dc:creator>嘉心糖</dc:creator><author>嘉心糖</author></item></channel></rss>