[{"content":"Gentoo 是一个快速、现代化的 Linux 发行版，它的设计简洁、灵活。Gentoo 围绕自由软件建立，它不会对它的用户隐瞒“引擎盖下的细节”。Gentoo 所使用的软件包维护系统 Portage 是用 Python 编写的，这意味着用户可以轻松地查看和修改它的源代码。 Gentoo 的软件包管理系统使用源代码包（虽然也支持预编译软件包），并通过标准的文本文件配置Gentoo。换句话说，开放无处不在。 \u0026mdash;\u0026mdash;- 摘抄自Gentoo Wiki\n安装的总流程 强烈建议参考Wiki\n我在这里只是做了一些简单的记录，而且可能因为版本迭代不再适用，强烈建议参考官方Wiki。\n准备好镜像和环境 互联网连接 完成分区 并挂载 chroot，安装核心部件 配置并编译内核 创建大部分Gentoo的系统配置文件 必要的系统工具安装 引导程序安装 退出livecd环境并进入新系统 添加用户配置图形界面等 下载镜像并进入LiveCD 本人是在清华大学镜像站下载的iso镜像，链接如下\n镜像传送门，选择合适的镜像下载。\n然后制作U盘启动器，推荐使用Ventoy\nU盘插入电脑选择启动项启动，选择gentoo默认内核启动\n前置准备 联网ping www.baidu.com\n有线网的话本人使用手机联网通过USB共享给LiveCD，使用dhcpcd命令 无线网的话可以使用wpa_supplicant -i \u0026lt;dev\u0026gt; -c \u0026lt;(wpa_passphrase [SSID] [passwd])\u0026gt; 硬盘分区\n分区工具建议使用fdisk，分区前建议使用lsblk查看\nUEFI or BIOS 引导分区（UEFI建议256MB及以上） swap （如果内存足够可以不要，如果内存不足，建议设置为RAMx2） root 根目录（其实本人并不建议为home目录单独分一个分区，因为我觉得对于ssd来说没必要，当然也可以创建一个共用区ntf给Windows） 创建文件系统（以sda为例）\nUEFI分区mkfs.vfat -F 32 /dev/sda1 swap分区：初始化mkswap /dev/sda2，激活swapon /dev/sda2 root分区mkfs.ext4 /dev/sda3 开始安装 挂载\n1 2 3 4 5 mkdir --parents /mnt/gentoo mount /dev/sda3 /mnt/gentoo # 如果/tmp/需要放在一个单独的分区，请确保其在挂载后有对应的权限 chmod 1777 /mnt/gentoo/tmp 安装Gentoo安装文件\n确保时间正确date，从互联网下载stage3归档文件，建议使用links https://mirrors.tuna.tsinghua.edu.cn/gentoo/命令行浏览器进入tui界面进行下载，或者也可以用其他的电脑下载到硬盘后直接挂载导入到当前地址\n下载完成之后将下载的归档文件解压到新系统根目录\n1 tar xpvf stage3-amd64-desktop-openrc-20230521T160357Z.tar.xz --xattrs-include=\u0026#39;*.*\u0026#39; --numeric-owner 然后配置编译选项\nnano -w /mnt/gentoo/etc/portage/make.conf\n本人的make.conf文件如下所示：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 # These settings were set by the catalyst build script that automatically # built this stage. # Please consult /usr/share/portage/config/make.conf.example for a more # detailed example. COMMON_FLAGS=\u0026#34;-march=native -O2 -pipe\u0026#34; CFLAGS=\u0026#34;${COMMON_FLAGS}\u0026#34; CXXFLAGS=\u0026#34;${COMMON_FLAGS}\u0026#34; FCFLAGS=\u0026#34;${COMMON_FLAGS}\u0026#34; FFLAGS=\u0026#34;${COMMON_FLAGS}\u0026#34; # NOTE: This stage was built with the bindist Use flag enabled # This sets the language of build output to English. # Please keep this setting intact when reporting bugs. LC_MESSAGES=C.utf8 MAKEOPTS=\u0026#34;-j4\u0026#34; ACCEPT_LICENSE=\u0026#34;*\u0026#34; ACCEPT_KEYWORDS=\u0026#34;amd64\u0026#34; GENTOO_MIRRORS=\u0026#34;https://mirrors.aliyun.com/gentoo/ http://mirrors.aliyun.com/gentoo/ https://mirrors.tuna.tsinghua.edu.cn/gentoo\u0026#34; GRUB_PLATFORMS=\u0026#34;efi-64\u0026#34; # USE USE=\u0026#34;networkmanager savedconfig X elogind alsa icu zsh-completion -systemd\u0026#34; # Xorg # support for touchpad, mouse, and keyboard INPUT_DEVICES=\u0026#34;synaptics libinput\u0026#34; 配置并Chroot进入新系统环境\n选择镜像mirroselect -i -o \u0026gt;\u0026gt; /mnt/gentoo/etc/portage/make.conf，\n配置软件仓库mkdir --parents /mnt/gentoo/etc/portage/repos.conf，\n复制Portage提供的Gentoo仓库配置文件到新创建的目录cp /mnt/gentoo/usr/share/portage/config/repos.conf /mnt/gentoo/etc/portage/repos.conf/gentoo.conf\n复制DNS信息，不然进新环境后会没有网络cp --dereference /etc/resolv.conf /mnt/gentoo/etc/\n挂载必要的文件系统\n1 2 3 4 5 6 7 mount --types proc /proc /mnt/gentoo/proc mount --rbind /sys /mnt/gentoo/sys mount --make-rslave /mnt/gentoo/sys mount --rbind /dev /mnt/gentoo/dev mount --make-rslave /mnt/gentoo/dev mount --bind /run /mnt/gentoo/run mount --make-slave /mnt/gentoo/run #----# 为systemd提供的 Chroot\n1 2 3 4 5 6 chroot /mnt/gentoo /bin/bash source /etc/profile export PS1=\u0026#34;(chroot) ${PS1}\u0026#34; # 挂载boot分区 mount /dev/sda1 /boot 配置Portage\n从网站安装ebuild数据库快照emerge-webrsync 更新ebuild数据库emerge --sync 选择正确的配置文件 1 2 3 4 5 6 7 8 9 10 11 eselect profile list Available profile symlink targets: [1] default/linux/amd64/17.1 (stable) [2] default/linux/amd64/17.1/selinux (stable) [3] default/linux/amd64/17.1/hardened (stable) [4] default/linux/amd64/17.1/hardened/selinux (stable) [5] default/linux/amd64/17.1/desktop (stable) * [6] default/linux/amd64/17.1/desktop/gnome (stable) [7] default/linux/amd64/17.1/desktop/gnome/systemd (stable) [8] default/linux/amd64/17.1/desktop/gnome/systemd/merged-usr (stable) ...... eselect profile set 5\n更新@world集合emerge --ask --verbose --update --deep --newuse @world 或者 emerge -avuDN @world 配置USE变量 见我上面展示的make.conf 时区、区域配置（OpenRC）\n选择系统时区echo \u0026quot;Asia/Shanghai\u0026quot; \u0026gt; /etc/timezone，emerge --config sys-libs/timezone-data 区域配置nano -w /etc/locale.gen 启用en_US.UTF-8 UTF-8和zh_CN.UTF-8 UTF-8，然后执行locale-gen 选择区域eselect locale list我选的是en_US.utf8 重新加载环境env-update \u0026amp;\u0026amp; source /etc/profile \u0026amp;\u0026amp; export PS1=\u0026quot;(chroot) ${PS1}\u0026quot; 安装微码和固件\n固件 emerge --ask sys-kernel/linux-firmware\n微码 emerge --ask sys-firmware/intel-microcode\n其中，AMD处理器的微码已经在linux-firmware固件里面包含了\n编译内核（冲！！）\n我使用的是Genkernel\n接受系统范围任意软件的许可证，也可以在/etc/portage/make.conf中设置。 mkdir /etc/portage/package.license\n安装genkernel包 emerge --ask sys-kernel/genkernel\n编译内核源码并安装 genkernel --mountboot --install all\n第一次可能比较慢，当然如果你的电脑性能好，也可能会编译的很快，Howerver，等到编译浏览器的时候，你会发现是处理器性能和内存大小对这影响还是很大的 (确信) 。\n配置系统并完成安装 fstab\n建议使用UUID， blkid命令可以查看UUID 内附我的fstab文件\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 # \u0026lt;fs\u0026gt; \u0026lt;mountpoint\u0026gt; \u0026lt;type\u0026gt; \u0026lt;opts\u0026gt; \u0026lt;dump/pass\u0026gt; # NOTE: If your BOOT partition is ReiserFS, add the notail option to opts. # # NOTE: Even though we list ext4 as the type here, it will work with ext2/ext3 # filesystems. This just tells the kernel to use the ext4 driver. # # NOTE: You can use full paths to devices like /dev/sda3, but it is often # more reliable to use filesystem labels or UUIDs. See your filesystem # documentation for details on setting a label. To obtain the UUID, use # the blkid(8) command. #LABEL=boot /boot ext4 noauto,noatime 1 2 #UUID=58e72203-57d1-4497-81ad-97655bd56494 / ext4 noatime 0 1 #LABEL=swap none swap sw 0 0 #/dev/cdrom /mnt/cdrom auto noauto,ro 0 0 # /dev/sda1 UUID=\u0026#34;80AE-02C2\u0026#34; /boot vfat defaults,noatime 0 2 # /dev/sda2 UUID=\u0026#34;711c9bab-9f43-4ebf-8817-1712928b6a89\u0026#34; / ext4 defaults,noatime,discard 0 1 # /dev/sda3 UUID=\u0026#34;6676917276914429\u0026#34; /home/haoleng/Public ntfs defaults,noatime,discard 0 1 网络配置\nroot密码passwd\nOpenRC\n查看/etc/rc.conf的注释并根据需求修改\n安装系统日志工具emerge --ask app-admin/sysklogd，更新应用配置rc-update add sysklogd default\n时间同步emerge --ask net-misc/chrony，运行rc-update add chronyd default\n网络工具NetworkManager\n引导程序GRUB（UEFI）\n安装Grub echo 'GRUB_PLATFORMS=\u0026quot;efi-64\u0026quot;' \u0026gt;\u0026gt; /etc/portage/make.conf， emerge --ask sys-boot/grub\n激活Grub grub-install --target=x86_64-efi --efi-directory=/boot --removable\n生成Grub配置文件\n1 2 3 4 5 6 7 8 9 10 11 sudo grub-mkconfig -o /boot/grub/grub.cfg 密码： 正在生成 grub 配置文件 ... 找到主题：/usr/share/grub/themes/Cyberpunk/theme.txt 找到 Linux 镜像：/boot/vmlinuz-6.1.28-gentoo-x86_64 找到 initrd 镜像：/boot/initramfs-6.1.28-gentoo-x86_64.img 警告： os-prober will not be executed to detect other bootable partitions. Systems on them will not be added to the GRUB boot configuration. Check GRUB_DISABLE_OS_PROBER documentation entry. Adding boot menu entry for UEFI Firmware Settings ... 完成 然后就可以重启进入新系统了。\n重新启动正常将进入系统之后，就要自己创建一个日常使用的用户了，然后记得安装sudo 并且修改用户权限。 恭喜你，完成了Gentoo Linux 的安装工作，然后就要开始进行个人的配置工作了。下面是一些我自己的随手小记，想要了解的话可以继续阅读。\n安装后相关问题记录 包管理器 Portage是用Python和Bash两种语言编写的，Portage软件系统是Gentoo最显著的特色之一。\nGentoo是滚动发行版，需要将软件仓库与上游同步:\nsudo emaint --auto sync\n然后再更新整个系统:\nsudo emerge --ask --verbose --update --deep --newuse @world\n内核的更新可以参考\u0026ndash;\u0026gt;更新内核\n获取新的内核源码（一般会在更新系统软件包的时候自行下载完成）\n当然您也可以单独下载\nsudo emerge --ask sys-kernel/gentoo-sources\n使用eselect设置一个符号链接到新的内核源码\n1 2 3 4 ❯ sudo eselect kernel list Available kernel symlink targets: [1] linux-6.1.31-gentoo * ❯ sudo eselect kernel set 1 当然也可以手动链接\nln -sf /usr/src/linux-6.1.31-gentoo /usr/src/linux\n移动到新内核文件夹并配置\n移动到新内核文件夹cd /usr/src/linux\n配置新内核make menuconfig，或者直接使用旧内核的配置make olddefconfig，该命令会保持所有旧的.config的选项并将旧内核没有包括的新的内核选项设置为默认值\n然后编译内核，本人比较菜，采用的是自动编译的工具genkernel\nGenkernel自动构建和安装\ngenkernel all可以自动构建和安装内核到/usr/src/linux符号链接指向的$BOOTDIR和引导程序\n如果还有外部内核模块如nvidia等需要再重新编译一次\nsudo make modules_prepare sudo emerge --ask @module-rebuid\n最后将内核更新到引导程序\nsudo grub-mkconfig -o /boot/grub/grub.cfg\n安装和卸载软件（以mpv为例） 安装mpv播放器\nsudo emerge --ask media-video/mpv\n该过程会从软件源下载源代码并默认存在/var/cache/distfiles/中，然后解压缩、编译和安装该软件包。如果仅下载源代码而不安装，可以添加--fetchonly到emerge命令\n卸载mpv播放器\nsudo emerge --deselect media-video/mpv\n该命令告诉Protage这个软件包现在不需要了，可以通过--depclean清理掉 注意emerge --depclean是一项危险的操作，我们可以添加一个-p选项来只列出这些包而不删除他们，示例如下：\nsudo emerge -p --depclean\ntty自动登录 我用的init是OpenRC，在安装系统时已经附带安装了sysvinit 因此直接编辑/etc/inittab文件修改为如下样式：\n1 2 3 4 5 6 7 # TERMINALS c1:12345:respawn:/sbin/agetty --autologin \u0026lt;username\u0026gt; --noclear 38400 tty1 linux c2:2345:respawn:/sbin/agetty 38400 tty2 linux c3:2345:respawn:/sbin/agetty 38400 tty3 linux c4:2345:respawn:/sbin/agetty 38400 tty4 linux c5:2345:respawn:/sbin/agetty 38400 tty5 linux c6:2345:respawn:/sbin/agetty 38400 tty6 linux 双系统时间同步问题 sudo nvim /etc/conf.d/hwclock对应项修改为clock=\u0026quot;local\u0026quot;\n禁用 nouveau 并安装 Nvidia 驱动 编辑配置文件 /etc/modprobe.d/blacklist.conf 以禁用nouveau驱动\n1 2 3 blacklist nouveau blacklist lbm-nouveau options nouveau modeset=0 安装nvidia驱动\nsudo emerge --ask x11-drivers/nvidia-drivers\n图形界面 附上我的xorg环境下的dwm配置和源码，配置简单，基本上clone下来之后make clean install就可以使用。\nFirefox浏览器下指定网站禁用插件 原理是Firefx为了保护用户隐私，有些官方的域名是强制不开启扩展的，因此我们可以在这些域名后加或者删除域名\n具体操作如下：\n首先在地址栏输入about:config然后无视风险继续浏览，进入之后在搜索框输入extensions.webextensions.restrictedDomains，在里面添加指定域名即可，注意使用,隔开。\nLinux下一些实用的软件介绍 Android设备投屏软件scrcpy 该软件在gentoo的软件源里有，如果您使用的是ACCEPT_KEYWORDS=\u0026quot;amd64\u0026quot;或者我需要把软件包加入accept_keywords中，操作如下\n编辑/etc/portage/package.accept_keywords：\n1 2 # Always use unstable packages app-mobilephone/scrcpy ~amd64 或者将其ACCEPT_KEYWORDS=\u0026quot;amd64\u0026quot;改为\u0026quot;~amd64\u0026quot;，个人不建议这样\n然后就可以安装软件包了， 但是安装之后有个坑，就是adb无法连接usb手机\n会报错如下：\n1 adb no permissions(user xxx is not in the plugdev group);.... 我们需要将用户加入plugdev组中，\n1 root #usermod -aG plugdev $LOGNAME 然后还是会报错，我们还需要编辑android规则文件\nsudo nvim /etc/udev/rules.d/android.rules\n内容如下：\n1 SUBSYSTEM==\u0026#34;usb\u0026#34;, ENV{DEVTYPE}==\u0026#34;usb_device\u0026#34;, MODE=\u0026#34;0666\u0026#34; 然后就可以顺利使用使用scrcpy了。\nadb tcpip 5037 adb connect 192.168.0.4:5037 scrcpy -S\nscrcpy的一些快捷键(Mod默认为ALT键)\n快捷键 运行命令 Mod + f 切换全屏 Mod + g 重置屏幕尺寸到像素比1：1 Mod + w 重置屏幕尺寸消除黑边 Mod + p 相当于电源按键 Mod + o 关闭屏幕 Mod + Shift + o 点亮关闭的屏幕 Mod + c 同步复制到电脑剪切板 Mod + v 同步粘贴剪切板的内容 Mod + i 启用或者禁用FPS监视器 ","date":"2023-05-21T21:38:04+08:00","image":"https://haoleng-wick.github.io/p/gentoo%E9%9A%8F%E6%89%8B%E8%AE%B0/gentoo_hu15065158252994307299.png","permalink":"https://haoleng-wick.github.io/p/gentoo%E9%9A%8F%E6%89%8B%E8%AE%B0/","title":"Gentoo随手记"},{"content":"毕业 时间过得好快啊，不知不觉已经毕业了3个多月了，回顾这三年，但也算是丰富多彩吧。有用的知识也没学多少，没用的知识也没少学，能摆绝对没卷，反正稀里糊涂就过了三年，秋招也算找了个工作吧，毕业后和兄弟们也联系少了，大伙们都上班挺忙的……\n上班 工作之后才知道原来时间是真的不够用，搞得我现在都电子阳痿了，明明有很多想玩的游戏，可是下班回来之后就硬是不想打开。而且工作搞得东西和学校学的东西也有很大出入，所以还需要学习很多东西，现在先学C语言和计算机网络协议，另外我本人出于兴趣还想学些Rust语言相关的知识。现在我的情况就是，上班时间就完成领导安排的工作，闲暇之余学点东西，有时候无聊了打两把游戏（两把之后就突然间不想玩了，这或许就是电子阳痿吧），下周还要出差，估计会更累，先这么着吧。\n希望 多学点东西肯定没错的，估计以后很长一段时间都在搞目前搞的这些设备开发，所以，希望大家都能越来越好吧！\n也不知道自己写了些什么东西，反正也没人看。。\n","date":"2024-10-24T10:24:10+08:00","image":"https://haoleng-wick.github.io/p/%E5%8D%81%E6%9C%88%E5%B0%8F%E8%AE%B0/1024_hu6820670817370559309.jpg","permalink":"https://haoleng-wick.github.io/p/%E5%8D%81%E6%9C%88%E5%B0%8F%E8%AE%B0/","title":"十月小记"},{"content":"汉明码简介 汉明码（也叫海明码），常用于数据传输中的校验编码。汉明码具有一位纠错能力，其主要特点如下：\n数据分组校验\n校验位在$2^x$位\n校验位个数和值的确定\n校验码的解码和纠正\n说白了就是每个校验位负责一组数据的奇偶校验，然后整个数据分成了好几组，最后将各组的校验整合得到整个数据中出现错误的那一位。\n编码 假设数据有n位，其中校验码有x位，那么编码后的数据应该有n+x位。x位校验码一共有$2^x$种取值，其中一种取值表示数据正确，则剩下的$((2^x) - 1)$种取值表示有一位数据出错。不难推出如下不等式：\n$$ ((2^x) - 1) \u0026gt;= (n+x) $$\n使得不等式成立的x的最小值就是汉明码的校验位数。\n例如0101 0011数据有8位，则代入不等式可以得到“$((2^x) - 1) \u0026gt;= (8+x)$”，所以x=4。\n原始数据0101 0011经过编码之后变成了12位数据，其中第$2^k$位是校验位，其余位为数据位，如下所示：\n1 2 3 4 5 6 7 8 9 10 11 12 P1 P2 D1 P4 D2 D3 D4 P8 D5 D6 D7 D8 然后对他们进行分组，首先观察其位置编码： 位置 二进制表示 对应数据 对应取值 1 0001 P1 待定 2 0010 P2 待定 3 0011 D1 0 4 0100 P4 待定 5 0101 D2 1 6 0110 D3 0 7 0111 D4 1 8 1000 P8 待定 9 1001 D5 0 10 1010 D6 0 11 1011 D7 1 12 1100 D8 1 将最低位是1的数据分为第一组：P1, D1, D2, D4, D5, D7\n将第二低位是1的数据分为第二组：P2, D1, D3, D4, D6, D7\n将最三低位是1的数据分为第三组：P4, D2, D3, D4, D8\n将最高位是1的数据分为第四组：P8, D5, D6, D7, D8\n再根据每个分组的数据位和偶校验的规则计算校验位具体数值\n1 2 3 4 5 P1 = D1 ^ D2 ^ D4 ^ D5 ^ D7 // 0 ^ 1 ^ 1 ^ 0 ^ 1 P2 = D1 ^ D3 ^ D4 ^ D6 ^ D7 // 0 ^ 0 ^ 1 ^ 0 ^ 1 P3 = D2 ^ D3 ^ D4 ^ D8 // 1 ^ 0 ^ 1 ^ 1 P4 = D5 ^ D6 ^ D7 ^ D8 // 0 ^ 0 ^ 1 ^ 1 // 计算得到：P1 = 1, P2 = 0, P3 = 1, P4 = 0 原始数据0101 0011经过编码变成1001 1010 0011 解码 汉明码只能对一位数据进行检测和纠正（但是在计算机网络传输过程中，绝大多数情况下都是仅发生一位数据错误，因此汉明码校验仍然在广泛使用），假设收到的数据为1001 1010 0010，此时接收方并不知道数据是否有效，需要对该数据进行汉明码校验，具体如下：\n首先对校验码进行校验计算\n1 2 3 4 5 P1_Check = P1 ^ D1 ^ D2 ^ D4 ^ D5 ^ D7 // 1 ^ 0 ^ 1 ^ 1 ^ 0 ^ 1 P2_Check = P2 ^ D1 ^ D3 ^ D4 ^ D6 ^ D7 // 0 ^ 0 ^ 0 ^ 1 ^ 0 ^ 1 P3_Check = P3 ^ D2 ^ D3 ^ D4 ^ D8 // 1 ^ 1 ^ 0 ^ 1 ^ 0 P4_Check = P4 ^ D5 ^ D6 ^ D7 ^ D8 // 0 ^ 0 ^ 0 ^ 1 ^ 0 // 计算得到：P1_Check = 0, P2_Check = 0, P3_Check = 1, P4_Check = 1 按照对应高低位将校验计算的结果进行排列，得到P4P3P2P1 = 1100，\n该数据表示P4和P3校验位检测到数据出错，而P1和P2校验位检测到数据正常（即D2, D3, D4 D8和D5, D6, D7, D8中各有一个数据出现错误，而D1, D2, D4, D5和D1, D3, D4, D6, D7均正常）\n所以可以推测出出现错误的是D8表示的数据，即第12位数据位出错，0b1100对应十进制正好为12，只需要将对应位取反即可得到正确数据。\n","date":"2024-08-04T14:02:04+08:00","image":"https://haoleng-wick.github.io/p/%E6%B1%89%E6%98%8E%E7%A0%81%E6%A0%A1%E9%AA%8C/HanMing_hu1783845554367520671.png","permalink":"https://haoleng-wick.github.io/p/%E6%B1%89%E6%98%8E%E7%A0%81%E6%A0%A1%E9%AA%8C/","title":"汉明码校验"},{"content":"常见的C99头文件 stdio.h : 标准IO接口 printf(), scanf()\nstdlib.h : 通用工具 malloc(), free(), atoi()\nstdarg.h : 可变参数处理 va_list\nstddef.h : 常用类型以及宏的定义 size_t, NULL\nstring.h/strings.h : 字符串操作函数 strcat(), strcasecmp()\nctype.h : 字符操作函数或宏 toupper()\ntime.h : 时间和时区操作函数 time()\nstdint.h : 整数类型及各整数类型的极值\nstdbool.h : true、false等bool类型\nmath.h : 数学函数\n函数 含义 abs(x) 返回x的绝对值 sin(x) 返回x的正弦值（弧度制） cos(x) 返回x的余弦值 tan(x) 返回x的正切值 exp(x) 返回Ex的值 asin(x) 返回x的反正弦值 acos(x) 返回x的反余弦值 atan(x) 返回x的反正切值 cbrt(x) 返回x的立方根 常见C语言预处理器指令 #include : 包含一个源代码文件\n#define : 定义宏\n#undef : 取消已定义的宏\n#ifdef : 如果宏已经定义，则返回真\n#ifndef : 如果宏没有定义，则返回真\n#if : 如果给定条件为真，则编译下面的代码\n#else : #if 的代替方案\n#elif : 如果前面的 #if 给定条件不为真，当前条件为真，则编译下面代码\n#endif : 结束一个 #if……#else 条件编译块\n#error : 当遇到标准错误时，输出错误消息\n#pragma : 使用标准化方法，向编译器发布特殊的命令到编译器中\n位运算符 运算符 描述 实例 \u0026amp; 按位“与” | 按位“或” ^ “异或” ~ 按位“取反” \u0026lt;\u0026lt; 二进制“左移” \u0026gt;\u0026gt; 二进制“右移” 数据类型 数据类型 大小Size char 1字节 int 2~4字节 short int 2字节 long int 4字节 float 4字节 double 8字节 long double 10字节 C中的枚举Enum 简单的示例\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include \u0026lt;stdio.h\u0026gt; int main(int argc, char *argv[]) { enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun } day; scanf(\u0026#34;%d\u0026#34;, \u0026amp;day); switch(day){ case Mon: puts(\u0026#34;Monday\u0026#34;); break; case Tues: puts(\u0026#34;Tuesday\u0026#34;); break; case Wed: puts(\u0026#34;Wednesday\u0026#34;); break; case Thurs: puts(\u0026#34;Thursday\u0026#34;); break; case Fri: puts(\u0026#34;Friday\u0026#34;); break; case Sat: puts(\u0026#34;Saturday\u0026#34;); break; case Sun: puts(\u0026#34;Sunday\u0026#34;); break; default: puts(\u0026#34;Error!\u0026#34;); } return 0; } 指针、可怕的指针 指针是一个变量，它存储另一个变量的内存地址作为其值。\n函数指针（用来存放函数的地址） 指针函数（返回值是指针的函数） 例如strcpy(char *str1, const char *str2)返回str1\n另外需要注意的杂项 使用const修饰的只读变量不具有“常量表达式”属性，因此无法用来表示定长数组的大小，或者在case语句中使用。“常量表达式”本身会在程序编译时被求值，而“只读变量”只能在程序运行时才被得知。\n变量存储类型：\nauto默认； register声明告诉编译器，它所声明的变量在程序中使用频率较高，因此可以将register变量放在机器的寄存器中，使程序更小、执行速度更快，（但是编译器可以忽略此选项）； static extern *p++问题\n若有定义int i =2, a[10], *p=\u0026amp;a[i];，则*p++等价于a[i++]。\n++的优先级比*高，但是++是后缀，先使用值再加加，所以是加之前的值去解引用。\n待续未完……此外，如有侵权，深感歉意，烦请邮箱联系。\n","date":"2024-07-09T21:01:04+08:00","image":"https://haoleng-wick.github.io/p/c%E8%AF%AD%E8%A8%80%E6%9D%82%E8%AE%B0/CLanguage_hu13100799662827422430.jpg","permalink":"https://haoleng-wick.github.io/p/c%E8%AF%AD%E8%A8%80%E6%9D%82%E8%AE%B0/","title":"C语言杂记"},{"content":"Gentoo On RaspberryPi 该部分主要参考Gentoo Wiki，部分内容仅做简要描述。\nSD分区 对SD进行分区 1 fdisk /dev/mmcblk0 创建DOS标签\n创建启动分区\n创建交换分区\n创建跟分区\n设置文件系统\n最终检查\n1 2 3 4 5 6 7 8 9 10 11 12 13 Command (m for help): p Disk /dev/sda: 59.48 GiB, 63864569856 bytes, 124735488 sectors Disk model: Storage Device Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: dos Disk identifier: 0x4fe0c75b Device Boot Start End Sectors Size Id Type /dev/mmcblk0p1 2048 526335 524288 256M b W95 FAT32 /dev/mmcblk0p2 526336 17303551 16777216 8G 82 Linux swap / Solaris /dev/mmcblk0p3 17303552 124735487 107431936 51.2G 83 Linux 写入并格式化 1 2 3 mkfs -t vfat /dev/mmcblk0p1 mkswap --pagesize 16384 /dev/mmcblk0p2 mkfs -t ext4 /dev/mmcblk0p3 Gentoo基本系统 1. 挂载跟分区 1 2 mount /dev/mmcblk0p3 /mnt/gentoo cd /mnt/gentoo/ 2. 安装基本系统和portage快照 下载最新的stage文件，解压到跟分区\n1 tar xpf /path/to/stage3-arm64-desktop-openrc-20240317T232028Z.tar.xz --xattrs-include=\u0026#39;*.*\u0026#39; --numeric-owner 如果一切顺利，那么/mnt/gentoo/目录下应该是如下的文件系统层次结构\n1 2 3 Johnson /mnt/gentoo # ls bin dev home lib64 media opt root sbin tmp var boot etc lib lost+found mnt proc run sys usr 下载最新的portage快照，解压到软件仓库目录\n1 2 mkdir -p /mnt/gentoo/var/db/repos/gentoo tar xpf /path/to/portage-latest.tar.bz2 --strip-components=1 -C /mnt/gentoo/var/db/repos/gentoo 3. 安装内核、模块和固件 克隆树莓派官方固件存储仓库git clone --depth=1 https://github.com/raspberrypi/firmware.git\n将固件放入boot\n1 2 3 4 5 6 7 8 9 10 mount /dev/mmcblk0p1 /mnt/gentoo/boot cp /path/to/firmware/boot/bcm2712-rpi-5-b.dtb /mnt/gentoo/boot/ cp /path/to/firmware/boot/fixup_cd.dat /mnt/gentoo/boot/ cp /path/to/firmware/boot/fixup.dat /mnt/gentoo/boot/ cp /path/to/firmware/boot/start_cd.elf /mnt/gentoo/boot/ cp /path/to/firmware/boot/start.elf /mnt/gentoo/boot/ cp /path/to/firmware/boot/bootcode.bin /mnt/gentoo/boot/ cp /path/to/firmware/boot/kernel8.img /mnt/gentoo/boot/ cp -r /path/to/firmware/boot/overlays/ /mnt/gentoo/boot/ 另一种方法是利用交叉编译的方法从源码开始裁剪编译内核，详见树莓派内核指南\n引导配置文件(编写/mnt/gentoo/boot/cmdline.txt 和 /mnt/gentoo/boot/config.txt)\n4. 安装无线网络Module 从github上将二进制固件复制到/mnt/gentoo/lib/firmware/brcm/目录中并作软连接\n克隆固件库git clone --depth=1 https://github.com/RPi-Distro/firmware-nonfree.git\n然后创建存放固件的目录mkdir -p /mnt/gentoo/lib/firmware/brcm\n树莓派5的wifi模式是brcmfmc43455，所以我们只需要复制brcmfmc43455的文件即可。\n1 2 3 cp /path/to/raspberrypi/firmware-nonfree/debian/config/brcm80211/cypress/cyfmac43455-sdio-standard.bin /mnt/gentoo/lib/firmware/brcm/brcmfmac43455-sdio.bin root #cp /path/to/raspberrypi/firmware-nonfree/debian/config/brcm80211/cypress/cyfmac43455-sdio.clm_blob /mnt/gentoo/lib/firmware/brcm/brcmfmac43455-sdio.clm_blob cp /path/to/raspberrypi/firmware-nonfree/debian/config/brcm80211/brcm/brcmfmac43455-sdio.txt /mnt/gentoo/lib/firmware/brcm/ 然后再对他们做软连接，最后连接完的目录如下：\n1 2 3 4 5 6 7 8 root #ls -l /mnt/gentoo/lib/firmware/brcm/ -rw-r--r-- 1 root root 643651 Jan 21 12:20 brcmfmac43455-sdio.bin -rw-r--r-- 1 root root 2676 Jan 21 12:18 brcmfmac43455-sdio.clm_blob lrwxrwxrwx 1 root root 22 Jan 21 12:23 brcmfmac43455-sdio.raspberrypi,5-model-b.bin -\u0026gt; brcmfmac43455-sdio.bin lrwxrwxrwx 1 root root 27 Jan 21 12:23 brcmfmac43455-sdio.raspberrypi,5-model-b.clm_blob -\u0026gt; brcmfmac43455-sdio.clm_blob lrwxrwxrwx 1 root root 22 Jan 21 12:24 brcmfmac43455-sdio.raspberrypi,5-model-b.txt -\u0026gt; brcmfmac43455-sdio.txt -rw-r--r-- 1 root root 2074 年 1 月 21 日 12:19 brcmfmac43455-sdio.txt 5. 安装蓝牙Module 克隆固件库git clone --depth=1 https://github.com/RPi-Distro/bluez-firmware.git\n树莓派5的蓝牙只需要BCM4345C0.hcd，把他复制到目录然后做软连接。\n1 2 3 cp /home/darthjoker/raspberrypi/bluez-firmware/debian/firmware/broadcom/BCM4345C0.hcd /mnt/gentoo/lib/firmware/brcm/ ln -s /mnt/gentoo/lib/firmware/brcm/BCM4345C0.hcd /mnt/gentoo/lib/firmware/brcm/BCM4345C0.raspberrypi,5-model-b.hcd 6. 编辑系统挂载表文件 /mnt/gentoo/etc/fstab\n1 2 3 /dev/mmcblk0p1 /boot vfat noatime,noauto,nodev,nosuid,noexec\t1 2 /dev/mmcblk0p2 swap swap defaults 0 0 /dev/mmcblk0p3 / ext4 noatime 0 0 至此，基本上所有的固件都装完了。如果您迫不及待的话，现在就可以卸载SD卡然后在RaspberryPi 5上启动自己的Gentoo了，但是您知道的，Gentoo这个系统需要配置的地方特别多，接下来请耐心看完。\n7. 启动前的设置 如果你有以太网的话，直接就可以写在SD卡并启动树莓派，通过有线网络完成基本的配置了。\n如果没有，那就只能先设置好无线网络了，先将Networkmanager软件所需要的文件下载到软件缓存库/var/cache/distfiles，然后启动树莓派再本地安装Networkmanager。\n在登录树莓派之前，我们需要更改root用户的密码。直接简单粗暴的将/mnt/gentoo/etc/shadow中root这一行改为如下\n1 root:::::::: 如此一来，首次登录时直接使用root用户登录，不需要密码就可以直接登录，进入系统之后再passwd自己设置密码。\n启动之后的配置 刚启动的树莓派系统时间肯定是对不上的，建议使用date -s \u0026quot;2024-03-20 16:13\u0026quot;手动设置一下，不然portage密钥对不上，根本安装不了软件包。\n另外注意国内环境下可能需要换源，建议参考清华大学开源镜像站\n该部分涉及到了/etc/portage/make.conf文件，以下是我的树莓派5的make.conf文件，可以参考一下。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 $ sudo cat /etc/portage/make.conf # These settings were set by the catalyst build script that automatically # built this stage. # Please consult /usr/share/portage/config/make.conf.example for a more # detailed example. COMMON_FLAGS=\u0026#34;-mcpu=cortex-a76+crc+crypto -mtune=cortex-a76 -O2 -pipe\u0026#34; CFLAGS=\u0026#34;${COMMON_FLAGS}\u0026#34; CXXFLAGS=\u0026#34;${COMMON_FLAGS}\u0026#34; FCFLAGS=\u0026#34;${COMMON_FLAGS}\u0026#34; FFLAGS=\u0026#34;${COMMON_FLAGS}\u0026#34; # This sets the language of build output to English. # Please keep this setting intact when reporting bugs. LC_MESSAGES=C.utf8 # WARNING: Changing your CHOST is not something that should be done lightly. # Please consult https://wiki.gentoo.org/wiki/Changing_the_CHOST_variable before changing. CHOST=\u0026#34;aarch64-unknown-linux-gnu\u0026#34; GENTOO_MIRRORS=\u0026#34;https://mirrors.tuna.tsinghua.edu.cn/gentoo\u0026#34; # NOTE: This stage was built with the bindist Use flag enabled USE=\u0026#34;X zsh-completion elogind -systemd\u0026#34; VIDEO_CARDS=\u0026#34;fbdev vc4 v3d\u0026#34; 1. iwd 对比Networkmanager，我个人更喜欢iwd，因此直接emerge安装iwd进行配置。\n1 2 3 emerge --ask iwd rc-update add iwd default rc-update add dhcpcd default sshd\n1 rc-update add sshd default 注意使用kitty进行ssh时，要先kitty +kitten ssh root@192.168.8.88，不然会出现难以忍受的问题(试试就知道了)\n2. 时区与时间同步 Openrc中时区的设置是通过/etc/timezone文件设置的\n以中国为例，时区的设置如下：\n1 2 3 echo \u0026#34;Asia/Shanghai\u0026#34; \u0026gt; /etc/timezone emerge --config sys-libs/timezone-data 时间自动更新可以使用Chrony\n3. add user 1 2 3 useradd -m -G users,wheel,video -s /bin/bash haoleng passwd haoleng 4. 安装sudo 安装sudo emerge --ask sudo，然后编辑文件/etc/sudoers将# %wheel ALL=(ALL:ALL) ALL的注释取消掉。就可以在普通用户环境下使用sudo提权了。\n5. zsh 个人比较喜欢用zsh，可以通过emerge --ask zsh app-shells/gentoo-zsh-completions安装，下面是我在gentoo系统下的.zshrc文件：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 # history HISTSIZE=1000 SAVEHIST=1000 HISTFILE=~/.histfile setopt INC_APPEND_HISTORY setopt HIST_IGNORE_DUPS setopt AUTO_PUSHD setopt PUSHD_IGNORE_DUPS setopt HIST_IGNORE_SPACE setopt SHARE_HISTORY # gentoo-zsh-completions autoload -U compinit promptinit compinit promptinit; prompt gentoo # alias alias ra=\u0026#39;yazi\u0026#39; alias ne=\u0026#39;neofetch\u0026#39; # plugins eval \u0026#34;$(starship init zsh)\u0026#34; source /usr/share/zsh-autosuggestions/zsh-autosuggestions.zsh # ssh if [ -n \u0026#34;$SSH_CONNECTION\u0026#34; ]; then echo \u0026#34; \u0026#34; echo $( shuf ~/.local/share/dwm/script/messages.txt -n 1 | awk \u0026#39;{ print(\u0026#34;󰕃 \u0026#34; $0) }\u0026#39; ) fi 记得安装git sudo emerge --ask dev-vcs/git，然后将zsh-autosuggestions插件clone到/usr/share/目录下。\n另外推荐俩工具starship和yazi，用过都说好👍！\n6. 安装桌面环境 Xorg桌面环境 安装dwm窗口管理器 本人使用的dwm源码:链接地址，如果要使用的话，记得安装依赖xorg-server, x11-libs/libXinerama，还有hack-nerd-font，然后直接进目录sudo make clean install就好了。\n再在家目录添加xinitrc文件。echo \u0026quot;exec dwm\u0026quot; \u0026gt; ~/.xinitrc\n开机自启需要编辑以下配置文件：\n1 2 3 4 5 6 7 8 9 10 11 12 13 # 开机自动登录 # Openrc /etc/inittab ...... #x1:12345:respawn:/sbin/agetty 38400 console linux c1:12345:respawn:/sbin/agetty --autologin haoleng --noclear 38400 tty1 linux c2:2345:respawn:/sbin/agetty 38400 tty2 linux ...... # 登录后执行startx # zsh ~/.zprofile if [ -z \u0026#34;${DISPLAY}\u0026#34; ] \u0026amp;\u0026amp; [ \u0026#34;${XDG_VTNR}\u0026#34; -eq 1 ]; then exec startx fi 另外需要注意的是，在树莓派5中使用xorg需要手动创建配置文件/etc/X11/xorg.conf.d/99-vc4.confg\n1 2 3 4 5 6 7 8 # Shamelessly copied from PiOS Section \u0026#34;OutputClass\u0026#34; Identifier \u0026#34;vc4\u0026#34; MatchDriver \u0026#34;vc4\u0026#34; Driver \u0026#34;modesetting\u0026#34; Option \u0026#34;PrimaryGPU\u0026#34; \u0026#34;true\u0026#34; EndSection 配置VNC远程桌面 介于RealVNC收费的缘故，本人选择了TigerVNC作为VNC工具，详细可以参考WIKI，本人在此只做简要介绍。\n本人并没有使用TigerVNC的服务器软件包，只是以最简单的方式实现了VNC功能，下面是本人认为关键的步骤：\n1 2 3 4 5 6 7 sudo emerge --ask net-misc/tigervnc vnc passwd ~/.vnc/passwd # 最后一个选n # 然后再在dwm启动脚本中加入 x0vncserver -PasswordFile ~/.vnc/passwd \u0026amp; # 就可以在dwm启动后执行VNC服务 Wayland桌面环境 本人选择稳定的SwayWM，直接emerge --ask gui-wm/sway安装即可，需要注意的就是记得以drmUSE标志安装wlroots，如下：\n1 2 3 4 emerge --info wlroots ↓↓↓↓↓↓ gui-libs/wlroots-0.17.2::gentoo was built with the following: USE=\u0026#34;X drm libinput session vulkan -liftoff -tinywl -x11-backend -xcb-errors\u0026#34; 然后可能才能启动Wayland，另外Wayland下的VNC需要安装wayvnc，然后再以headless模式启动Sway，我的配置文件在这里\nGPIO 树莓派的备份 暂时先记这条： sudo dd if=/dev/mmcblk0 |gzip\u0026gt;/home/haoleng/raspberrypi.gz status=progress sudo gzip -dc raspberrypi.gz | sudo dd of=/dev/mmcblk0 status=progress\n待续未完……\n","date":"2024-03-20T10:16:13+08:00","image":"https://haoleng-wick.github.io/p/raspberrypi-5-and-gentoo/Cover_hu10193379931846734990.png","permalink":"https://haoleng-wick.github.io/p/raspberrypi-5-and-gentoo/","title":"RaspberryPi 5 and Gentoo"},{"content":"Gentoo Linux 系统下 Tenosorflow-gpu 的安装 安装 nvidia 驱动 可以参考我的这篇文章\n使用 nvidia-smi命令 验证安装是否成功\n我的电脑输出如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Mon Jul 17 10:54:53 2023 +-----------------------------------------------------------------------------+ | NVIDIA-SMI 525.125.06 Driver Version: 525.125.06 CUDA Version: 12.0 | |-------------------------------+----------------------+----------------------+ | GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC | | Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. | | | | MIG M. | |===============================+======================+======================| | 0 NVIDIA GeForce ... Off | 00000000:01:00.0 Off | N/A | | N/A 37C P0 N/A / 80W | 6MiB / 6144MiB | 0% Default | | | | N/A | +-------------------------------+----------------------+----------------------+ +-----------------------------------------------------------------------------+ | Processes: | | GPU GI CI PID Type Process name GPU Memory | | ID ID Usage | |=============================================================================| | 0 N/A N/A 3640 G /usr/bin/X 4MiB | +-----------------------------------------------------------------------------+ 安装cuda 安装软件包\nsudo emerge --ask dev-util/nvidia-cuda-toolkit\n下载cudnn\n将下载后的文件解压缩并复制到指定位置\n1 2 3 4 5 6 7 tar xvzf cudnn-linux-x86_64-8.9.3.28_cuda12-archive.tar.xz cd cudnn-linux-x86_64-8.9.3.28_cuda12-archive sudo cp include/cudnn.h /opt/cuda/include sudo cp lib/libcudnn* /opt/cuda/lib64 sudo chmod a+r /opt/cuda/include/cudnn.h sudo chmod a+r /opt/cuda/lib64/libcudnn* 安装Anaconda 下载安装脚本Download\n给予执行权限chomd +x Anaconda3-*.sh\n执行./Download/Anaconda3-*.sh\n然后配置安装目录，是否在当前shell环境中激活，建议是。\n安装Tensorflow 创建虚拟环境\nconda create --name tensorflow python=3.10\n激活虚拟环境\nconda activate tensorflow\n安装软件包\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # conda环境下的cuda和cudnn conda install -c conda-forge cudatoolkit=11.8.0 cudnn=8.9.2.26 # conda虚拟环境的环境变量配置 # 临时设置 export LD_LIBRARY_PATH=$CONDA_PREFIX/lib/:$CUDNN_PATH/lib:$LD_LIBRARY_PATH export XLA_FLAGS=--xla_gpu_cuda_data_dir=/opt/cuda # 永久配置（建议临时设置测试tensorflow环境，成功之后再进行永久性配置） mkdir -p $CONDA_PREFIX/etc/conda/activate.d echo \u0026#39;export LD_LIBRARY_PATH=$CONDA_PREFIX/lib/:$CUDNN_PATH/lib:$LD_LIBRARY_PATH\u0026#39; \u0026gt;\u0026gt; $CONDA_PREFIX/etc/conda/activate.d/env_vars.sh echo \u0026#39;export XLA_FLAGS=--xla_gpu_cuda_data_dir=/opt/cuda\u0026#39; \u0026gt;\u0026gt; $CONDA_PREFIX/etc/conda/activate.d/env_vars.sh # pip安装tensorflow pip install --upgrade pip pip install tensorflow 验证\n1 python3 -c \u0026#34;import tensorflow as tf; print(\u0026#39;GPU\u0026#39;, tf.test.is_gpu_available())\u0026#34; 本人电脑上输出如下：\n1 2 3 4 **** 2023-07-17 11:14:12.266188: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1635] Created device /device:GPU:0 with 4069 MB memory: -\u0026gt; device: 0, name: NVIDIA GeForce RTX 3060 Laptop GPU, pci bus id: 0000:01:00.0, compute capability: 8.6 GPU True **** 至此，Tensorflow安装完毕。\nTensorflow Object Detection API 安装 安装教程参考 tensorflow2 od install\n克隆tensorflow模型存储库\n1 git clone https://github.com/tensorflow/models.git pip安装\n1 2 3 4 5 6 cd models/research # 需要安装 protos 在环境当中 protoc object_detection/protos/*.proto --python_out=. # 安装 TensorFlow Object Detection API cp object_detection/packages/tf2/setup.py . pip install . 测试\n1 2 # Test the installation. python object_detection/builders/model_builder_tf2_test.py 恭喜您，已经完成了安装，下来试试一个小例子吧。\n一个浣熊检测小模型\n","date":"2023-07-17T10:50:29+08:00","image":"https://haoleng-wick.github.io/p/tensorflow%E5%85%A5%E9%97%A8/Tensorflow_hu10989047162192874471.jpg","permalink":"https://haoleng-wick.github.io/p/tensorflow%E5%85%A5%E9%97%A8/","title":"Tensorflow入门"},{"content":"视觉导航 室内导航 关键技术：\n传感器信息采集分析\nSLAM建图的准确性、及时性，均取决于传感器获得数据的完备性。\n视觉里程计与回环检测分析\n传统VO框架搭配回环检测消除误差。\n3D建图\n​\t传统的ORB-SLAM只能构建稀疏云点地图，无法应用与导航任务，需要构建立体视觉惯性SLAM，生成三维语义拓扑图，最后生成路径规划地图，实现自主导航。\n基于深度视觉感知的SLAM导航 深度视觉的SLAM工作原理图： stateDiagram-v2 direction LR [*] --\u003e operation state operation{ direction LR 传感器数据 --\u003e （前端）视觉里程计 （前端）视觉里程计 --\u003e（后端）非线性优化 （后端）非线性优化 --\u003e 建图 } 传感器数据 --\u003e 回环检测 回环检测 --\u003e （后端）非线性优化 建图 --\u003e [*] 视觉处理主要前端处理算法： graph LR A([前端处理]) --\u003e BB[特征法] \u0026 C[直接法] BB --\u003e B1(关键点提取) B1 --\u003e b11([\"ORB(具有良好的平移旋转不变性，提取时间快)\"]) B1 --\u003e b12([\"SIFT(鲁棒性强,光照,旋转等均不敏感)\"]) B1 --\u003e b13([\"FAST(Fast角点，噪声敏感，速度快)\"]) B1 --\u003e b14([\"SURF(计算量比SIFT显著降低,同时鲁棒性也降低)\"]) BB --\u003e B2(描述子) B2 --\u003e b21([梯度计算HOG描述子]) B2 --\u003e b22([二进制BRISK描述子]) BB --\u003e B3(特征匹配-FLANN算法) BB --\u003e B4(数据关联) B4 --\u003e b41(\"2D-2D(多视图几何)\") B4 --\u003e b42(\"2D-3D(线性化(PNP)、非线性求解(BA))\") B4 --\u003e b43(\"3D-3D\") C --\u003e C1[\"半直接法(光流)\"] C --\u003e C2[直接法] C1 \u0026 C2 --\u003e c(\"(使用最小化光度误差，非线性优化求解)\") ​\t特征法： 特征法视觉里程计，将数据关联和位姿估计问题分开考虑、独立解决，但是特征计算和特征匹配非常耗时，在特征缺失、纹理重复的环境中容易跟踪丢失，并且稀疏的点云地图只能用于定位，而无法提供导航、避障等诸多功能。\n​\t直接法： 直接法视觉里程计以一种更整体、更优雅的方式，处理数据关联和位姿估计问题，仅使用像素的灰度信息，计算效率高，并且在特征缺失的环境中，也有很好的表现。其不足在于，对光照变化非常敏感，所建立的地图无特征描述子，导致难以进行地图的复用。因此，直接法更擅长端到端的解决连续缓慢的定位问题，而特征法更适合重定位与回环检测。\n视觉处理主要后端处理算法：\ngraph LR A([后端优化]) --\u003e B(基于滤波的方法) \u0026 C(基于优化的方法) \u0026 D(回环检测) B --\u003e B1([\"EKF算法(扩展卡尔曼滤波)\"]) B --\u003e B2([\"PKF算法(粒子滤波算法)\"]) B --\u003e B3([\"MSCKF算法(多状态约束卡尔曼)\"]) C --\u003e C1(BA优化) C1 --\u003e c11([\"PTAM\"]) C1 --\u003e c12([\"ORB-SLAM\"]) C --\u003e C2(位姿图优化,舍弃对于路标点的优化) C2 --\u003e c21([Vins-Mono]) C2 --\u003e c22([OKVIS]) C --\u003e C3(因子图优化) C3 --\u003e c31([ISAM以及其对应的gtsam算法库]) D --\u003e D1(基于深度学习) D --\u003e D2(基于词袋模型) ​\t**滤波器法：**虽然滤波器的全局优化假设了状态量之间的马尔可夫性，存在较大的线性误差，但相比非线性优化法在计算资源受限平台或结构化场景中，仍然是一种非常有效的方法。\n​\t**优化算法：**非线性优化法是利用约束条件构造目标函数，通过最优化的算法寻找位姿和地图点的最优解。集束调整和图优化都是非线性优化法，但是比较难以求解。\n​\t**回环检测：**基于子地图与子地图间的匹配、基于图像与子地图间的匹配、基于图像与图像间匹配（目前主流）、基于卷积神经网络的回环检测。\n室外导航 关键技术：\n构建特定区域先验数据库\n匹配算法\n匹配算法直接关乎无人机定位的准确度，对于 SIFT 算法而言，计算复杂度较高，内部运算的数据量巨大，难以符合无人机定位导航的实时性需求，同样也会降低让无人机的工作效率。\n搜索目标特征提取、目标确认分析\n搜索前需要得到目标的特征，目标特征点太少会导致识别率降低，目标特征不够明确会导致识别错误率增加，目标特征过于精细由于传感器精度不够，会难以匹配成功。\n无人机巡查工作原理： stateDiagram direction LR 特定区域先验数据库 --\u003e 匹配算法 实测环境特征数据 --\u003e 匹配算法 匹配算法 --\u003e 无人机 无人机 --\u003e 定位与导航 定位与导航 --\u003e 巡查工作 特殊目标搜索识别原理：\nstateDiagram-v2 direction LR [*] --\u003e operation 搜索目标特征 --\u003e 目标确认 搜索目标特征 --\u003e 无人机 state operation { direction LR 目标搜索 --\u003e 目标确认 目标确认 --\u003e 无人机 无人机 --\u003e 定位与跟踪 } 定位与跟踪 --\u003e [*] 相关技术与设备 视觉感知单元 单目摄像头 单目测距原理：\n测量原理主要分为基于已知运动和已知物体的测量方法。\n假设有一个宽度为 $W$ 的目标或者物体，然后将它放在距离相机为 $D$ 的位置，用该相机对该物体进行拍照并且测量物体的像素宽度 $P$ ,则相机的焦距公式为： $$ F = (P * D) / W $$ 当继续将相机移动靠近或远离物体或目标是，可利用相似三角形计算物体与相机的间距： $$ D = (W * F) / P $$ 单目目标检测：\n在3D目标检测领域，根据输入信息的不同，大致可分为三类\nPoint Cloud-based Methods (基于点云) ——经典 Multimodal Fusion-based Methods (点云和图像的融合) ——主流 Monocular/Stereo Image-based Methods (单目/立体图像的方法) 双目摄像头 双目测距：\n通过两幅图像的视差计算。\n双目视觉目标检测：\n基于直接视锥空间的双目目标检测方法\n不需要额外的坐标空间转换，只需要基础骨干提取的两个单目特征构造双目特征。\n基于串接构造视锥空间特征的方法\n将基础骨干提取的两个单目视锥空间特征串接起来，利用卷积神经网络强大的拟合能力提取候选框或直接检测三维目标。不改变原单目特征的空间坐标，简单快速。\n基于平面扫描构造视锥空间特征的方法\n基于平面扫描的检测方法受益于双目深度估计方法的发展，能够直接利用点云监督取得更好的匹配结果，进而学习到每个视锥空间像素是否被物体占据的信息，辅助提高三维检测性能。\n基于显式逆投影空间的双目目标检测方法\n逆投影变换主要可以应用在输入图像、特征、候选区域三个不同环节。\n基于原始图像视差的逆投影方法\n伪雷达方法。\n基于特征体的逆投影方法\n基于特征体逆投影的双目目标检测方法通过插值和采样的方式将平面扫描得到的匹配代价体变换到三维空间，利用了图像特征提供的颜色和纹理信息，实现了端到端训练的双目目标检测。\n基于候选像素视差的逆投影方法\n这种逆投影方法生成的三维空间有效体素较少，可以在有限的检测时间内更灵活地控制特征的空间分辨率；聚焦于前景目标，能够避免不准确的深度估计带来的性能下降。\n姿态感知单元 惯性导航系统 惯性导航系统（Inertial Navigation System，INS）是一种利用惯性敏感器件、基准方向及最初的位置信息，来确定运载体在惯性空间中的位置、方向和速度的自主式导航系统，有时也简称为惯导。其工作环境不仅包括空中、地面，还可以在水下。\n惯性系统至少需要包含加速度计、陀螺仪等的惯性测量单元和用于推理的计算单元两大部分。\n计算单元流程图：\nstateDiagram-v2 direction TB state 惯性单元{ 加速度计 陀螺仪 磁力计 } 惯性单元 --\u003e 姿态解算 姿态解算 --\u003e 加速度积分 加速度积分 --\u003e 误差补偿 误差补偿 --\u003e 姿态和位置 多源融合视觉处理 视觉+惯性融合 视觉惯性融合定位需要视觉传感器和惯性测量传感器进行融合。 融合方案 一般可以分为松耦合和紧耦合。\n松耦合情况下是两个优化问题，视觉测量优化问题包含视觉的状态变量，惯性测量的优化问题包含惯性测量的状态变量，最终，我们只是融合这两个优化得到的位姿估计结果。\n紧耦合是一个优化问题，它将视觉测量和惯性测量放在同一个状态向量中，构建包含视觉残差和惯性测量残差的误差项，同时优化视觉状态变量和惯性测量状 态变量。\n基于模型的视觉/惯导组合：\nstateDiagram-v2 direction TB state 基于模型的纯视觉导航{ 视觉原始数据 --\u003e 前端视觉里程计 闭环检测 --\u003e 后端闭环优化与构图 前端视觉里程计 --\u003e 后端闭环优化与构图 后端闭环优化与构图 --\u003e 闭环检测 视觉原始数据 --\u003e 闭环检测 } 后端闭环优化与构图 --\u003e 导航参数与环境地图 后端闭环优化与构图 --\u003e 导航信息融合 state 惯导 { 惯性原始数据 --\u003e 惯性信息预处理 惯性信息预处理 --\u003e 导航信息融合 } 惯性信息预处理 --\u003e 前端视觉里程计 导航信息融合 --\u003e 导航参数与环境地图 视觉+无线电融合 AS算法 随机森林分类算法 SVM算法 多源融合 多传感器融合层次（级别） 基本原理图：\nstateDiagram-v2 监测对象 --\u003e sensor state sensor{ direction TB 传感器1 传感器2 .... 传感器n } sensor --\u003e features state features{ direction TB 特征提取1 特征提取2 .. 特征提取n } features --\u003e identify state identify{ direction TB 识别1 识别2 ... 识别n } identify --\u003e 决策级融合 决策级融合 --\u003e 决策 在多传感器信息融合中，按其在融合系统中信息处理的抽象程度可分为三个层次：\n数据级融合\n也称像素级融合，属于底层数据融合，数据级融合不存在数据丢失的问题，得到的结果也作为准确；但是计算量大，对系统通信带宽要求较高；\n特征级融合\n分为目标状态融合、目标特征融合，\n特征层融合技术发展较为完善，此级别融合对计算量和通信带宽要求相对降低，但由于部分数据的舍弃使其准确性也有所下降。\n决策级融合\n多传感器融合体系结构 集中式 分布式 混合式 ","date":"2023-07-17T10:50:29+08:00","image":"https://haoleng-wick.github.io/p/%E8%A7%86%E8%A7%89%E5%AF%BC%E8%88%AA%E7%BB%BC%E8%BF%B0/R-C_hu13558946655315622512.jpg","permalink":"https://haoleng-wick.github.io/p/%E8%A7%86%E8%A7%89%E5%AF%BC%E8%88%AA%E7%BB%BC%E8%BF%B0/","title":"视觉导航综述"},{"content":"引言 安装相关 安装Anaconda\n通过链接Anaconda下载安装包并根据提示安装到自己的目标目录下。国内网络环境不友好，可以更换国内镜像源。\n然后在当前环境下创建一个新的环境命名为 pytorch(可随意确定) 并切换到新环境(pytorch)下，再安装pytorch所需的环境\n1 2 3 4 5 6 7 (base)$ conda create -n pytorch python=3.10 (base)$ conda activate pytorch # GPU版本的 (pytorch)$ conda install pytorch torchvision torchaudio pytorch-cuda=11.7 -c pytorch -c nvidia # CPU版本的 (pytorch)$ conda install pytorch torchvision torchaudio cpuonly -c pytorch 安装完成之后进行验证\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 (pytorch)$ python Python 3.10.11 (main, Apr 20 2023, 19:02:41) [GCC 11.2.0] on linux Type \u0026#34;help\u0026#34;, \u0026#34;copyright\u0026#34;, \u0026#34;credits\u0026#34; or \u0026#34;license\u0026#34; for more information. \u0026gt;\u0026gt;\u0026gt; import torch # 没有报错说明 pytorch 安装成功 \u0026gt;\u0026gt;\u0026gt; torch.cuda.is_available() # True说明 GPU 可用,我这里 linux 系统并没有装 nvidia 显卡驱动，所以是 False False \u0026gt;\u0026gt;\u0026gt; x = torch.rand(5, 3) \u0026gt;\u0026gt;\u0026gt; print(x) tensor([[0.7296, 0.8000, 0.3431], [0.9990, 0.2202, 0.0889], [0.1130, 0.9352, 0.8562], [0.4942, 0.4214, 0.4962], [0.3023, 0.7504, 0.6801]]) \u0026gt;\u0026gt;\u0026gt; Tensorboard使用 举一个简单的画三角函数图像的例子：\n1 2 3 4 5 6 7 8 9 10 import numpy as np from torch.utils.tensorboard import SummaryWriter writer = SummaryWriter(\u0026#34;./test_logs\u0026#34;) # 可自定义输出位置，默认为./runs r = 5 for i in range(100): writer.add_scalars(\u0026#39;trigonometric function\u0026#39;, {\u0026#39;xsinx\u0026#39;:i*np.sin(i/r), \u0026#39;xcosx\u0026#39;:i*np.cos(i/r), \u0026#39;tanx\u0026#39;: np.tan(i/r)}, i) writer.close() 然后通过命令 tensorboard --logdir=test_logs，根据提示可以打开网页看到结果，如下图所示。\nTransforms图像变换\n1 2 3 4 5 6 7 8 9 10 11 from torchvision import transforms transforms.CenterCrop() #对图片中心进行裁剪 transforms.FiveCrop() #对图像四个角和中心进行裁剪得到五分图像 transforms.Grayscale() #对图像进行灰度变换 transforms.Pad() #使用固定值进行像素填充 transforms.RandomResizedCrop()\t#随即裁剪为不同大小和宽高比，然后缩放为特定大小 transforms.RandomCrop() #随机区域裁剪 transforms.RandomRotation() #随机旋转 transforms.RandomHorizontalFlip() #随机水平翻转 transforms.Normalize()\t#归一化处理 ... transforms.Compose组合实现各种变化\n1 2 3 4 5 transform = transforms.Compose([ transforms.Random transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) ]) torchvision、DataLoader\nnn.Module\n卷积层 激活层 池化层 全连接层 损失函数 L1Loss \u0026amp;\u0026amp; MSELoss 假设预期结果为 [1, 2, 8]，但实际结果为 [1, 2, 3]\n定义：\n$$ ℓ(x,y)=\\begin{cases} mean(L),\\quad reduction=mean; \\\\\\\\ sum(L),\\quad reduction=sum \\end{cases} $$\nloss_l1 = nn.L1loss(reduction='mean'或者'sum')\nL1Loss的结果：\n$$ ℓ(x,y)=\\begin{cases} \\frac {(1 - 1) + (2 - 2) + (8 - 3)}{3} = 1.67\t\\quad reduction = mean \\\\\\ (1 - 1) + (2 - 2) + (8 - 3) = 5\t\\quad reduction = sum \\end{cases} $$\nloss_mse = nn.MSELoss()\nMSELoss的结果：\n$$ ℓ(x,y)=\\begin{cases} \\frac {(1 - 1)^2 + (2 - 2)^2 + (8 - 3)^2}{3} = 8.33\t\\quad reduction = mean \\\\\\ (1 - 1)^2 + (2 - 2)^2 + (8 - 3)^2 = 25\t\\quad reduction = sum \\end{cases} $$\nCrossEntropyLoss torch.nn.CrossEntropyLoss(weight=None, ignore_index=- 100, reduction='mean', label_smoothing=0.0)\n定义：\n$$ loss(x, class) = -ln(\\frac{e^{x[class]}}{\\sum_j e^{x[j]}}) \\\\\\ =-x[class] + ln(\\sum_j e^{x[j]}) $$\n1 2 3 4 5 6 7 8 9 10 11 12 13 import torch from torch import nn ## target\t[person, dog, cat] ## intputs\t[0.1, 0.2, 0.3] inputs = torch.tensor([0.1, 0.2, 0.3]) target = torch.tensor([1])\t# 1代表dog inputs = torch.reshape(inputs, (1, 3)) loss_cross = nn.CrossEntropyLoss() result_cross = loss_cross(inputs, target) print(result_cross) ## 输出 ## tensor(1.1019) CrossEntropyLoss的结果：\n$$ loss(x, class) = -0.2 + ln(e^{0.1} + e^{0.2} + e^{0.3}) = 1.1019 $$\n一个简单的例子（Cifar-10分类任务） 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 # -*- coding: utf-8 -*- # 导入包 import torch import torchvision from torch.utils.tensorboard import SummaryWriter from torch import nn from torch.utils.data import DataLoader # 导入数据集 train_data = torchvision.datasets.CIFAR10(root=\u0026#34;./data\u0026#34;, train=True, transform=torchvision.transforms.ToTensor(), download=True) test_data = torchvision.datasets.CIFAR10(root=\u0026#34;./data\u0026#34;, train=False, transform=torchvision.transforms.ToTensor(), download=True) # 输出训练和测试总量 train_data_size = len(train_data) test_data_size = len(test_data) print(\u0026#34;训练集的大小: {}\u0026#34;.format(train_data_size)) print(\u0026#34;测试集的大小: {}\u0026#34;.format(test_data_size)) batch_size = 64 train_dataloader = DataLoader(train_data, batch_size=batch_size) test_dataloader = DataLoader(test_data, batch_size=batch_size) class MyModel(nn.Module): def __init__(self): super().__init__() self.model = nn.Sequential( nn.Conv2d(3, 32, 5, stride=1, padding=2), nn.MaxPool2d(2), nn.Conv2d(32, 32, 5, stride=1, padding=2), nn.MaxPool2d(2), nn.Conv2d(32, 64, 5, stride=1, padding=2), nn.MaxPool2d(2), nn.Flatten(), nn.Linear(64*4*4, 64), nn.Linear(64, 10) ) def forward(self, x): x = self.model(x) return x # 实例一个model my_model = MyModel() # GPU if torch.cuda.is_available(): my_model = my_model.cuda() # 损失函数 loss_fn = nn.CrossEntropyLoss() # 交叉熵 if torch.cuda.is_available(): loss_fn = loss_fn.cuda() # 优化器 learning_rate = 0.01 # 学习率 optimizer = torch.optim.SGD(my_model.parameters(), lr=learning_rate) # 训练参数 total_train_step = 0 total_test_step = 0 epoch = 16 # tensorboard writer = SummaryWriter(\u0026#34;./taining_logs\u0026#34;) for i in range(epoch): print(\u0026#34;----开始第 {} 轮训练----\u0026#34;.format(i+1)) my_model.train() for data in train_dataloader: imgs, labels = data if torch.cuda.is_available(): imgs = imgs.cuda() labels = labels.cuda() outputs = my_model(imgs) loss = loss_fn(outputs, labels) # 优化 optimizer.zero_grad() loss.backward() optimizer.step() total_train_step = total_train_step + 1 # 每100次显示一次 if total_train_step % 100 == 0: print(\u0026#34;第 {} 次训练的loss值: {}\u0026#34;.format(total_train_step, loss.item())) writer.add_scalar(\u0026#34;train_loss\u0026#34;, loss.item(), total_train_step) # 测试步骤开始 my_model.eval() total_test_loss = 0 total_accuracy = 0 # 测试集不需要梯度 with torch.no_grad(): for data in test_dataloader: imgs, labels = data if torch.cuda.is_available(): imgs = imgs.cuda() labels = labels.cuda() outputs = my_model(imgs) loss = loss_fn(outputs, labels) total_test_loss = total_test_loss + loss.item() accuracy = (outputs.argmax(1) == labels).sum() total_accuracy = total_accuracy + accuracy print(\u0026#34;整体测试集上的Loss: {}\u0026#34;.format(total_test_loss)) print(\u0026#34;整体测试集上的正确率: {}\u0026#34;.format(total_accuracy / test_data_size)) writer.add_scalar(\u0026#34;test_loss\u0026#34;, total_test_loss, total_test_step) writer.add_scalar(\u0026#34;test_accuracy\u0026#34;, total_accuracy / test_data_size, total_test_step) total_test_step = total_test_step + 1 torch.save(my_model, \u0026#34;my_cifar10.pth\u0026#34;) print(\u0026#34;模型已保存\u0026#34;) writer.close() Pytorch 各种模型的格式 格式 说明 适用场景 对应后缀 .pt或.pth pytorch默认的模型文件 需要保存和加载完整Pytorch模型的场景 .pt或.pth .bin 通用的二进制格式 需要将Pytorch模型转化为通用的二进制格式的场景 .bin .onnx 通用的交叉模型格式 需要将Pytorch模型转化为其他深度学习框架或硬件平台可用的格式的场景 .onnx TorchScript Pytorch提供的一种序列化和优化模型的方法 需要将Pytorch模型序列化和优化，并在没有Pytorch环境的情况下运行模型的场景 .pt或.pth 目标检测数据集 VOC数据集 Annotations： 包含了xml文件，描述了图片的各种信息，特别是目标的坐标位置 ImagesSets： 主要关注Main文件夹的内容，里面的文件包含了不同类别目标的训练/验证数据集的图片名称 JPEGImages： 图片原文件 SegentationClass/Object： 用于语义分割 COCO数据集 创建自己的coco数据集：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 目录结构： ......... my_dataset ├───imgs │ ├───1.jpg │ ├───1.json │ ├───2.jpg │ ├───2.json │ │........... │ └───nnn.json └───coco labelme2coco.py labels.txt ......... 安装labelme,然后在当前目录执行命令：\n1 python labelme2coco.py --labels labels.txt my_data/imgs/ my_data/coco/ 其中文件链接labelme2coco.py和labels.txt文件示例如下(本演示只有一个标签raccoon):\n1 2 3 __ignore__ _background_ raccoon 之后会在coco目录下生成可用的coco数据集文件。\n加载该数据集并可视化显示的代码如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import torchvision from PIL import ImageDraw # torch官方的Coco API加载数据集 coco_dataset = torchvision.datasets.CocoDetection(root=r\u0026#34;./my_data/coco/\u0026#34;, annFile=r\u0026#34;./my_data/coco/annotations.json\u0026#34;) # 读取第一张图片并显示 image, info = coco_dataset[0] image_handler = ImageDraw.ImageDraw(image) # 在该图片上画上标注框 for annotation in info: x_min, y_min, width, height = annotation[\u0026#39;bbox\u0026#39;] image_handler.rectangle(((x_min, y_min), (x_min+width, y_min+height)), fill=None, outline=\u0026#39;red\u0026#39;, width=2) # 显示 image.show() To be contuine\n","date":"2023-05-20T05:59:13+08:00","image":"https://haoleng-wick.github.io/p/pytorch%E5%85%A5%E9%97%A8/PyTorch_hu7179382626443050191.jpg","permalink":"https://haoleng-wick.github.io/p/pytorch%E5%85%A5%E9%97%A8/","title":"Pytorch入门"},{"content":"KMP算法 \u0026raquo;\u0026gt;配合视频食用，效果更佳!\nKMP简介 KMP算法主要用于字符串匹配。\nKMP的主要思想是当出现字符串不匹配时，可以知道一部分之前已经匹配的文本内容，可以利用这些信息避免从头再去做匹配了。\n前缀表 前缀表是用来匹配回溯的，它记录了模式串与主串不匹配时，模式串应该从哪里开始重新匹配。\n前缀： 不包含最后一个字符的所有以第一个字符开头的连续子串。\n后缀：不包含第一个字符的所有以最后一个字符结尾的连续子串。\n前缀表：记录下标i之前（包括i）的字符串中，有多大长度的_相同前缀后缀_。\n长度为前3个字符的子串aab，其最长相同前后缀的长度为0.\n长度为前4个字符的子串aaba，其最长相同前后缀的长度为1.\n利用前缀表匹配字符串：\n前缀表与next数组 next数组可以有很多种版本，其影响的仅是匹配失败后回溯时依据的下标。本文采用的时前缀表统一减去一之后的版本，即\n1 2 3 模式串：\t\u0026#34;aabaaf\u0026#34; 前缀表：\t[0, 1, 0, 1, 2, 0] next数组：[-1, 0, -1, 0, 1, -1] next数组的构建 初始化 处理前后缀不同的情况 处理前后缀相同的情况 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void get_next(int *next, char *needle) { // 初始化 // j时前缀的末尾，也代表了前缀的长度 // 因为只有相等的时候j才推进，所以它能代表最长相等前后缀的长度，即next数组的值 int j = -1; next[0] = j; // 从下标为1开始遍历，i作为后缀的末尾 for(int i = 1; i \u0026lt; strlen(needle); i++) { // 由于可能存在递归回退，所以要用while while(j \u0026gt;= 0 \u0026amp;\u0026amp; needle[i] != neddle[j+1])\t// 前后缀不相同 j = next[j];\t// 回溯到前一位next数组对应的值,由于这里时j+1，所以应该是next[j] if(needle[i] == needle[j+1])\t// 相同继续推进 j++; next[i] = j;\t// 将j(前缀的长度)赋值给next[i] } } 用next数组匹配 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int KMP(char *haystack, char *needle) { if(strlen(needle) == 0) return 0; int *next = malloc(strlen(needle) * sizeof(int)); get_next(next, needle); int j = -1; for(int i = 0; i \u0026lt; strlen(haystack); i++) { while(j \u0026gt;= 0 \u0026amp;\u0026amp; haystack[i] != needle[j+1]) // 回溯 j = next[j]; if(haystack[i] == needle[j+1]) // 向前遍历 j++; if(j == strlen(needle) - 1) // 判断是否匹配完成 return(i - strlen(needle) + 1); // 返回haystack的下标 } return -1; } C语言实现完整代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;string.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; int KMP(char *haystack, char *needle) { if(strlen(needle) == 0) return 0; // get_next int *next = malloc(strlen(needle) * sizeof(int)); int j = -1; next[0] = j; printf(\u0026#34;next[]: [ %d \u0026#34;,next[0]); for(int i = 1; i \u0026lt; strlen(needle); i++) { while(j \u0026gt;= 0 \u0026amp;\u0026amp; needle[i] != needle[j+1]) j = next[j]; if(needle[i] == needle[j+1]) j++; next[i] = j; printf(\u0026#34;%d \u0026#34;,next[i]); } printf(\u0026#34;]\\n\u0026#34;); // KMP j = -1; for(int i = 0; i \u0026lt; strlen(haystack); i++) { while(j \u0026gt;= 0 \u0026amp;\u0026amp; haystack[i] != needle[j+1]) j = next[j]; if(haystack[i] == needle[j+1]) j++; if(j == strlen(needle) - 1) { free(next); return(i - strlen(needle) + 1); } } free(next); return -1; } int main(int argc, char **argv) { if(argc != 3) fprintf(stderr, \u0026#34;Usage: ./a.out abcabcbb cab\\n\u0026#34;); int k = KMP(argv[1], argv[2]); printf(\u0026#34;the index is: %d \\n\u0026#34;, k); return 0; } ","date":"2023-05-11T14:32:13+08:00","image":"https://haoleng-wick.github.io/p/kmp%E7%AE%97%E6%B3%95/Kmp_hu10854606807330125474.jpg","permalink":"https://haoleng-wick.github.io/p/kmp%E7%AE%97%E6%B3%95/","title":"KMP算法"},{"content":"神经网络视觉基础 基础知识 神经网络的基本原理 神经网络的训练，主要目的就是通过学习算法得到各层神经元之间的连接权重和偏置参数等，然后通过参数计算出输入值的输出。\n卷积神经网络包括输入层、中间层、输出层。而中间层可以细化为卷积层、激活层、池化层和全连接层。\n卷积层（局部感知、权值共享）\u0026ndash;（图像特征提取） 卷积层中，卷积操作由一个或者多个卷积核(filter)在前层图像上选择相应的区域做卷积运算，然后按一定的步长作滑动运算，依次提取图像区域的像素级特征，图像特征综合后经过激活函数激活，完成一次输入到输出的特征提取过程，卷积后的特征图反映了前层图像的融合特征。\n卷积操作 经卷积后的矩阵尺寸大小计算公式为(输入图片大小WxW，卷积核FxF，步长S，padding = P) $$ N = (W - F + 2P) / S + 1 $$\n激活层 (通过函数把特征保留并映射到输出端) 卷积神经网络在卷积操作后作用非线性激活函数，可以实现对输入信息的非线性变化，从而使网络的输入和输出产生非线性映射关系，激活层对卷积后的逐元素作用激活函数，实现输入和输出信息的同维。引入激活函数的目的是为了增加神经网络的非线性拟合能力。\n\u0026ldquo;为什么一定要非线性？\u0026rdquo;\n因为神经网络的每一次输入和输出都是线性求和过程，下一层输出承接了上一层输入函数的线性变换，如果没有非线性激活函数，那么无论神经网络有多少层，最后的输出都是输入的线性组合。这样的线性组合并不能解决复杂的问题。\n激活函数 Sigmoid（又称“逻辑函数”） $$ f(x) = \\frac {1} {1 + e^{-x}} $$\n$$ y = \\begin{cases} 1, 当S(x) \\ge 0.5 \\\\\\ 0, 当S(x) \u0026lt; 0.5 \\end{cases} $$\n$$ S(x)\u0026rsquo; = \\frac{e^{-x}}{(1+e^{-x})^2} $$\n​\t优点：\n值域为[0,1]，他对每个神经元的输出进行了归一化，适合用于将预测概率作为输出的模型 梯度平滑，避免了跳跃式 的输出 ​\t缺点：\n接近0或1的神经元梯度趋近于0，容易引起梯度消失，即无法反向传播更新权重。 计算量大，需要更高的算力 ReLU（目前主流的激活函数） $$ f(x) = Max(0, x) $$\n$$ f\u0026rsquo;(x) = Sgn(x) $$\n​\t相比sigmoid函数，其计算速度快，收敛速度快（因为输入为负值时，神经元不会被激活，所以网络很稀疏，能更好的提取相关特征，拟合训练数据）。ReLU函数能够最大化发挥神经元的筛选能力。\n不足：很容易训练过程中使部分kernel废掉，且无法再次被激活。\n\u0026ldquo;ReLU是分段线性函数，它是怎么实现非线性的？\u0026rdquo;\nReLU在整个定义域内并不是线性的，组合多个（线性操作+ReLU）就可以任意划分空间，对于层数比较少的神经网络，用ReLU作为激活函数，那非线性肯定没有那么强，但是当层数多达几十甚至上千，虽然，单独的隐藏层是线性的，但是很多的隐藏层表现出来的就是非线性的。（即用很多小的直线可以拟合出曲线效果一样）。\nLeaky ReLU $$\rf(x) = max(\\alpha x, x)\r$$\r​\t其中$\\alpha$为(0,1)的系数，可以有效解决*ReLU*函数神经元死亡的现象。\rSoftmax (所有输出概率和为1) $$ o_i = \\frac {e^{y_i}} {\\sum_je^{y_j}} $$\n用于多分类问题的激活函数 在零点不可微，负输入的梯度为零会产生“死亡神经元” 对于二分类问题，理论上使用sigmoid和softmax没有区别，因为数学表达式的形式是一样的。 对于多分类非互斥问题（多标签分类）如人和女人，使用sigmoid更合适。 对于多分类互斥问题（单标签分类），使用softmax更合适。\n池化层（对特征图进行稀疏处理） 降低信息冗余，提升模型的尺度不变性、旋转不变性、防止过拟合。\nMaxPooling（最大值池化）\n全连接层（特征空间\u0026ndash;\u0026gt;样本标记空间) 卷积层提取了各种特征，但很多物体可能拥有同一类特征，全连接层相当于组合了这些特征起到了分类器的功能。\n损失函数 损失函数是用来衡量模型预测值$f(x)$与真实值$Y$的差异程度的运算函数，他是一个非负实数值函数，通常表示为$L(Y|f(x))$。\n损失函数使用主要是在模型的训练阶段，每批训练数据送入模型后，通过前向传播输出预测值，然后损失函数会计算出预测值和真实值之间的差异值，也就是损失值。得到损失值之后，模型通过反向传播去更新各个参数，来降低真实值与预测值之间的损失，使模型越来越准确。\n\u0026ldquo;反向传播过程\u0026rdquo; $$ \\omega_{11} = \\omega_{11} - \\eta \\frac{\\partial \\delta}{\\partial \\omega_{11}} $$ 通过链式求导法则和梯度下降，逐步修改权重参数$\\omega$。其中 $\\eta$ 为学习率，$\\delta$为损失值。\n基于距离度量的损失函数：\n均方误差损失函数（MSE） $$ L(Y|f(x)) = \\frac{1}{n} \\sum_{i=1}^N(Y_i - f(x_i))^2 $$\nL2损失函数(欧氏距离) $$ L(Y|f(x)) = \\sqrt{\\frac{1}{n} \\sum_{i=1}^N (Y_i - f(x_i))^2} $$\nL2损失函数(曼哈顿距离) $$ L(Y|f(x)) = \\sum_{i=1}^N |Y_i - f(x_i)| $$\nSmooth L1损失函数(主要用在目标检测中防止梯度爆炸) $$ L(Y|f(x)) = \\begin{cases} \\frac{1}{2}(Y-f(x))^2 \\qquad \\quad |Y-f(x)| \u0026lt; 1 \\\\\\ |Y-f(x)|- \\frac{1}{2} \\qquad |Y-f(x)| \\ge 1 \\end{cases} $$\nhuber损失函数(平方损失+绝对损失) $$ L(Y|f(x)) = \\begin{cases} \\frac{1}{2}(Y-f(x))^2 \\qquad \\qquad |Y-f(x)| \\le \\delta \\\\\\ \\delta |Y-f(x)|- \\frac{1}{2} \\delta^2 \\qquad |Y-f(x)| \u0026gt; \\delta \\end{cases} $$\n基于概率分布度量的损失函数：(涉及概率分布或预测类别出现的概率的问题中应用广泛)\nKL散度函数(相对熵) $$ L(Y|f(x)) = \\sum_{i=1}^n Y_i \\times log(\\frac{Y_i}{f(x_i)}) $$\n交叉熵损失 $$ L(Y|f(x)) = - \\sum_{i=1}^n Y_i \\times logf(x_i) $$ 交叉熵损失函数刻画了实际输出概率与期望输出概率之间的相似度，交叉熵的值越小，两个概率分布就越接近，特别是在正负样本不均衡的分类问题中，常用交叉熵作为损失函数。\n目前，交叉熵损失函数是卷积神经网络中最常使用的分类损失函数，它可以有效避免梯度消散。在二分类情况下也叫做对数损失函数。\nsoftmax损失函数 $$ L(Y|f(x)) = -\\frac{1}{n} \\sum_{i=1}^n Y_i \\times log \\frac{e^{f_{Y_i}}}{\\sum_{j=1}^c e^{f_j}} $$\nFocal loss $$ FE = \\begin{cases} -\\alpha(1-p)^\\gamma log(p) \\qquad \\quad y = 1 \\\\\\ -(1 - \\alpha)p^\\gamma log(1-p) \\quad y = 0 \\end{cases} $$\n优化器 优化器就是在深度学习反向传播过程中，指引损失函数（目标函数）的各个参数往正确的方向更新合适的大小，使得更新后的各个参数让损失函数（目标函数）值不断逼近全局最小。\nSGD优化器（随机梯度下降法，易受噪声影响，可能陷入局部最优解） $$ \\omega_{t+1} = \\omega_t - \\alpha \\cdot g(\\omega_t) $$\n​\t$\\alpha$为学习率，$g(\\omega_t)$ 为$t$ 时刻对参数$\\omega_t$ 的损失梯度。\n​\t优点：\n每次只用一个样本更新参数，训练速度快\n随机梯度下降所带来的波动有利于优化的方向从当前的局部极小值点跳到另一个更好的局部极小值点，这样对于非凸函数，最终收敛于一个较好的局部极值点，甚至全局极值点。\n​\t缺点：\n当遇到局部最优点或鞍点时，梯度为0，无法继续更新参数 沿陡峭方向震荡，而沿平缓维度进展缓慢，难以迅速收敛 SGD+Momentum优化器（引入动量，抑制样本噪声的干扰） $$ v_t = \\eta \\cdot v_{t-1} + \\alpha \\cdot g(\\omega_t) \\\\\\ \\omega_{t+1} = \\omega_t - v_t $$\n​\t$\\alpha$为学习率，$g(\\omega_t)$ 为$t$ 时刻对参数$\\omega_t$ 的损失梯度，$\\eta(0.9)$ 为动量系数。\n加入了动量因素，缓解了SGD在局部最优点无法持续更新的问题和震荡幅度过大的问题，但并没有完全解决，当局部沟壑比较深，动量加持用完了，依然会困在局部最优里来回振荡。 Adagrad优化器（自适应学习率，二阶动量） $$ s_t = s_{t-1} + g(\\omega_t) \\cdot g(\\omega_t) \\\\\\ \\omega_{t+1} = \\omega_t - \\frac{\\alpha} {\\sqrt{s_t + \\varepsilon}} \\cdot g(\\omega_t) $$\n​\t$\\alpha$为学习率，$g(\\omega_t)$ 为$t$ 时刻对参数$\\omega_t$ 的损失梯度，$\\varepsilon(10^{-7})$ 为防止分母为零的小数。学习率下降过快容易未收敛就停止训练\nRMSProp优化器（自适应学习率） $$ s_t = \\eta \\cdot s_{t-1} + (1-\\eta) \\cdot g(\\omega_t) \\cdot g(\\omega_t) \\\\\\ \\omega_{t+1} = \\omega_t - \\frac{\\alpha} {\\sqrt{s_t + \\varepsilon}} \\cdot g(\\omega_t) $$\n​\t$\\alpha$为学习率，$g(\\omega_t)$ 为$t$ 时刻对参数$\\omega_t$ 的损失梯度，$\\eta(0.9)$ 控制衰减速度，$\\varepsilon(10^{-7})$ 为防止分母为零的小数。\nAdam优化器（自适应学习率） $$ m_t = \\beta_1 \\cdot m_{t-1} + (1 - \\beta_1) \\cdot g(\\omega_t) \\qquad一阶动量 \\\\\\ v_t = \\beta_2 \\cdot v_{t-1} + (1 - \\beta_2) \\cdot g(\\omega_t) \\cdot g(\\omega_t) \\quad二阶动量 \\\\\\ \\hat{m_t} = \\frac{m_t}{1 - \\beta^t_1} \\quad \\hat{\\frac{v_t} {1 - \\beta^t_2}} \\\\\\ \\omega_{t + 1} = \\omega_t - \\frac{\\alpha} {\\sqrt{\\hat{v_t} + \\varepsilon}} \\hat{m_t} $$\n​\t$\\alpha$为学习率，$g(\\omega_t)$ 为$t$ 时刻对参数$\\omega_t$ 的损失梯度，$\\beta_1(0.9)$、$\\beta_2(0.999)$ 控制衰减速度，$\\varepsilon(10^{-7})$ 为防止分母为零的小数。\n通过一阶动量和二阶动量，有效控制学习率和梯度方向，防止梯度的振荡和在鞍点的静止。 可能错过全局最优解。自适应学习率算法可能会对前期出现的特征过拟合，后期才出现的特征很难纠正前期的拟合效果。后期Adam的学习率太低，影响了有效的收敛。 评估指标\u0026ndash;F1分数 二分类：\n阳性样本的真实数量：$TP + FN$\n阴性样本的真实数量：$FP + TN$\n$Precision$ （精确率，又称查准率）（值越高表示误诊数越低）： $$ Precision = \\frac {TP} {TP + FP} $$ $Recall$ （召回率，又称查全率）（值越高表示漏掉的病人越少）： $$ Recall = \\frac {TP} {TP + FN} $$ $Accuracy$ （准确率）（正确的样本占样本总数的比例）： $$ Accuracy = \\frac {TP + TN} {TP + FN + FP + TN} $$ $F1分数$ （综合考虑了精确率和召回率，认为他们同等重要）： $$ F1 = 2 \\times \\frac {Precision \\times Recall} {Precision + Recall} $$ 多分类问题：\n微观： $$ (查准率) \\quad microP = \\frac {TP_1 + TP_2} {TP_1 + FP_1 + TP_2 + FP_2} $$\n$$ (查全率) \\quad microR = \\frac {TP_1 + TP_2} {TP_1 + FN_1 + TP_2 + FN_2} $$\n$$ microF1 = 2 \\times \\frac {microP \\times microR} {microP + microR} $$\n宏观： $$ (查准率) \\quad macroP = \\frac {Precision1 + Precision2} {2} $$\n$$ (查全率) \\quad macroR = \\frac {Recall1 + Recall2} {2} $$\n$$ macroF1 = 2 \\times \\frac {macroP \\times macroR} {macroP + macroR} $$\n\u0026ldquo;评估指标的选择\u0026rdquo;\n当类别的分布相似时，可以使用准确率，当类别的分布不平衡时，F1分数是更好的评估指标。\n图像处理中的卷积神经网络 LeNet模型（1998） 由Yann Le Cun于1998年提出，奠定了卷积神经网络的基础，由两个卷积层、两个全连接层和一个输出层组成。激活函数采用softmax，池化层采用平均池化。该模型早期主要用于手写字符的识别和分类。\nALexNet模型（2012） 首次利用GPU进行网络加速训练 使用了ReLu激活函数，使用最大池化方法 使用了LRN局部相应归一化 在全连接层的前两层使用了Dropout随机失活神经元，减少过拟合 过拟合根本原因是特征维度过多，模型假设过于复杂，参数过多而训练数据过少，噪声多，导致拟合的函数完美的预测了训练集而对测试集预测结果差。\n卷积计算公式：\n$$ N = \\frac {(W - F + 2P)} {S} + 1 $$\nAlexNet 网络的具体实现\n原图输入224x224 实际上进行了随机裁剪，实际大小为227x227\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 from keras.layers import Input, Conv2D, MaxPooling2D, Flatten, Dense, deserialize from keras.models import Model def ALexNet(input_shape=None, num_classes=100): img_input = Input(shape=input_shape) # Block 1 # (227, 227, 3) --\u0026gt; (27, 27, 96) x = Conv2D(96, (11, 11), activation=\u0026#39;relu\u0026#39;, padding=\u0026#39;valid\u0026#39;, strides=(4, 4), name=\u0026#39;conv1\u0026#39;)(img_input) # (55, 55, 96) x = MaxPooling2D((3, 3), strides=(2, 2), name=\u0026#39;maxpool1\u0026#39;)(x) # Block 2 # (27, 27, 96) --\u0026gt; (13, 13, 256) x = Conv2D(256, (5, 5), activation=\u0026#39;relu\u0026#39;, padding=\u0026#39;same\u0026#39;, strides=(1, 1), name=\u0026#39;conv2\u0026#39;)(x) # (27, 27, 256) x = MaxPooling2D((3, 3), strides=(2, 2), name=\u0026#39;maxpool2\u0026#39;)(x) # Block 3 # (13, 13, 256) --\u0026gt; (13, 13, 384) x = Conv2D(384, (3, 3), activation=\u0026#39;relu\u0026#39;, padding=\u0026#39;same\u0026#39;, strides=(1, 1), name=\u0026#39;conv3\u0026#39;)(x) # Block 4 # (13, 13, 384) --\u0026gt; (13, 13, 384) x = Conv2D(384, (3, 3), activation=\u0026#39;relu\u0026#39;, padding=\u0026#39;same\u0026#39;, strides=(1, 1), name=\u0026#39;conv4\u0026#39;)(x) # Block 5 # (13, 13, 384) --\u0026gt; (6, 6, 256) x = Conv2D(256, (3, 3), activation=\u0026#39;relu\u0026#39;, padding=\u0026#39;same\u0026#39;, strides=(1, 1), name=\u0026#39;conv5\u0026#39;)(x) # (13, 13, 256) x = MaxPooling2D((3, 3), strides=(2, 2), name=\u0026#39;maxpool3\u0026#39;)(x) # Block 6 # (6, 6, 256) --\u0026gt; (1, 4096) x = Flatten(name=\u0026#39;flatten\u0026#39;)(x) x = Dense(num_classes, activation=\u0026#39;softmax\u0026#39;, name=\u0026#39;predictions\u0026#39;)(x) inputs = img_input model = Model(inputs, x, name=\u0026#39;alexnet\u0026#39;) return model if __name__ == \u0026#39;__main__\u0026#39;: model = ALexNet(input_shape=(227, 227, 3)) model.summary() GoogleNet模型（2014） 通过引入Inception模块来增加网络宽度 引入1x1的卷积层来压缩通道数量，降低计算量，从而进一步增加网络深度 添加两个softmax辅助分类器，缓解梯度消失现象 Inception就是把多个卷积或池化操作放在一起组装成一个网络模块。\nVGGNet模型（2014） 使用多个3x3小尺寸卷积核和池化层构造深度卷积 在最后使用三层全连接层，用最后一层全连接层的输出作为分类的预测 成功证明了增加网络的深度，可以更好的学习图像中的特征模式 VGG16 网络的具体结构\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 from keras.models import Model from keras.layers import Input,Activation,Dropout,Reshape,Conv2D,MaxPooling2D,Dense,Flatten def VGG16(input_shape=None, classes=1000): img_input = Input(shape=input_shape) # Block 1 # (224, 224, 3) --\u0026gt; (112, 112, 64) x = Conv2D(64, (3, 3), activation=\u0026#39;relu\u0026#39;, padding=\u0026#39;same\u0026#39;, name=\u0026#39;block1_conv1\u0026#39;)(img_input) x = Conv2D(64, (3, 3), activation=\u0026#39;relu\u0026#39;, padding=\u0026#39;same\u0026#39;, name=\u0026#39;block1_conv2\u0026#39;)(x) x = MaxPooling2D((2, 2), strides=(2, 2), name=\u0026#39;block1_pool\u0026#39;)(x) # Block 2 # (112, 112, 64) --\u0026gt; (56, 56, 128) x = Conv2D(128, (3, 3), activation=\u0026#39;relu\u0026#39;, padding=\u0026#39;same\u0026#39;, name=\u0026#39;block2_conv1\u0026#39;)(x) x = Conv2D(128, (3, 3), activation=\u0026#39;relu\u0026#39;, padding=\u0026#39;same\u0026#39;, name=\u0026#39;block2_conv2\u0026#39;)(x) x = MaxPooling2D((2, 2), strides=(2, 2), name=\u0026#39;block2_pool\u0026#39;)(x) # Block 3 # (56, 56, 128) --\u0026gt; (28, 28, 256) x = Conv2D(256, (3, 3), activation=\u0026#39;relu\u0026#39;, padding=\u0026#39;same\u0026#39;, name=\u0026#39;block3_conv1\u0026#39;)(x) x = Conv2D(256, (3, 3), activation=\u0026#39;relu\u0026#39;, padding=\u0026#39;same\u0026#39;, name=\u0026#39;block3_conv2\u0026#39;)(x) x = Conv2D(256, (3, 3), activation=\u0026#39;relu\u0026#39;, padding=\u0026#39;same\u0026#39;, name=\u0026#39;block3_conv3\u0026#39;)(x) x = MaxPooling2D((2, 2), strides=(2, 2), name=\u0026#39;block3_pool\u0026#39;)(x) # Block 4 # (28, 28, 256) --\u0026gt; (14, 14, 512) x = Conv2D(512, (3, 3), activation=\u0026#39;relu\u0026#39;, padding=\u0026#39;same\u0026#39;, name=\u0026#39;block4_conv1\u0026#39;)(x) x = Conv2D(512, (3, 3), activation=\u0026#39;relu\u0026#39;, padding=\u0026#39;same\u0026#39;, name=\u0026#39;block4_conv2\u0026#39;)(x) x = Conv2D(512, (3, 3), activation=\u0026#39;relu\u0026#39;, padding=\u0026#39;same\u0026#39;, name=\u0026#39;block4_conv3\u0026#39;)(x) x = MaxPooling2D((2, 2), strides=(2, 2), name=\u0026#39;block4_pool\u0026#39;)(x) # Block 5 # (14, 14, 512) --\u0026gt; (7, 7, 512) x = Conv2D(512, (3, 3), activation=\u0026#39;relu\u0026#39;, padding=\u0026#39;same\u0026#39;, name=\u0026#39;block5_conv1\u0026#39;)(x) x = Conv2D(512, (3, 3), activation=\u0026#39;relu\u0026#39;, padding=\u0026#39;same\u0026#39;, name=\u0026#39;block5_conv2\u0026#39;)(x) x = Conv2D(512, (3, 3), activation=\u0026#39;relu\u0026#39;, padding=\u0026#39;same\u0026#39;, name=\u0026#39;block5_conv3\u0026#39;)(x) x = MaxPooling2D((2, 2), strides=(2, 2), name=\u0026#39;block5_pool\u0026#39;)(x) # 对结果进行平铺 x = Flatten(name=\u0026#39;flatten\u0026#39;)(x) # 两次神经元为4096的全连接层 x = Dense(4096, activation=\u0026#39;relu\u0026#39;, name=\u0026#39;fc1\u0026#39;)(x) x = Dense(4096, activation=\u0026#39;relu\u0026#39;, name=\u0026#39;fc2\u0026#39;)(x) # 再全连接到1000维，用以分类任务 x = Dense(classes, activation=\u0026#39;softmax\u0026#39;, name=\u0026#39;predictions\u0026#39;)(x) inputs = img_input model = Model(inputs, x, name=\u0026#39;vgg16\u0026#39;) return model if __name__ == \u0026#39;__main__\u0026#39;: model = VGG16(intput_shape=(224, 224, 3)) model.summary() SSD模型（2016） SSD是基于一个前向传播反馈的CNN网络，属于one-stage类型。\n对多尺度特征图进行检测 设置不同长宽比的先验框 基本的SSD模型是在VGG网络模型的基础上构建的，通过融合不同卷积层的特征图来增强网络对特征的表达能力，采用多尺度卷积检测的方法来进行目标检测其结构如图所示：\n该模型基于VGG模型(改进版)来提取特征，将各级的卷积特征图作为该一级的特征表示，不同卷积级别的图像卷积特征描述了不同的语义，卷积层越深表达的图像特征信息级别越高。SSD模型中特征的提取采用的是逐层提取并抽象化的思想，低层的特征主要对应于占比较小的目标，高层的特征主要对应于占比较大的目标的抽象化的信息。 基本的SSD模型通过金字塔特征层进行特征提取，且各特征层之间相互独立，没有目标信息的相互补充，低特征层仅有Conv4_3层用于检测占比小的目标因而在缺乏充足的特征信息的情况下存在特征提取不充分的问题，因而导致对小型目标的识别效果一般。\nSSD 网络的具体结构：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 import keras.backend as K import numpy as np from keras.engine.topology import InputSpec, Layer from keras.layers import (Activation, Concatenate, Conv2D, Flatten, Input, Reshape) from keras.models import Model from vgg import VGG16 class Normalize(Layer): def __init__(self, scale, **kwargs): self.axis = 3 self.scale = scale super(Normalize, self).__init__(**kwargs) def build(self, input_shape): self.input_spec = [InputSpec(shape=input_shape)] shape = (input_shape[self.axis],) init_gamma = self.scale * np.ones(shape) self.gamma = K.variable(init_gamma, name=\u0026#39;{}_gamma\u0026#39;.format(self.name)) self.trainable_weights = [self.gamma] def call(self, x, mask=None): output = K.l2_normalize(x, self.axis) output *= self.gamma return output def SSD300(input_shape, num_classes=21): # ------ 输入尺寸(300, 300, 3) ------ # input_tensor = Input(shape=input_shape) net = VGG16(input_tensor) # ------- 对主干网络提取到的有效特征进行处理 ------- # # 对conv4_3的通道进行l2标准化处理 # (38, 38, 512) net[\u0026#39;conv4_3_norm\u0026#39;] = Normalize(20, name=\u0026#39;conv4_3_norm\u0026#39;)(net[\u0026#39;conv4_3\u0026#39;]) num_anchors = 4 # 对预测框的处理 # num_anchors表示每个网格点先验框的数量 # 4 是x,y(框中心偏移),h,w(框的高和宽)的调整 net[\u0026#39;conv4_3_norm_mbox_loc\u0026#39;] = Conv2D(num_anchors * 4, kernel_size=(3,3), padding=\u0026#39;same\u0026#39;, name=\u0026#39;conv4_3_norm_mbox_loc\u0026#39;)(net[\u0026#39;conv4_3_norm\u0026#39;]) net[\u0026#39;conv4_3_norm_mbox_loc_flat\u0026#39;] = Flatten(name=\u0026#39;conv4_3_norm_mbox_loc_flat\u0026#39;)(net[\u0026#39;conv4_3_norm_mbox_loc\u0026#39;]) # num_classes是所分的类 net[\u0026#39;conv4_3_norm_mbox_conf\u0026#39;] = Conv2D(num_anchors * num_classes, kernel_size=(3,3), padding=\u0026#39;same\u0026#39;,name=\u0026#39;conv4_3_norm_mbox_conf\u0026#39;)(net[\u0026#39;conv4_3_norm\u0026#39;]) net[\u0026#39;conv4_3_norm_mbox_conf_flat\u0026#39;] = Flatten(name=\u0026#39;conv4_3_norm_mbox_conf_flat\u0026#39;)(net[\u0026#39;conv4_3_norm_mbox_conf\u0026#39;]) # 对fc7层进行处理 # (19, 19, 1024) num_anchors = 6 # 预测框的处理 # 4 是x,y,h,w的调整 net[\u0026#39;fc7_mbox_loc\u0026#39;] = Conv2D(num_anchors * 4, kernel_size=(3,3),padding=\u0026#39;same\u0026#39;,name=\u0026#39;fc7_mbox_loc\u0026#39;)(net[\u0026#39;fc7\u0026#39;]) net[\u0026#39;fc7_mbox_loc_flat\u0026#39;] = Flatten(name=\u0026#39;fc7_mbox_loc_flat\u0026#39;)(net[\u0026#39;fc7_mbox_loc\u0026#39;]) # num_classes是所分的类 net[\u0026#39;fc7_mbox_conf\u0026#39;] = Conv2D(num_anchors * num_classes, kernel_size=(3,3),padding=\u0026#39;same\u0026#39;,name=\u0026#39;fc7_mbox_conf\u0026#39;)(net[\u0026#39;fc7\u0026#39;]) net[\u0026#39;fc7_mbox_conf_flat\u0026#39;] = Flatten(name=\u0026#39;fc7_mbox_conf_flat\u0026#39;)(net[\u0026#39;fc7_mbox_conf\u0026#39;]) # 对conv6_2进行处理 # (10, 10, 512) num_anchors = 6 # 预测框的处理 # 4 是x,y,h,w的调整 net[\u0026#39;conv6_2_mbox_loc\u0026#39;] = Conv2D(num_anchors * 4, kernel_size=(3,3), padding=\u0026#39;same\u0026#39;,name=\u0026#39;conv6_2_mbox_loc\u0026#39;)(net[\u0026#39;conv6_2\u0026#39;]) net[\u0026#39;conv6_2_mbox_loc_flat\u0026#39;] = Flatten(name=\u0026#39;conv6_2_mbox_loc_flat\u0026#39;)(net[\u0026#39;conv6_2_mbox_loc\u0026#39;]) # num_classes是所分的类 net[\u0026#39;conv6_2_mbox_conf\u0026#39;] = Conv2D(num_anchors * num_classes, kernel_size=(3,3), padding=\u0026#39;same\u0026#39;,name=\u0026#39;conv6_2_mbox_conf\u0026#39;)(net[\u0026#39;conv6_2\u0026#39;]) net[\u0026#39;conv6_2_mbox_conf_flat\u0026#39;] = Flatten(name=\u0026#39;conv6_2_mbox_conf_flat\u0026#39;)(net[\u0026#39;conv6_2_mbox_conf\u0026#39;]) # 对conv7_2进行处理 # (5, 5, 256) num_anchors = 6 # 预测框的处理 # 4 是x,y,h,w的调整 net[\u0026#39;conv7_2_mbox_loc\u0026#39;] = Conv2D(num_anchors * 4, kernel_size=(3,3), padding=\u0026#39;same\u0026#39;,name=\u0026#39;conv7_2_mbox_loc\u0026#39;)(net[\u0026#39;conv7_2\u0026#39;]) net[\u0026#39;conv7_2_mbox_loc_flat\u0026#39;] = Flatten(name=\u0026#39;conv7_2_mbox_loc_flat\u0026#39;)(net[\u0026#39;conv7_2_mbox_loc\u0026#39;]) # num_classes是所分的类 net[\u0026#39;conv7_2_mbox_conf\u0026#39;] = Conv2D(num_anchors * num_classes, kernel_size=(3,3), padding=\u0026#39;same\u0026#39;,name=\u0026#39;conv7_2_mbox_conf\u0026#39;)(net[\u0026#39;conv7_2\u0026#39;]) net[\u0026#39;conv7_2_mbox_conf_flat\u0026#39;] = Flatten(name=\u0026#39;conv7_2_mbox_conf_flat\u0026#39;)(net[\u0026#39;conv7_2_mbox_conf\u0026#39;]) # 对conv8_2进行处理 # (3, 3, 256) num_anchors = 4 # 预测框的处理 # 4是x,y,h,w的调整 net[\u0026#39;conv8_2_mbox_loc\u0026#39;] = Conv2D(num_anchors * 4, kernel_size=(3,3), padding=\u0026#39;same\u0026#39;,name=\u0026#39;conv8_2_mbox_loc\u0026#39;)(net[\u0026#39;conv8_2\u0026#39;]) net[\u0026#39;conv8_2_mbox_loc_flat\u0026#39;] = Flatten(name=\u0026#39;conv8_2_mbox_loc_flat\u0026#39;)(net[\u0026#39;conv8_2_mbox_loc\u0026#39;]) # num_classes是所分的类 net[\u0026#39;conv8_2_mbox_conf\u0026#39;] = Conv2D(num_anchors * num_classes, kernel_size=(3,3), padding=\u0026#39;same\u0026#39;,name=\u0026#39;conv8_2_mbox_conf\u0026#39;)(net[\u0026#39;conv8_2\u0026#39;]) net[\u0026#39;conv8_2_mbox_conf_flat\u0026#39;] = Flatten(name=\u0026#39;conv8_2_mbox_conf_flat\u0026#39;)(net[\u0026#39;conv8_2_mbox_conf\u0026#39;]) # 对conv9_2进行处理 # (1, 1, 256) num_anchors = 4 # 预测框的处理 # 4是x,y,h,w的调整 net[\u0026#39;conv9_2_mbox_loc\u0026#39;] = Conv2D(num_anchors * 4, kernel_size=(3,3), padding=\u0026#39;same\u0026#39;,name=\u0026#39;conv9_2_mbox_loc\u0026#39;)(net[\u0026#39;conv9_2\u0026#39;]) net[\u0026#39;conv9_2_mbox_loc_flat\u0026#39;] = Flatten(name=\u0026#39;conv9_2_mbox_loc_flat\u0026#39;)(net[\u0026#39;conv9_2_mbox_loc\u0026#39;]) # num_classes是所分的类 net[\u0026#39;conv9_2_mbox_conf\u0026#39;] = Conv2D(num_anchors * num_classes, kernel_size=(3,3), padding=\u0026#39;same\u0026#39;,name=\u0026#39;conv9_2_mbox_conf\u0026#39;)(net[\u0026#39;conv9_2\u0026#39;]) net[\u0026#39;conv9_2_mbox_conf_flat\u0026#39;] = Flatten(name=\u0026#39;conv9_2_mbox_conf_flat\u0026#39;)(net[\u0026#39;conv9_2_mbox_conf\u0026#39;]) # ------- 将所有结果进行堆叠 ------- # net[\u0026#39;mbox_loc\u0026#39;] = Concatenate(axis=1, name=\u0026#39;mbox_loc\u0026#39;)([net[\u0026#39;conv4_3_norm_mbox_loc_flat\u0026#39;], net[\u0026#39;fc7_mbox_loc_flat\u0026#39;], net[\u0026#39;conv6_2_mbox_loc_flat\u0026#39;], net[\u0026#39;conv7_2_mbox_loc_flat\u0026#39;], net[\u0026#39;conv8_2_mbox_loc_flat\u0026#39;], net[\u0026#39;conv9_2_mbox_loc_flat\u0026#39;]]) net[\u0026#39;mbox_conf\u0026#39;] = Concatenate(axis=1, name=\u0026#39;mbox_conf\u0026#39;)([net[\u0026#39;conv4_3_norm_mbox_conf_flat\u0026#39;], net[\u0026#39;fc7_mbox_conf_flat\u0026#39;], net[\u0026#39;conv6_2_mbox_conf_flat\u0026#39;], net[\u0026#39;conv7_2_mbox_conf_flat\u0026#39;], net[\u0026#39;conv8_2_mbox_conf_flat\u0026#39;], net[\u0026#39;conv9_2_mbox_conf_flat\u0026#39;]]) # (8732, 4) net[\u0026#39;mbox_loc\u0026#39;] = Reshape((-1, 4), name=\u0026#39;mbox_loc_final\u0026#39;)(net[\u0026#39;mbox_loc\u0026#39;]) # (8732, 21) net[\u0026#39;mbox_conf\u0026#39;] = Reshape((-1, num_classes), name=\u0026#39;mbox_conf_logits\u0026#39;)(net[\u0026#39;mbox_conf\u0026#39;]) net[\u0026#39;mbox_conf\u0026#39;] = Activation(\u0026#39;softmax\u0026#39;, name=\u0026#39;mbox_conf_final\u0026#39;)(net[\u0026#39;mbox_conf\u0026#39;]) # (8732, 25) net[\u0026#39;predictions\u0026#39;] = Concatenate(axis =-1, name=\u0026#39;predictions\u0026#39;)([net[\u0026#39;mbox_loc\u0026#39;], net[\u0026#39;mbox_conf\u0026#39;]]) model = Model(net[\u0026#39;input\u0026#39;], net[\u0026#39;predictions\u0026#39;]) return model 总结而言，SSD是把一张图片划分为不同的网格，当某一物体的中心点落在这个区域，这个物体就由这个网格来确定。\nMobileNet（2017） MobileNetV1 Deptwise Convolution(深度可分离卷积)(大大减少运算量和参数数量) 标准的卷积网络结构：\n深度可分离卷积网络结构：\n当输入特征图的 shape 是$D_F \\times D_F \\times M$，其中 $M$ 为通道数，输出特征图的 shape 为$D_G \\times D_G \\times N$，通道数为 $N$ ，标准卷积核的尺寸为$D_k \\times D_k \\times M$时，卷积核的参与个数为 $D_k \\cdot D_k \\cdot M \\cdot N$ 。深度可分离卷积一共分为两个步骤的卷积，其中 Depthwise Convolution 的卷积核为$D_k \\times D_k \\times 1$， Pointwise Convolution 的卷积核为$1 \\times 1 \\times M$。那么可以得出如下结论：\n标准卷积的运算量： $$ D_k \\cdot D_k \\cdot M \\cdot N \\cdot D_F \\cdot D_F = D_F \\cdot D_F \\cdot M \\cdot (D^2_K \\cdot N) $$ 深度可分离卷积的运算量： $$ D_k \\cdot D_k \\cdot M \\cdot D_F \\cdot D_F + M \\cdot N \\cdot D_F \\cdot D_F = D_F \\cdot D_F \\cdot M \\cdot (D^2_K + N) $$ 运算量对比： $$ \\frac {D_F \\cdot D_F \\cdot M \\cdot (D^2_K + N)} {D_F \\cdot D_F \\cdot M \\cdot (D^2_k \\cdot N)} = \\frac {1} {N} + \\frac {1} {D^2_K} $$\n深度可分离卷积的tensorflow代码实现：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 from keras import layers from keras import backend \u0026#34;\u0026#34;\u0026#34; DepthwiseConv2D的原函数定义: tf.keras.layers.DepthwiseConv2D( kernel_size, strides=(1, 1), padding=\u0026#34;valid\u0026#34;, depth_multiplier=1, data_format=None, dilation_rate=(1, 1), # 膨胀率 activation=None, use_bias=True, # 是否使用偏置向量 depthwise_initializer=\u0026#34;glorot_uniform\u0026#34;, bias_initializer=\u0026#34;zeros\u0026#34;, # 偏置向量的初始值 depthwise_regularizer=None, bias_regularizer=None, activity_regularizer=None, depthwise_constraint=None, bias_constraint=None, **kwargs ) \u0026#34;\u0026#34;\u0026#34; def _depthwise_conv_block( inputs, pointwise_conv_filters, alpha, depth_multiplier=1, strides=(1, 1), block_id=1): \u0026#34;\u0026#34;\u0026#34;通道的处理channel\u0026#34;\u0026#34;\u0026#34; channel_axis = 1 if backend.image_data_format() == \u0026#34;channels_first\u0026#34; else -1 \u0026#34;\u0026#34;\u0026#34;alpha超参数\u0026#34;\u0026#34;\u0026#34; pointwise_conv_filters = int(pointwise_conv_filters * alpha) \u0026#34;\u0026#34;\u0026#34;步长的处理padding\u0026#34;\u0026#34;\u0026#34; if strides == (1, 1): x = inputs else: x = layers.ZeroPadding2D(((0, 1), (0, 1)), name=\u0026#34;conv_pad_%d\u0026#34; % block_id)(inputs) \u0026#34;\u0026#34;\u0026#34;逐通道卷积\u0026#34;\u0026#34;\u0026#34; x = layers.DepthwiseConv2D( (3, 3), padding=\u0026#34;same\u0026#34; if strides == (1, 1) else \u0026#34;valid\u0026#34;, depth_multiplier=depth_multiplier, strides=strides, use_bias=False, name=\u0026#34;conv_dw_%d\u0026#34; % block_id )(x) x = layers.BatchNormalization(axis=channel_axis, name=\u0026#34;conv_dw_%d_bn\u0026#34; % block_id)(x) x = layers.ReLU(6.0, name=\u0026#34;conv_dw_%d_relu\u0026#34; % block_id)(x) \u0026#34;\u0026#34;\u0026#34;逐点卷积\u0026#34;\u0026#34;\u0026#34; x = layers.Conv2D( pointwise_conv_filters, (1, 1), padding=\u0026#34;same\u0026#34;, use_bias=False, strides=(1, 1), name=\u0026#34;conv_pw_%d\u0026#34; % block_id )(x) \u0026#34;\u0026#34;\u0026#34;Batch Normalization是2015年一篇论文中提出的数据归一化方法，往往用在深度神经网络中激活层之前。 其作用可以加快模型训练时的收敛速度，使得模型训练过程更加稳定，避免梯度爆炸或者梯度消失。并且起到一定的正则化作用，几乎代替了Dropout。\u0026#34;\u0026#34;\u0026#34; x = layers.BatchNormalization(axis=channel_axis, name=\u0026#34;conv_pw_%d_bn\u0026#34; % block_id)(x) return layers.ReLU(6.0, name=\u0026#34;conv_pw_%d_relu\u0026#34; % block_id)(x) \u0026ldquo;Conv2D 和 Depthwise_conv2D的区别\u0026rdquo;\nConv2d在每个通道上卷积，然后求和，Depthwise_conv2D卷积，不求和。\nDepthwise_conv2D的输出维度和输入维度始终是一致的。\n标准卷积 深度可分离卷积 运算特点 每个卷积核的通道与输入通道相同，每个通道单独做卷积运算然后相加 DW卷积：一个卷积核只有一个通道，单独负责一个通道\nPW卷积：将上一步的特征图在通道方向上进行扩展 超参数 $\\alpha$ $\\rho$ $\\alpha$ 宽度系数，对网络中每一层卷积的通道数乘以 $\\alpha$ 取值范围[0,1]，比较典型的值为1、0.75、0.5、0.35 $$ 计算量: \\qquad D_k \\cdot D_k \\cdot \\alpha M \\cdot D_F \\cdot D_F + \\alpha M \\cdot \\alpha N \\cdot D_F \\cdot D_F $$\n$\\rho$ 分辨率系数，只改变网络的计算量而不影响网络的参数量 $$ 计算量: \\qquad D_k \\cdot D_k \\cdot \\alpha M \\cdot \\rho D_F \\cdot \\rho D_F + \\alpha M \\cdot \\alpha N \\cdot \\rho D_F \\cdot \\rho D_F $$\nDepthWise部分的卷积核容易废掉，即卷积核参数大部分为零。（很重要的一个原因是因为 ReLU 激活函数对0值的梯度是0，后续无论怎么迭代这个节点都不会恢复，即“废掉了”）\n\u0026ldquo;你知道吗？\u0026rdquo;\n深度可分离卷积将一个标准卷积分割成了两个卷积（逐深度，逐点），因此减小了参数量，对应也减小了总计算量。 深度可分离卷积总计算量变小了，但是深度可分离卷积的层数变多了。\nGPU是并行处理大规模数据(矩阵内积)运算的平台，而CPU则更倾向于对数据串行计算。\n因此影响GPU总运算时间的主导因素一般是网络的层数。\n而影响CPU总运算时间的主导因素是总计算量。\n所以才会出现MobileNet在某些计算能力有限的CPU平台上速度竟然高于某些GPU平台上的速度。\nMobileNet的tensorflow代码实现：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 from keras import backend from keras import layers from keras.models import Model from keras.applications import imagenet_utils from keras.applications.efficientnet import block def MobileNet(input_shape, alpha=1.0, depth_multiplier=1, dropout=None, classes=1000): img_input = layers.Input(shape=input_shape) # (224, 224, 3) x = _conv_block(img_input, 32, alpha, strides=(2, 2)) # (112, 112, 32) x = _depthwise_conv_block(x, 64, alpha, depth_multiplier, block_id=1) # (112, 112, 64) x = _depthwise_conv_block(x, 128, alpha, depth_multiplier, strides=(2, 2), block_id=2) # (56, 56, 64) x = _depthwise_conv_block(x, 128, alpha, depth_multiplier, block_id=3) # (56, 56 ,128) x = _depthwise_conv_block(x, 256, alpha, depth_multiplier, strides=(2, 2), block_id=4) # (28, 28, 256) x = _depthwise_conv_block(x, 256, alpha, depth_multiplier, block_id=5) # (28, 28, 256) x = _depthwise_conv_block(x, 512, alpha, depth_multiplier, strides=(2, 2), block_id=6) # (14, 14, 512) x = _depthwise_conv_block(x, 512, alpha, depth_multiplier, block_id=7) x = _depthwise_conv_block(x, 512, alpha, depth_multiplier, block_id=8) x = _depthwise_conv_block(x, 512, alpha, depth_multiplier, block_id=9) x = _depthwise_conv_block(x, 512, alpha, depth_multiplier, block_id=10) x = _depthwise_conv_block(x, 512, alpha, depth_multiplier, block_id=11) # (14, 14, 512) x = _depthwise_conv_block(x, 1024, alpha, depth_multiplier, strides=(2, 2), block_id=12) # (7, 7, 1024) x = _depthwise_conv_block(x, 1024, alpha, depth_multiplier, block_id=13) # (7, 7, 1024) x = layers.GlobalAveragePooling2D(keepdims=True)(x) x = layers.Dropout(dropout, name=\u0026#34;drpout\u0026#34;)(x) x = layers.Conv2D(classes, (1, 1), padding=\u0026#34;same\u0026#34;, name=\u0026#34;conv_preds\u0026#34;)(x) x = layers.Reshape((classes,), name=\u0026#34;reshape_2\u0026#34;)(x) x = layers.Activation(activation=\u0026#34;softmax\u0026#34;, name=\u0026#34;predictions\u0026#34;)(x) model = Model(img_input, x, name=\u0026#34;mobilenet\u0026#34;) return model def _conv_block(inputs, filters, alpha, kernel=(3, 3), strides=(1, 1)): filters = int(filters * alpha) x = layers.Conv2D( filters, kernel, padding=\u0026#34;same\u0026#34;, use_bias=False, strides=strides, name=\u0026#34;conv1\u0026#34; )(inputs) x = layers.BatchNormalization(name=\u0026#34;conv1_bn\u0026#34;)(x) return layers.ReLU(6.0, name=\u0026#34;conv1_relu\u0026#34;)(x) def _depthwise_conv_block( inputs, pointwise_conv_filters, alpha, depth_multiplier=1, strides=(1, 1), block_id=1): \u0026#34;\u0026#34;\u0026#34;通道的处理channel\u0026#34;\u0026#34;\u0026#34; channel_axis = 1 if backend.image_data_format() == \u0026#34;channels_first\u0026#34; else -1 \u0026#34;\u0026#34;\u0026#34;alpha超参数\u0026#34;\u0026#34;\u0026#34; pointwise_conv_filters = int(pointwise_conv_filters * alpha) \u0026#34;\u0026#34;\u0026#34;步长的处理padding\u0026#34;\u0026#34;\u0026#34; if strides == (1, 1): x = inputs else: x = layers.ZeroPadding2D(((0, 1), (0, 1)), name=\u0026#34;conv_pad_%d\u0026#34; % block_id)(inputs) \u0026#34;\u0026#34;\u0026#34;逐通道卷积（处理长宽方向的信息）\u0026#34;\u0026#34;\u0026#34; x = layers.DepthwiseConv2D( (3, 3), padding=\u0026#34;same\u0026#34; if strides == (1, 1) else \u0026#34;valid\u0026#34;, depth_multiplier=depth_multiplier, strides=strides, use_bias=False, name=\u0026#34;conv_dw_%d\u0026#34; % block_id )(x) x = layers.BatchNormalization(axis=channel_axis, name=\u0026#34;conv_dw_%d_bn\u0026#34; % block_id)(x) x = layers.ReLU(6.0, name=\u0026#34;conv_dw_%d_relu\u0026#34; % block_id)(x) \u0026#34;\u0026#34;\u0026#34;逐点卷积（处理跨通道方向的信息）\u0026#34;\u0026#34;\u0026#34; x = layers.Conv2D( pointwise_conv_filters, (1, 1), padding=\u0026#34;same\u0026#34;, use_bias=False, strides=(1, 1), name=\u0026#34;conv_pw_%d\u0026#34; % block_id )(x) x = layers.BatchNormalization(axis=channel_axis, name=\u0026#34;conv_pw_%d_bn\u0026#34; % block_id)(x) return layers.ReLU(6.0, name=\u0026#34;conv_pw_%d_relu\u0026#34; % block_id)(x) if __name__ == \u0026#39;__main__\u0026#39;: model = MobileNet(input_shape=(224, 224, 3)) model.summary() MobileNet v2 Inverted Residuals(倒残差结构)\n倒残差结构是从ResNet中的残差结构而来的。ResNet中Residuals结构中，先用1x1的卷积实现了降维，然后通过3x3卷积，最后通过1x1卷积实现升维，即两头大中间小。\n而在MobileNetV2中，先用1x1的卷积升维，然后将3x3卷积换为3x3DW卷积，再用1x1的卷积实现降维，即两头小中间大。\nMobileNetV2的倒残差结构示意图：\nstateDiagram\rDirection LR\r[*] --\u003e Conv1x1,ReLU6 :升维\rConv1x1,ReLU6 --\u003e Dwise3x3,ReLU6\rDwise3x3,ReLU6 --\u003e conv1x1,Linear conv1x1,Linear --\u003e Add :降维，线性激活\r[*] --\u003e Add :残差连接\rAdd --\u003e [*]\rReLU6激活函数: $$ y = ReLU6(x) = min(max(x, 0), 6) $$\nLinear Bottlenecks(线性瓶颈层)\n作者发现当信息从高维空间经过非线性映射到低维空间时，会发生信息坍塌，所以在倒残差结构进行降维操作的时候，使用了线性激活函数（实现方式就是不使用激活函数）。\n\u0026ldquo;思考\u0026rdquo;\n之所以使用倒残差结构，和线性瓶颈层，是因为作者通过数学证明的方式，得出了在降维过度时，ReLU会造成大量的信息丢失，即升维之后更容易保持可逆\nMobileNetV2的tensorflow实现：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 from keras import backend from keras import layers from keras.models import Model from keras.applications import imagenet_utils def MobileNetV2(input_shape=None, alpha=1.0, classes=1000): img_input = layers.Input(shape=input_shape) channel_axis = 1 if backend.image_data_format() == \u0026#34;channels_first\u0026#34; else -1 first_block_filters = _make_divisible(32 * alpha, 8) # 输入(224, 224, 3) x = layers.Conv2D( first_block_filters, kernel_size=3, strides=(2, 2), padding=\u0026#34;same\u0026#34;, use_bias=False, name=\u0026#34;Conv1\u0026#34;, )(img_input) x = layers.BatchNormalization( axis=channel_axis, epsilon=1e-3, momentum=0.999, name=\u0026#34;bn_Conv1\u0026#34; )(x) x = layers.ReLU(6.0, name=\u0026#34;Conv1_relu\u0026#34;)(x) # (112, 112, 32) x = _inverted_res_block(x, filters=16, alpha=alpha, stride=1, expansion=1, block_id=0) # (112, 112, 16) x = _inverted_res_block(x, filters=24, alpha=alpha, stride=2, expansion=6, block_id=1) x = _inverted_res_block(x, filters=24, alpha=alpha, stride=1, expansion=6, block_id=2) # (56, 56, 24) x = _inverted_res_block(x, filters=32, alpha=alpha, stride=2, expansion=6, block_id=3) x = _inverted_res_block(x, filters=32, alpha=alpha, stride=1, expansion=6, block_id=4) x = _inverted_res_block(x, filters=32, alpha=alpha, stride=1, expansion=6, block_id=5) # (28, 28, 32) x = _inverted_res_block(x, filters=64, alpha=alpha, stride=2, expansion=6, block_id=6) x = _inverted_res_block(x, filters=64, alpha=alpha, stride=1, expansion=6, block_id=7) x = _inverted_res_block(x, filters=64, alpha=alpha, stride=1, expansion=6, block_id=8) x = _inverted_res_block(x, filters=64, alpha=alpha, stride=1, expansion=6, block_id=9) # (14, 14, 64) x = _inverted_res_block(x, filters=96, alpha=alpha, stride=1, expansion=6, block_id=10) x = _inverted_res_block(x, filters=96, alpha=alpha, stride=1, expansion=6, block_id=11) x = _inverted_res_block(x, filters=96, alpha=alpha, stride=1, expansion=6, block_id=12) # (14, 14, 96) x = _inverted_res_block(x, filters=160, alpha=alpha, stride=2, expansion=6, block_id=13) x = _inverted_res_block(x, filters=160, alpha=alpha, stride=1, expansion=6, block_id=14) x = _inverted_res_block(x, filters=160, alpha=alpha, stride=1, expansion=6, block_id=15) # (7, 7, 160) x = _inverted_res_block(x, filters=320, alpha=alpha, stride=1, expansion=6, block_id=16) # 宽度因子α if alpha \u0026gt; 1.0: last_block_filters = _make_divisible(1280 * alpha, 8) else: last_block_filters = 1280 # (7, 7, 320) x = layers.Conv2D( last_block_filters, kernel_size=1, use_bias=False, name=\u0026#34;Conv_1\u0026#34; )(x) x = layers.BatchNormalization( axis=channel_axis, epsilon=1e-3, momentum=0.999, name=\u0026#34;Conv_1_bn\u0026#34; )(x) x = layers.ReLU(6.0, name=\u0026#34;out_relu\u0026#34;)(x) # (7, 7, 1280) x = layers.GlobalAveragePooling2D()(x) imagenet_utils.validate_activation(\u0026#34;softmax\u0026#34;, \u0026#34;imagenet\u0026#34;) x = layers.Dense( classes, activation=\u0026#34;softmax\u0026#34;, name=\u0026#34;predictions\u0026#34; )(x) # 预测层(1, 1, classes) # 返回模型实例 return Model(img_input, x, name=f\u0026#34;mobilenetv2\u0026#34;) def _inverted_res_block(inputs, expansion, stride, alpha, filters, block_id): \u0026#34;\u0026#34;\u0026#34;倒残差结构\u0026#34;\u0026#34;\u0026#34; channel_axis = 1 if backend.image_data_format() == \u0026#34;channels_first\u0026#34; else -1 in_channels = backend.int_shape(inputs)[channel_axis] # 返回张量或变量的shape，作为int或者None条目的元组 pointwise_conv_filters = int(filters * alpha) # 确保最后一个1x1卷积上的滤波器个数可以被8整除 pointwise_filters = _make_divisible(pointwise_conv_filters, 8) x = inputs prefix = f\u0026#34;block_{block_id}_\u0026#34; if block_id: # 点卷积升维 x = layers.Conv2D( expansion * in_channels, kernel_size=1, padding=\u0026#34;same\u0026#34;, use_bias=False, activation=None, name=prefix + \u0026#34;expand\u0026#34;, )(x) x = layers.BatchNormalization( axis=channel_axis, epsilon=1e-3, momentum=0.999, name=prefix + \u0026#34;expand_BN\u0026#34;, )(x) x = layers.ReLU(6.0, name=prefix + \u0026#34;expand_relu\u0026#34;)(x) else: prefix = \u0026#34;expanded_conv_\u0026#34; # Dw卷积 if stride == 2: x = layers.ZeroPadding2D( padding=imagenet_utils.correct_pad(x, 3), name=prefix + \u0026#34;pad\u0026#34; )(x) x = layers.DepthwiseConv2D( kernel_size=3, strides=stride, activation=None, use_bias=False, padding=\u0026#34;same\u0026#34; if stride == 1 else \u0026#34;valid\u0026#34;, name=prefix + \u0026#34;depthwise\u0026#34;, )(x) x = layers.BatchNormalization( axis=channel_axis, epsilon=1e-3, momentum=0.999, name=prefix + \u0026#34;depthwise_BN\u0026#34;, )(x) x = layers.ReLU(6.0, name=prefix + \u0026#34;depthwise_relu\u0026#34;)(x) # 点卷积降维，线性激活函数（即None） x = layers.Conv2D( pointwise_filters, kernel_size=1, padding=\u0026#34;same\u0026#34;, use_bias=False, activation=None, name=prefix + \u0026#34;project\u0026#34;, )(x) x = layers.BatchNormalization( axis=channel_axis, epsilon=1e-3, momentum=0.999, name=prefix + \u0026#34;project_BN\u0026#34;, )(x) if in_channels == pointwise_filters and stride == 1: return layers.Add(name=prefix + \u0026#34;add\u0026#34;)([inputs, x]) return x def _make_divisible(v, divisor, min_value=None): if min_value is None: min_value = divisor new_v = max(min_value, int(v + divisor / 2) // divisor * divisor) # 确保向下舍入不会下降超过10% if new_v \u0026lt; 0.9 * v: new_v += divisor return new_v if __name__ == \u0026#39;__main__\u0026#39;: model = MobileNetV2(input_shape=(224, 224, 3)) model.summary() 待续未完。。。\n","date":"2023-05-11T05:59:13+08:00","image":"https://haoleng-wick.github.io/p/%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C%E8%A7%86%E8%A7%89%E5%9F%BA%E7%A1%80/cnn_total_hu14699074184501299526.jpg","permalink":"https://haoleng-wick.github.io/p/%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C%E8%A7%86%E8%A7%89%E5%9F%BA%E7%A1%80/","title":"神经网络视觉基础"},{"content":"传统机器视觉基础 传统计算机视觉 FAST特征点检测算法 FAST只是一种特征点检测算法，并不涉及特征点的特征描述。\nFAST的提出者Rosten等将 FAST角点 定义为：若某像素与其周围邻域内足够多的像素点相差较大，则该像素可能是角点。\n算法流程\n以像素$p$为中心，半径为3的圆上，有16个像素点 定义一个阈值，计算$p1$、$p5$、$p9$、$p13$与中心$p$的像素差，若它们的绝对值至少有三个超过阈值，则当作候选角点，否则不可能是角点 若$p$是候选点，则计算$p1$到$p16$这16个点与中心$p$点的像素差，若它们至少有连续9个超过阈值，则为角点，否则不可能是角点 对图像进行非极大值抑制（nms）：判断以特征点$p$为中心的一个邻域（如3x3或5x5）内，计算若有多个特征点，则判断每个特征点的$s$值（16个点与中心差值的绝对值总和），若$p$是邻域所有特征点中响应值最大的，则保留；否则，抑制。若邻域内只有一个特征点（角点），则保留。得分计算公式如下（公式中用$V$表示得分，$t$表示阈值）： $$ V = max \\begin{cases} \\sum{pixel values - p}, \u0026amp; \\text{if (value - p) \u0026gt; t} \\\\\\ \\sum{p - pixel values}, \u0026amp; \\text{if (p- value) \u0026gt; t} \\end{cases} $$\nBRIEF特征点描述算法 BRIEF是对已检测到的特征点进行描述，它是一种二进制编码的描述子，摈弃了利用区域灰度直方图描述特征点的传统方法，大大的加快了特征描述符建立的速度，同时也极大的降低了特征匹配的时间，是一种非常快速，很有潜力的算法。 由于BRIEF仅仅是特征描述子，所以事先要得到特征点的位置\n算法流程\n为减少噪声干扰，需对图像先进行滤波（如高斯滤波）\n以特征点为中心，取$S \\times S$的邻域窗口。在窗口内随机选取一对（两个）点，比较二者像素的大小，进行如下二进制赋值。 $$ \\iota(p;x,y):=\\begin{cases} 1,\u0026amp; \\text{$if p(x)\u0026lt;p(y)$ } \\\\\\ 0,\u0026amp; \\text{$otherwise$} \\end{cases} $$ 其中，$p(x)$，$p(y)$分别是随机点$x=(u1,v1)$, $y=(u2,v2)$的像素值。\n在窗口中随机选取$N$对随机点，重复步骤2的二进制赋值，形成一个二进制编码，这个编码就是对特征点的描述，即特征描述子。（一般$N=256$）\n利用BPIEF特征进行配准\n经过特征点提取，对于一幅图中的每一个特征点，都得到了一个256bit的二进制编码。接下来对有相似或重叠部分的两幅图像进行配准。\n特征配对是利用汉明距离进行判决的： 两个特征编码对应bit位上相同元素的个数小于128的，一定不是配对的。 一幅图上特征点与另一幅图上特征编码对应bit位上相同元素的个数最多的特征点配成一对。 ORB算法（快速特征点提取和描述算法） ORB算法分为两部分，分别是特征点提取和特征点描述。特征提取是由FAST算法（前面已经学过）发展来的，特征点描述是根据BRIEF特征描述算法改进的。 算法流程\nORB对FAST的改进或者拓展，主要是为其增加了其尺度不变性以及旋转不变性。接下里来看一看怎么实现的。\n关键点提取\n通过FAST算法提取特征点 建立金字塔 设置一个比例因子$scale$（opencv默认取1.2）和金字塔层数$n$（通常取8），将原图像按比例因子缩小为$n$幅图像（ $I\u0026rsquo; = I/scale^k$ ） $k=1、2、3 \\dots n$幅不同比例的图像提取特征点总和作为原图的FAST特征点 定义特征点方向 ORB的论文中提出了一种利用灰度质心法来解决这个问题，通过计算一个矩来计算特征点以$r$为半径范围内的质心，特征点坐标到质心形成一个向量作为该特征点的方向。我们来看看具体怎么实现灰度质心法。 一个图像块（比如5x5的图像块），对应2x2的矩的元素表达为：\n$$ m_{pq} = \\sum_{x,y} \\ x^p y^q I(x,y) $$\n$x,y$ 分别为坐标值，$I(x,y)$为像素值而该图像的窗口质心为： $$ C = (\\frac{m_{10}}{m_{00}},\\frac{m_{01}}{m_{00}}) $$\n那么特征点与质心的夹角定义为FAST特征点的方向：\n$$ \\theta = arctan(m_{01},m_{10}) $$\n有了特征点的方向，继而实现旋转不变性。\n关键点描述\nORB选择了BRIEF作为特征描述方法，并对其进行改进使其加上旋转不变性并增加其可区分性。\nBRIEF描述子\nBRIEF算法计算出来的是一个二进制串的特征描述符。 它是在每一个特征点的邻域内，选择n对像素点$8pi、qi（i=1,2,…,n）$。然后比较每个点对的灰度值的大小。如果$I(pi)\u0026gt; I(qi)$，则生成二进制串中的1，否则为0。所有的点对都进行比较，则生成长度为$n$的二进制串。一般$n$取128、256或512，通常取256。 为了增强抗噪性，一般会先对图像进行高斯平滑。ORB算子采用5x5的子窗口进行平滑。\n$n$个点对的选取方法\n在点周围选取点对$（p,q）$的方法\n$p$和$q$都符合$(0,S2/25)$的高斯分布；\n$p$符合$(0,S2/25)$的高斯分布，而$q$符合$(0,S2/100)$的高斯分布； 在空间量化极坐标下的离散位置随机采样；\n把$p$固定为$(0,0)$，$q$在周围平均采样。\nBRIEF算法改进\n增加其旋转不变性，选点方式改进\nsteered BRIEF增加其旋转不变性\n所谓steered BRIEF就是对挑选出的点对加上一个旋转角度$θ$。对于任何一个特征点来说，它的BRIEF描述子是一个长度为𝑛的二值码串，这个二值码串是由特征点邻域𝑛个点对生成的，我们现在讲这2𝑛个点(𝑥𝑖,𝑦𝑖),𝑖=1,2,\u0026hellip;..,2𝑛组成一个矩阵𝑆: $$ S = \\left[ \\begin{matrix} x_1 \u0026amp; x_2 \u0026amp; \u0026hellip; \u0026amp; x_{2n}\\\\\\ y_1 \u0026amp; y_2 \u0026amp; \u0026hellip; \u0026amp; y_{2n} \\end{matrix} \\right] $$\n使用邻域方向$\\theta$和对应的旋转矩阵$R_\\theta$，构建$S$的一个校正版本$S_\\theta$： $$ S_\\theta = R_\\theta S $$ 其中\n$$ R_\\theta = \\left[ \\begin{matrix} \\cos\\theta \u0026amp; sin\\theta \\\\\\ -sin\\theta \u0026amp; cos\\theta \\end{matrix} \\right] $$\nsteered BRIEF加入了旋转不变性，但同时特征描述量的可区分行就下降了,所以就有了ORB作者提出的rBRIEF\nrBRIEF增加其可区分性 ORB使用统计学习的方法来重新选择点对集合，目的是增大其特征描述量的可区分行。\nOpenCV大致思路： 一个就是将提取的角点信息进行格式化，输出为numpy数组： 1 2 3 4 5 6 7 8 9 10 11 ## 提取图像ORB特征并转化为numpy数组 def extraORBfromImg(ORB, img): keypoints, desc = ORB.detectAndCompute(img, mask=None) # 关键点检测 ## 特征点信息 axis = np.array([kp.pt for kp in keypoints]) # 特征点图像坐标 scale = np.array([kp.octave+1 for kp in keypoints])# 特征点尺度(在哪一层金字塔) direct = np.array([kp.angle*np.pi/180 for kp in keypoints]) # 特征点方向(弧度) ## 拼接 infos = np.array([scale,direct]).T cors_info = np.hstack([axis,infos]) return cors_info, desc 另一个就是进行图像间的角点匹配函数： 1 2 3 4 5 6 7 8 9 10 11 ## ORB特征BRIEF描述子匹配 def ORBMatch(BF, desc1, desc2): matches = BF.match(desc1, desc2) dist = np.array([mc.distance for mc in matches]) idx1 = np.array([mc.trainIdx for mc in matches]) idx0 = np.array([mc.queryIdx for mc in matches]) idx = np.array([idx0,idx1]).T ## 匹配点筛选，当描述子之间的距离大于两倍的最小距离时，认为匹配有误 min_dist = min(dist) filte_idx = np.where(dist \u0026lt;= max(2 * min_dist, 30))[0] return dist[filte_idx], idx[filte_idx,:] 由于ORBMatch函数返回的是所有点的匹配距离和匹配索引，因此必要时我们还需要一个函数计算匹配点对的坐标： 1 2 3 4 5 ## 获取图像对匹配点的坐标(一对) def findMatchCord(match_idx, cors1, cors2): left = cors1[match_idx[:,0], :2] right = cors2[match_idx[:,1], :2] return np.hstack([left, right]) 结果示例： NMS算法（非极大值抑制） 目标检测过程中在同一目标的位置上会产生大量的候选框，这些候选框相互之间可能会有重叠，此时我们需要利用非极大值抑制找到最佳的目标边界框，消除冗余的边界框。 流程一般如下：\n前提 ：目标边界框列表及其对应的置信度得分列表，设定阈值，阈值用来删除重叠较大的边界框。\nIOU ：intersection-over-union，即两个边界框的交集部分除以它们的并集。\n根据置信度得分进行排序 选择置信度最高的边界框添加到最终的传输列表中，将其从边界框列表中删除 计算所有边界框的面积 计算置信度最高的边界框与其他候选框的loU 删除loU大于阈值的边界框 重复上述过程，直至边界框列表为空 RANSAC算法() 随机抽样一致算法 ，采用迭代的方式从一组包含离群的被观测数据中估算出数学模型的参数，RANSAC算法假设数据中包含正确数据和异常数据（或者噪声）。正确数据记为内点（inliers），异常数据记为外点（outliers）。同时RANSAC也假设，给定一组正确的数据，存在可以计算出符合这些数据的模型参数的方法。该算法核心思想就是随机性和假设性， 随机性 是根据正确数据出现概率去随机选取抽样数据，根据大数定律，随机性模拟可以近似得到正确结果。 假设性 是假设选取出的抽样数据都是正确数据，然后用这些正确数据通过问题满足的模型，去计算其他点，然后对这次结果进行一个评分。\nRANSAC的基本就假设是：\n数据由“局内点”组成，例如：数据的分布可以用一些模型来解释; “局外点”是不能是适应模型的数据; 除此之外的数据属于噪声。 RANSAC也做出了以下假设：给定一组（通常很小的）局内点，存在一个可以估计模型参数的过程；而该模型能够解释或者适用于局内点。\n算法流程：\n选择出可以估计出模型的最小数据集；(对于直线拟合来说就是两个点，对于计算Homography矩阵就是4个点) 使用这个数据集来计算出数据模型； 将所有数据带入这个模型，计算出“内点”的数目；(累加在一定误差范围内的适合当前迭代推出模型的数据) 比较当前模型和之前推出的最好的模型的“内点“的数量，记录最大“内点”数的模型参数和“内点”数； 重复1-4步，直到迭代结束或者当前模型已经足够好了(“内点数目大于一定数量”)。 RANSAC算法的输入是一组观测数据，一个可以解释或者适应于观测数据的参数化模型，一些可信的参数。\n输入 1 2 3 4 5 6 data\t// 一组观测数据 model\t// 适应于数据的模型 n\t// 适应于模型的最少数据个数，如果是直线的话，n=2 k\t// 算法的迭代次数 t\t// 用于决定数据是否应用于模型的阈值 d\t// 判定模型是否适用于数据集的数据个数，人为设定 输出 1 2 3 best_model\t// 跟数据最匹配的模型参数（如果没有好的模型，返回null） best_consensus_set\t// 估计出模型的数据点 best_error\t// 跟数据相关的估计出的模型错误 Kmeans算法 聚类与KMeans\n聚类是在事先并不知道任何样本标签的情况下，通过数据之间的内在关系把样本划分为若干类别，使得同类别样本之间的相似度高，不同类别之间的样本相似度低（即增大类内聚，减少类间距）。\nK均值聚类是最基础常用的聚类算法。它的基本思想是：通过迭代寻找K个簇（Cluster）的一种划分方案，使得聚类结果对应的损失函数最小。其中，损失函数可以定义为各个样本距离所属簇中心点的误差平方和： $$ J(c, \\mu) = \\sum _{i=1} ^{M} \\parallel x_i - \\mu _{c_i} \\parallel ^2 $$\n其中 $x_i$ 代表第 $i$ 个样本，$c_i$ 是 $x_i$ 所属的簇，$\\mu_{c_i}$ 代表簇对应的中心点，$M$是样本总数\n算法流程\nKMeans的核心目标是将给定的数据集划分成K个簇（K是超参），并给出每个样本数据对应的中心点。具体步骤非常简单，可以分为4步：\n数据预处理。主要是标准化、异常点过滤。 随机选取K个中心，记为 $\\mu_1^{(0)}$ , $\\mu_2^{(0)}$ ,……, $\\mu_k^{(0)}$ 定义损失函数: $$ J(c, \\mu) = \\sum _{i=1} ^{M} \\parallel x_i -\\mu _{c_i} \\parallel ^2 $$\n令 t = 0, 1, 2,……为迭代步数，重复如下过程直到 $J$ 收敛:\n对于每一个样本 $x_i$ ,将其分配到距离最近的中心 $$ c_i^t \u0026lt; - argmin_k \\parallel x_i - \\mu_k^t \\parallel ^2 $$ 对于每一个类中心k，重新计算该类的中心 $$ \\mu_k^{t+1} \u0026lt; -argmin_{\\mu} \\sum _{i:c_i^t = k} ^b \\parallel x_i - \\mu \\parallel ^2 $$\nKMeans最核心的部分就是先固定中心点，调整每个样本所属的类别来减少 $J$ ；再固定每个样本的类别，调整中心点继续减小$J$ 。两个过程交替循环， $J$ 单调递减直到最（极）小值，中心点和样本划分的类别同时收敛。\nKMeans 迭代示意图：\nKMeans算法步骤：\n随机初始化k个点作为簇质心； 将样本集中的每个点分配到一个簇中；计算每个点与质心之间的距离（常用欧式距离和余弦距离），并将其分配给距离最近的质心所对应的簇中； 更新簇的质心。每个簇的质心更新为该簇所有点的平均值； 反复迭代2 - 3 步骤，直到达到某个终止条件；（a. 达到指定的迭代次数；b. 簇心不再发生明显的变化，即收敛；c. 最小误差平方和SSE；） SSE(Sum of Square Error, 误差平方和)，SSE值越小表示数据点越接近于它们的质心，聚类效果也越好。\n$$ SSE = \\sum_{i = 1} ^ k \\sum_{x\\in C_i} (x-\\mu_i) ^2 $$\n$$ \\mu_i = \\frac{1}{|C_i|} \\sum_{x\\in{C_i}} x $$\n算法过程：\n点 $x = (x_1,x_2,……,x_n)$ 和点 $y = (y_1,y_2,……,y_n)$ 之间的欧氏距离为： $$ d(x,y) = \\sqrt{(x_1-y_1)^2 + (x_2-y_2)^2 +……+ (x_n-y_n)^2} $$\n两个向量 A 和 B，其余弦距离（即两向量夹角的余弦）由点积和向量长度给出，计算公式如下： $$ cos\\theta = \\frac{A * B}{|A|*|B|} = \\frac{\\sum_{i=1}^n A_i B_i} {\\sqrt{\\sum_{i=1}^n (A_i)^2} \\sqrt{\\sum_{i=1}^n (B_i)^2}} $$\n$$ dist(A,B) = 1 - cos(A,B) = \\frac{|A| _2 |B|_2 - A * B}{|A| |B|} $$\n式子中， $A_i$ 和 $B_i$ 分别代表向量A和B的各分量。\n算法优缺点\n优点：容易实现\n缺点：可能收敛到局部最小值，在大规模数据集上收敛较慢\n适用数据类型：数值型数据\n","date":"2023-05-06T03:20:13+08:00","image":"https://haoleng-wick.github.io/p/%E4%BC%A0%E7%BB%9F%E6%9C%BA%E5%99%A8%E8%A7%86%E8%A7%89%E5%9F%BA%E7%A1%80/R-C_hu5961029251031618139.png","permalink":"https://haoleng-wick.github.io/p/%E4%BC%A0%E7%BB%9F%E6%9C%BA%E5%99%A8%E8%A7%86%E8%A7%89%E5%9F%BA%E7%A1%80/","title":"传统机器视觉基础"},{"content":"无人机基础 四旋翼无人机基础 keywords : 构造函数、重载、启动脚本、uorb、创建线程、PID、PWM、PX4、APM、鲁棒性、\n四旋翼无人机的动力学模型 基本假设：\n四旋翼飞行器是均匀对称的刚体 四旋翼飞行器的质量和转动惯量不发生改变 四旋翼飞行器的几何中心与其重心重合 四旋翼飞行器只受重力和螺旋桨拉力 牛顿\u0026ndash;欧拉方程 : : 刚体运动 = 质心的平动 + 绕质心的转动\n质心的平动 : $F = m \\frac{dv}{dt}$\n绕质心的转动 : $M = J \\dot\\omega + \\omega \\times J\\omega$\n\u0026ldquo;x\u0026quot;表示的是矩阵乘法（即 叉乘 ）\n位置动力学模型 (合外力和速度的方程) 旋转矩阵 可以将机体坐标系( $O_bY_bZ_b$ )下表示的向量转变到地面坐标系( $O_eY_eZ_e$ )下表示:\n$$ R^e_b = \\left[ \\begin{matrix} \\cos\\theta\\cos\\psi \u0026amp; \\cos\\psi\\sin\\theta\\sin\\phi - \\sin\\psi\\cos\\phi \u0026amp; \\cos\\psi\\sin\\theta\\cos\\phi + \\sin\\psi\\sin\\phi \\\\\\ \\cos\\theta\\sin\\psi \u0026amp; \\sin\\psi\\sin\\theta\\sin\\phi + \\cos\\psi\\cos\\phi \u0026amp; \\sin\\psi\\sin\\theta\\cos\\phi - \\cos\\psi\\sin\\phi \\\\\\ -\\sin\\theta \u0026amp; \\sin\\phi\\cos\\theta \u0026amp; \\cos\\phi\\cos\\theta \\end{matrix} \\right] $$\n根据牛顿第二定律整理得：\n$$ \\dot v^e = g^e + R^e_b \\cdot \\frac{T^b}{m} $$\n即：\n$$ \\begin{cases} \\dot v_x = -\\frac{f}{m}(\\cos\\psi\\sin\\theta\\cos\\phi + \\sin\\psi\\sin\\phi) \\\\\\ \\dot v_y = -\\frac{f}{m}(\\sin\\psi\\sin\\theta\\cos\\phi - \\cos\\psi\\sin\\theta) \\\\\\ \\dot v_z = g - \\frac{f}{m}\\cos\\phi\\cos\\theta \\end{cases} $$\n姿态动力学模型 (合力矩和角速度的方程)\n由欧拉方程知：\n$$ J\\dot\\omega^b + \\omega^b \\times J\\omega^b = G^b_a + \\tau^b $$\n$$ \\omega^b = \\left[\\begin{matrix}\\omega_{xb} \u0026amp; \\omega_{yb} \u0026amp; \\omega_{zb}\\end{matrix} \\right]^T $$\n式中， $\\omega^b$ 表示在机体坐标系下的角速度； $G_a$ 表示陀螺力矩； $\\tau$ 表示螺旋桨在机体轴上产生的力矩，包括绕 $O_bX_b$ 轴的滚转力矩 $\\tau_x$ 、绕 $O_bY_b$ 轴的俯仰力矩 $\\tau_y$ 以及绕 $O_bZ_b$ 轴的偏航力矩 $\\tau_z$ 。\n陀螺力矩Ga: 当电机高速旋转的时候，相当于一个陀螺。高速旋转的陀螺是非常稳定的个体，具有保持自身轴向不变的能力。\n$$ G_a = \\left[ \\begin{matrix} G_{a,\\phi} \\\\\\ G_{a, \\theta} \\\\\\ G_{a,\\psi} \\end{matrix} \\right] = \\left[ \\begin{matrix} J_1q(\\overline\\omega_1 - \\overline\\omega_2 + \\overline\\omega_3 - \\overline\\omega_4) \\\\\\ J_1p(-\\overline\\omega_1 + \\overline\\omega_2 - \\overline\\omega_3 + \\overline\\omega_4) \\\\\\ 0 \\end{matrix} \\right] $$\n式中， $J_1$ 表示整个电机转子和螺旋桨绕机体转轴的总转动惯量； $\\overline\\omega_i$ 表示螺旋桨 1,2,3,4 的转速。\n由于假设四旋翼飞行器是均匀对称的刚体,所以 惯性矩阵J 可表示为:\n$$ J = \\left[ \\begin{matrix} I_{xx} \u0026amp; -I_{xy} \u0026amp; -I_{xz} \\\\\\ -I_{xy} \u0026amp; I_{yy} \u0026amp; -I_{yz} \\\\\\ -I_{xz} \u0026amp; -I_{yz} \u0026amp; I_{zz} \\end{matrix} \\right] = \\left[ \\begin{matrix} I_{xx} \u0026amp; \u0026amp; \\\\\\ \u0026amp; I_{yy} \u0026amp; \\\\\\ \u0026amp; \u0026amp; I_{zz} \\end{matrix} \\right] $$\n所以上式可化为:\n$$ \\begin{cases} \\dot p = \\dot\\omega_{xb} = \\frac{1}{I_{xx}}\\left[\\tau_x + qr(I_{yy} - I_{zz}) - J_{RP} q \\cdot \\Omega\\right]\t\\\\\\ \\dot q = \\dot\\omega_{yb} = \\frac{1}{I_{yy}}\\left[\\tau_y + pr(I_{zz} - I_{xx}) + J_{RP} p \\cdot\\Omega\\right]\t\\\\\\ \\dot r = \\dot\\omega_{zb} =\\frac{1}{I{zz}}\\left[\\tau_z + pq(I_{xx} - I_{yy})\\right] \\end{cases} $$\n式中， $\\Omega = -\\overline\\omega_1 + \\overline\\omega_2 - \\overline\\omega_3 + \\overline\\omega_4$\n四旋翼飞行器的运动学模型 输入 -\u0026gt;[速度和角速度] 输出 -\u0026gt;[位置和姿态]\n位置：\n$$ \\dot p^e = v^e $$\n姿态：\n$$ \\left[ \\begin{matrix} \\dot\\phi \\\\\\ \\dot\\theta \\\\\\ \\dot\\psi \\end{matrix} \\right]\t= \\left[ \\begin{matrix} 1 \u0026amp; \\tan\\theta\\sin\\phi \u0026amp; \\tan\\theta\\cos\\phi \\\\\\ 0 \u0026amp; \\cos\\phi \u0026amp; -\\sin\\phi \\\\\\ 0 \u0026amp; \\frac{\\sin\\phi}{\\cos\\theta} \u0026amp; \\frac{\\cos\\phi}{\\cos\\theta} \\end{matrix} \\right] \\left[ \\begin{matrix} p \\\\\\ q \\\\\\ r \\end{matrix} \\right] $$\n无人机姿态 Pich,俯仰\nRoll，横滚\nYaw，航向（偏航）\n共轴双桨旋翼无人机基础 共轴双桨旋翼无人机的飞行原理类似于常见的直升机。但是共轴双桨无人机取消了直升机的尾桨，而使用两个直径相同共轴布局的螺旋桨，利用两个共轴螺旋桨反向旋转来抵消扭矩，再通过倾斜盘作为变距机构，从而控制飞行器的俯仰和横滚自由度。\n该类型飞行器的优点：\n没有尾桨的功率损耗，具有更高的悬停效率，根据莫卡夫设计局的研究资料，共轴双旋翼直升机的悬停效率要比单旋翼带尾桨的直升机高出17%~30%。 由于没有尾桨，可以将机身做的很短，因此其结构重量和载荷均集中在重心处，从而减少了直升机俯仰和偏航的转动惯量，计有较高的加速特性。 该类飞行器的缺点：\n机械机构相对复杂，制造成本和可维护性上不如多旋翼飞行器 飞行模态相对多旋翼复杂一些，在飞控设计方面有一定的挑战性 共轴双桨旋翼无人机基本操纵原理 共轴旋翼无人机在空中有6个自由度，分别为：\n沿X轴的纵向移动、绕X轴的横滚 沿Y轴的横向移动、绕Z轴的偏航 沿Z轴的垂直移动、绕Y轴的俯仰 其主要操纵原理如下：\n通过总距操纵调节上下旋翼的总距，从而改变旋翼拉力的大小，操纵无人机的垂直运动。 通过周期变距操纵使倾斜盘沿不同的方向倾斜，从而改变旋翼拉力的方向，操纵无人机的横向运动和纵向运动。 通过航向操纵改变上下旋翼的扭矩差使无人机的航向改变来操纵航向运动。 关于相机的内参 相机内参的作用就是把坐标从相机坐标系转换到像素坐标系\n设 $O - x - y - z$ 为相机坐标系，习惯上我们把 $z$ 轴指向相机前方， $x$ 向右， $y$ 向下。 $O$ 为摄像机的光心，也就是针孔模型中的针孔。 P 为真实世界中的一点，在相机坐标系中坐标为 $[X, Y, Z]^T$，成像点**P\u0026rsquo;**的坐标为 $[X\u0026rsquo;, Y\u0026rsquo;, Z\u0026rsquo;]^T$ ，焦距（物理成像平面和光心的距离）为 $f$ 。\n根据相似三角形可知： $$ X\u0026rsquo; = f \\frac{X} {Z} \\ Y\u0026rsquo; = f \\frac{Y} {Z} $$ 设 $O\u0026rsquo; - u - v$ 是像素坐标系，原点 $o\u0026rsquo;$ 位于图像左上角， $u$ 轴向右与 $x$ 轴平行， $v$ 轴向下与 $y$ 轴平行。像素坐标在 $u$ 轴上缩 $\\alpha$ 放倍，在v轴上缩放了 $\\beta$ 倍，原点平移了 $[c_x, c_y]^T$ 。**P\u0026rsquo;**在像素平面坐标系上的坐标是 $[u, v]^T$ 。\n则可以得到P‘与像素坐标系的关系为： $$ u = \\alpha X\u0026rsquo; + c_x \\ v = \\beta Y\u0026rsquo; + c_y $$ 进而得到P与**P’**的关系： $$ u = \\alpha f \\frac{X} {Z} + c_x = f_x \\frac{X} {Z} + c_x $$\n$$ v = \\beta f \\frac{Y} {Z} + c_y = f_y \\frac{Y} {Z} + c_y $$\n转换为矩阵形式： $$ Z \\left( \\begin{matrix} u \\\\\\ v \\\\\\ 1 \\end{matrix} \\right) = \\left( \\begin{matrix} f_x \u0026amp; 0 \u0026amp; c_x \\\\\\ 0 \u0026amp; f_y \u0026amp; c_y \\\\\\ 0 \u0026amp; 0 \u0026amp; 1 \\end{matrix} \\right) \\left( \\begin{matrix} X \\\\\\ Y \\\\\\ Z \\end{matrix} \\right) =KP $$ 其中，K为相机的内参矩阵。\n相机外参的作用是把坐标从世界坐标系转换到相机坐标系中\n最右边那个矩阵就是相机的外参矩阵。\n","date":"2023-04-18T05:59:13+08:00","image":"https://haoleng-wick.github.io/p/%E6%97%A0%E4%BA%BA%E6%9C%BA%E5%9F%BA%E7%A1%80/uav_hu12096962700665607028.jpg","permalink":"https://haoleng-wick.github.io/p/%E6%97%A0%E4%BA%BA%E6%9C%BA%E5%9F%BA%E7%A1%80/","title":"无人机基础"},{"content":"Shell基础 基础正则表达式符号 RE字符 意义 备注 ^word 待查词word在行首 word$ 待查词word在行尾 . 一定有一个任意字符 * 重复零到无穷个 * 前面的字符 [ABC] ABC中的一个字符 [a-z] a~z中的一个字符 [^peach] 列出不包含peach中一个字符的行 反向选取 \\{n,m\\} 连续n～m个的前一个RE字符 \\{n,\\} 表示连续n个以上的前一个RE字符 grep管道命令 grep -n '[^a-z]pple' filename.md选取出pple前面不包含a~z中一个字符的行（像apple就不会被列出来） grep -n '^$' filename.md列出文件中的所有空行 grep -v '^$' /etc/rsyslog.conf |grep -v '^#'去掉空行和以#开头的行 sed管道命令 格式：sed -[nefr] {操作}\nsed '2,5d删除第2～5行 sed '2,$d删除第2行到最后一行 sed '2a add something'在第二行后加入add something sed '2i add something'在第二行前加入add something sed '2,5c Something将第二行到第五行按行替换为Something sed 's/要被替换的字符/新的字符/g（部分替换） 栗子：\n1 2 cat /etc/man_db.conf|grep \u0026#34;MAN\u0026#34;|sed \u0026#39;s/#.*$//g\u0026#39;|sed \u0026#39;/^$/d\u0026#39; 列出文件中含有MAN字符的整行并把以#开头的行换成空行之后删掉空行 sed -i xxx 修改原文件而不是仅修改打印，比较危险 例如sed -i 's/\\.$/\\!/g' filename.md表示修改filename.md中以.结尾的行的句末的.为！ awk awk不是整行操作的，它更倾向于一行中分成数个字段来处理。\n1 2 3 4 5 6 7 $ last -n 5 haoleng tty1 :0 Thu Jun 18 07:28 still logged in reboot system boot 5.7.2-arch1-1 Thu Jun 18 07:27 still running haoleng tty1 :0 Wed Jun 17 16:07 - down (00:41) haoleng tty1 :0 Wed Jun 17 16:04 - 16:07 (00:02) reboot system boot 5.7.2-arch1-1 Wed Jun 17 16:04 - 16:48 (00:44) wtmp begins Sat Apr 11 12:49:36 2020 使用awk之后：\n1 2 3 4 5 6 7 8 $ last -n 5 |awk \u0026#39;{print $1 \u0026#34;\\t\u0026#34; $3}\u0026#39; haoleng\t:0 reboot\tboot haoleng\t:0 haoleng\t:0 reboot\tboot wtmp\tSat awk的内置变量 变量名称 代表意义 NF 每一行（$0）拥有的字段总数 NR 目前awk所处理的是第几行数据 FS 目前的分割字符 1 2 3 4 5 6 7 8 $ last -n 5|awk \u0026#39;{print $1 \u0026#34;\\t line_count: \u0026#34;NF \u0026#34;\\t line: \u0026#34; NR}\u0026#39; haoleng\tline_count: 10\tline: 1 reboot\tline_count: 10\tline: 2 haoleng\tline_count: 10\tline: 3 haoleng\tline_count: 10\tline: 4 reboot\tline_count: 11\tline: 5 line_count: 0\tline: 6 wtmp\tline_count: 7\tline: 7 假设有一份薪资数据表pay.txt，内容如下\n1 2 3 4 Name\t1st\t2nd\t3th John\t23000\t24000\t25000 Wick\t21000 20000\t26000 V\t43000\t42000\t41900 可以用awk求和：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ cat pay.txt |\\ \u0026gt; awk \u0026#39;NR==1{printf \u0026#34;%10s %10s %10s %10s %10s\\n\u0026#34;,$1,$2,$3,$4,\u0026#34;Total\u0026#34;} \u0026gt; NR\u0026gt;=2{total = $2 +$3 +$4;\\ \u0026gt; printf \u0026#34;%10s %10s %10s %10s %10.2f\\n\u0026#34;,$1,$2,$3,$4,total}\u0026#39; Name 1st 2nd 3th Total John 23000 24000 25000 72000.00 Wick 21000 20000 26000 67000.00 V 43000 42000 41900 126900.00 或者用下面的命令同样可以： $ cat pay.txt |\\ \u0026gt; awk \u0026#39;{if (NR==1) printf \u0026#34;%10s %10s %10s %10s %10s\\n\u0026#34;,$1,$2,$3,$4,\u0026#34;Total\u0026#34;} \u0026gt; NR\u0026gt;=2{total = $2 +$3 +$4;\\ \u0026gt; printf \u0026#34;%10s %10s %10s %10s %10.2f\\n\u0026#34;,$1,$2,$3,$4,total}\u0026#39; Name 1st 2nd 3th Total John 23000 24000 25000 72000.00 Wick 21000 20000 26000 67000.00 V 43000 42000 41900 126900.00 简单的扩展正则表达式 RE字符 意义 范例 + 一个或一个以上+前面的字符 egrep -n 'go+d' filename.md——good god\u0026hellip; ? 一个或者零个？前面的字符 go+d与go?d的集合是 go*d | 用or的方法找出数个字符串 `egrep -n \u0026lsquo;god ( ) 找出[组群]字符串 `egrep -n \u0026lsquo;g(lo ( )+ 重复的组群 一个简单的栗子：grep -v '^$' filename.md|grep -v '^#' 等价与 egrep -v '^$|^#' filename.md\n","date":"2020-06-18T05:59:13+08:00","image":"https://haoleng-wick.github.io/p/shell%E5%9F%BA%E7%A1%80/terminal_hu1733500932178902221.png","permalink":"https://haoleng-wick.github.io/p/shell%E5%9F%BA%E7%A1%80/","title":"Shell基础"}]