-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsearch.xml
More file actions
254 lines (120 loc) · 170 KB
/
search.xml
File metadata and controls
254 lines (120 loc) · 170 KB
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
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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>《认知觉醒》读书睡前小记</title>
<link href="/%E8%AF%BB%E7%82%B9%E4%B9%A6-2025-09-09-wakeup.html"/>
<url>/%E8%AF%BB%E7%82%B9%E4%B9%A6-2025-09-09-wakeup.html</url>
<content type="html"><![CDATA[<h1>2025年3月1日:睡前小记与《认知觉醒》</h1><p><em><s>排版怎么乱乱的,大部分都是一些摘抄,就酱吧。</s></em></p><p>今晚决定早早睡觉。睡前翻了几页书,是前阵子 fishman 推荐我的《<strong>认知觉醒</strong>》。本以为又是什么心灵毒鸡汤,读到14%之后发现,这位作者还真有点来头。</p><hr><p>这本书剖析了<strong>焦虑的根源</strong>,将其分为:</p><ul><li>完成焦虑</li><li>定位焦虑</li><li>选择焦虑</li><li>环境焦虑</li><li>难度焦虑</li></ul><blockquote><p>“归结起来,焦虑的原因就两条:想同时做很多事,又想立即看到效果。”</p></blockquote><p>王小波也曾说:</p><blockquote><p>“人的一切痛苦,本质上都是对自己无能的愤怒。”</p></blockquote><p>说得再直白些:</p><blockquote><p>“自己的欲望大于能力,又极度缺乏耐心。焦虑就是因为欲望与能力之间差距过大。”</p></blockquote><hr><p>首先要理解自己——这些的确是人类的天性使然:<strong>避难趋易</strong>和<strong>急于求成</strong>。<br>焦虑不是缺陷,而是天性,是人类默认的设置。</p><p><strong>解法清单 🧾</strong>:</p><ol><li>克制欲望,不要让自己同时做很多事;</li><li>面对现实,看清自己真实的能力水平;</li><li>要事优先,想办法只做最重要的事情;</li><li>接受环境,在局限中做力所能及的事;</li><li>直面核心,狠狠逼自己一把去突破它。</li></ol><hr><blockquote><p>“耐心是人类最珍贵的品质之一。”</p></blockquote><ul><li><strong>复利曲线</strong>、<strong>平台期突破</strong>,都是对耐心者的回报。</li><li>“然而回避痛苦并不会使痛苦消失,反而会使其转入潜意识,变成模糊的感觉。而具体事件一旦变模糊,其边界就会无限扩大。原本并不困难的小事,也会在模糊的潜意识里变得难以解决。”</li><li>“任何痛苦的事件都不会自动消失,哪怕再小的事情也是如此。”</li></ul><p>要想不受其困扰,唯一的办法就是:<br><strong>正视它、看清它、拆解它、化解它。</strong></p><p>真正的困难总比想象的要小很多。<br>这就是为什么每次克服困难再回头看,发现也不过就是那么一回事。</p><hr><p>勇敢面对自己汹涌的情绪,认清并接纳真实的自己。<br>不是劝你水仙,而是劝你先爱上自己——<br>那个不完美、平凡却仍在努力改变的自己。</p><blockquote><p>“恐惧就是一个欺软怕硬的货色,你躲避它,它就张牙舞爪;你正视它,它就原形毕露。”</p></blockquote><p>让恐惧统统从潜意识中消散,生活才能舒畅无比。</p><hr><p>哎,说起这个,忽然想起小时候在语文老师那补习,有一段文言文讲的是<strong>淳于髡</strong>(chún yú kūn),出自《史记·滑稽列传》。我至今还记得,还会背:</p><blockquote><p>威王八年,楚大发兵加齐。齐王使淳于髡之赵请救兵,赍(jī)金百斤,车马十驷。淳于髡仰天大笑,冠(guān)缨索绝。王曰:“先生少(shǎo)之乎?”髡曰:“何敢!”王曰:“笑岂有说(shuō)乎?”髡曰:“今者,臣从东方来,见道傍有禳(ráng)田者,操(tún)一豚蹄,酒一盂(yú),祝曰:‘瓯(ōu)窭(jù)满篝(gōu),汙(wū)邪(yé)满车,五穀(gǔ)蕃(fán)熟(shú),穰(ráng)穰(ráng)满家。’<strong>臣见其所持者狭而所欲者奢,故笑之。</strong>”于是齐威王乃益赍(jī)黄金千镒(yì),白璧十双,车马百驷(sì)。髡辞而行,至赵。赵王与之精兵十万,革车千乘(shèng)。楚闻之,夜引兵而去。</p></blockquote><p><strong>我又何尝不是如此?</strong></p><p>想要的东西太多了,反而就显得自己渺小得可笑。<br>任何事,躺在床上是无法完成的呀!</p><hr><p>🌙 晚安,清醒而温柔的三月。</p>]]></content>
<categories>
<category> Read </category>
</categories>
</entry>
<entry>
<title>论原谅</title>
<link href="/%E7%A2%8E%E7%A2%8E%E5%BF%B5-2025-09-09-forgive.html"/>
<url>/%E7%A2%8E%E7%A2%8E%E5%BF%B5-2025-09-09-forgive.html</url>
<content type="html"><![CDATA[<h1>论原谅</h1><blockquote><p>此篇为2024年暑假军训期间,因下雨在教室观影时有感而作(当时没有手机)。如今重读,自觉笔锋略显凌厉,但既已录入,便姑且存之,献丑了。</p></blockquote><hr><p><strong>犯错</strong>、<strong>道歉</strong>、<strong>原谅</strong>,这三个词按理来说应组成了一件事情的始终。不过细想,三者之间的联系也未必那样紧密。</p><p>犯错与道歉,全看一个人道德与良知如何,或也如 fishman 所说“<strong>不要道歉,否则会变成讨好型人格</strong>”。话虽如此,虽是“<strong>人非圣贤孰能无过</strong>”,但一般来说,道德操守高的人,所犯的过错也应会较少。而对于恶者,选择道歉,或是恶得不必道歉的……还有一种说法:犯下的错误无法用道歉弥补,受害者的伤害依然存在,所谓的“道歉”不过是作恶者为了减少自身罪恶感的谎言罢了。道歉既是无用,索性倒也不必多此一举。</p><p>再谈谈<strong>原谅</strong>。</p><p>小时候上幼儿园时,小朋友 A 欺负小朋友 B,老师会让 A 跟 B 道歉,然后问 B 是否原谅 A。B 要说:“没关系,我原谅你。”这个环节在我当时看来就非常愚蠢。若真如此,我若是恶属性大爆发,成为了 A,那无所谓,反正在老师的主持下,只要不是闹得太大,B 都会“<strong>不得不</strong>”原谅我。毕竟,我从未见过没被原谅的 A,不过是老师多劝慰了几句 B,B 也就哭着答应了——我只见过<strong>心中不甘但既怕 A 又怕老师的 B 泪流满面违心地说着“我原谅你”</strong>。</p><p>我不明白这种弱智的<strong>形式主义原谅</strong>对于教育来说有何种进步或者可取之处。错了就是错了,凭什么必须就要原谅作恶者?原不原谅是受害者的自由,受到伤害还能原谅那是人家大度,<strong>不原谅是应该的、是本分,原谅才是破例、是情分</strong>!怎么,那些要用“气度小”来道德绑架受害者的,是想看他被二创吗?这哪里是<strong>伸张、褒奖正义</strong>?这分明是<strong>扭曲、侮辱正义</strong>!</p><p><strong>错了就是该罚</strong>,不然你以为幼儿园里的 A 和 B,和常发但一般比较隐蔽的<strong>校园霸凌</strong>、和校园高发的<strong>抑郁症</strong>之间有何因果联系呢?不要以为 B 永远是受害者,谁知道他若是某一天发现了幼儿园里的“公道”原理之后,不会变成他害怕又讨厌的 A、把手伸向更弱者呢?</p><p>(扯得有些远,但这也与幼师没法惩罚小孩有关。这教育制度与方式原本就存在问题,而“人性恶”的偶尔暴露就作为导火索,引发了一系列教育系统的灾难。错了就该狠狠罚,让作恶者长记性,让受害者与观众们都感受到正义的力量。至于原不原谅,原本连着道歉都属于是多此一举那类了,这就该纯属于受害者个人的心理活动,不公之于众也罢。)</p><p>为何有感而发?虽然我的主场是“<strong>人性本恶</strong>”,但大约两三年前,我莫名收到一些旁人的评价,说我是一个善良的人(当时我还真信了哈哈)。对,回想一下,若有 AB 之事,我都是以 B 的身份置身事内。并且,在一些教育下,我甚至会对自己进行道德绑架:“<strong>我要是不原谅他,我就是不念旧情、就是我冷血</strong>。”</p><p>没错,如此荒谬,我却也逐渐“参悟”了这种“正义之道”。我不至于成为极端的 A,但“恶属性”的爆发频率确实变高了。“不想原谅他们”,让他们“这辈子都活在愧疚里!”有点夸张了,但是,<strong>“不原谅”是我应有的权力</strong>。</p><p>我有时也会觉得我如今“这般恶”是一种病态,<strong>现在想来,也许原先“善良且软弱”的我才是病态,理性分析做出决定的才是正常态</strong>。</p><hr><p><strong>—— 全文完 ——</strong></p>]]></content>
<categories>
<category> Think </category>
</categories>
</entry>
<entry>
<title>比双系统方便的Linux——wsl!</title>
<link href="/%E5%AD%A6%E7%82%B9%E4%B9%A0-2025-08-27-linux-wsl.html"/>
<url>/%E5%AD%A6%E7%82%B9%E4%B9%A0-2025-08-27-linux-wsl.html</url>
<content type="html"><![CDATA[<h2 id="前言:从双系统到WSL的转变">前言:从双系统到WSL的转变</h2><p>前段时间和朋友吃饭,聊到他日常电脑使用已经全面转向<strong>Linux系统</strong>。他解释说这样做主要是因为Linux系统<strong>高度可定制化</strong>。这让我想起2025年寒假时,我费了九牛二虎之力才安装成功的双系统——虽然界面确实美观(不过我觉得主要是因为不会在Linux桌面上乱丢文件),但因为启动太麻烦,已经整整半年没有打开过了。</p><p>后来因为参加比赛的需要,师兄向我推荐了更便捷的Linux启动方式:<strong>WSL</strong>(Windows Subsystem for Linux)。</p><h2 id="什么是WSL?为什么它如此高效?">什么是WSL?为什么它如此高效?</h2><p>WSL是微软开发的能够在Windows系统上运行Linux环境的工具。<strong>不用不知道,一用发现真是高效</strong>!直接通过SSH连接到VSCode或者PyCharm,在Windows系统下就能无缝操作Linux环境。</p><p>我当时因为比赛时间紧迫,只安装了基础环境没有装桌面。我觉得如果需要完整的桌面体验,还不如使用双系统。但对于开发工作来说,WSL提供的命令行环境已经足够强大且高效。</p><h2 id="WSL安装与配置全攻略">WSL安装与配置全攻略</h2><p>以下是一些参考教程(虽然时间有点久远,可能不太记得当时遇到的具体问题解决方案了,但这些资源仍然非常有价值):</p><p><strong>好消息是</strong>,从开始安装WSL,到Ubuntu 22.04装好,再配上CUDA 12.9、PyTorch,最后通过VSCode启动(不需要在Linux系统里面再装VSCode,直接Windows下远程连接即可)——<strong>整个过程不超过两小时</strong>。</p><p>别笑,这真的是我配置环境最快的一次了!</p><p>今天因为又安装了一个Conda环境,想起写这篇博客,这里也分享一些相关经验:</p><h2 id="搜狗输入法安装体验">搜狗输入法安装体验</h2><p>我那位日常使用Linux系统的朋友,饭后我回去在双系统的Linux里安装了一个搜狗输入法向他展示,发现他还在忍受非常蹩脚的默认输入法。于是也把搜狗输入法的经验帖分享在这里:</p><h3 id="关于搜狗输入法的注意事项:">关于搜狗输入法的注意事项:</h3><ul><li><strong>Ubuntu 24.04</strong> 安装搜狗输入法可以参考这个<strong>2025年7月发布的最新文档</strong>,我亲自跟着安装没有问题</li><li>支持<strong>导入本地词库</strong></li><li><strong>皮肤选项</strong>比较少(一共就五个),但其中两三个都很美观,足以满足基本需求</li><li><strong>不支持登录个人账户</strong>,意味着之前在Windows中的输入习惯无法同步</li></ul><p>尽管有这些限制,但相比最初那连全拼都要找半天的默认输入法(对我这种本来打字速度就很慢的人来说更是雪上加霜),搜狗输入法已经好太多了!</p><p>效果如下:</p><div style="text-align: center; font-family: 'Arial', sans-serif;"> <div style="margin-bottom: 20px;"> <img src="images\learn_pic\2025-08-27_sougou.jpg" alt="搜狗输入法——你是我的神!" style="width: 90%; height: auto; border-radius: 10px;"> <div style="font-size: 14px; color: #333; margin-top: 5px;"> 搜狗输入法——你是我的神! </div> </div></div><h2 id="科学上网工具推荐">科学上网工具推荐</h2><p>Conda有时候会遇到镜像源SSL问题,即使用pip也不一定能解决。但我发现使用科学上网工具后问题就迎刃而解了。</p><p>事已至此,再推荐一个科学上网工具:<strong>Mihomo Party</strong>。日常使用只需要点一下开关就能用,非常方便。可以在GitHub上直接搜索,它同时支持Windows和Linux(Mac我没注意,不好意思)。</p><p>当然,你需要自己购买流量。如果你也跟我一样需求量不是特别大,我推荐购买<strong>限量不限时</strong>的套餐。我是在这里购买的,支付后可以直接复制链接自动导入Mihomo Party,非常便捷。</p><p>网站叫XXAI,网址是:<a href="https://panel.xx-ai.uk/">https://panel.xx-ai.uk/</a><br>参考价格:80CNY 200G流量(我买的就是这个套餐)</p><h2 id="实用资源汇总">实用资源汇总</h2><p>以下是我收集的一些实用资源链接,希望能帮助你更顺利地使用WSL:</p><ol><li><p><strong>安装wsl2</strong>: <a href="https://learn.microsoft.com/zh-cn/windows/wsl/install">https://learn.microsoft.com/zh-cn/windows/wsl/install</a></p></li><li><p><strong>B站WSL安装Anaconda教程</strong>:<a href="https://www.bilibili.com/video/BV1ok4y1t7XC/?spm_id_from=333.337.search-card.all.click&vd_source=bfef7a81da7228f3c22bd6ab3b5a8bb6">https://www.bilibili.com/video/BV1ok4y1t7XC/</a></p></li><li><p><strong>PyTorch官网不支持Conda语句时的pip下载方法</strong>(虽然帖子里面是针对Windows,但本人亲测Linux系统下也适用):<a href="https://blog.csdn.net/LBJ170/article/details/146074211">https://blog.csdn.net/LBJ170/article/details/146074211</a></p></li><li><p><strong>PyTorch官方下载网站</strong>(选择好配置后把那句命令复制到终端执行即可):<a href="https://pytorch.org/get-started/locally/">https://pytorch.org/get-started/locally/</a></p></li><li><p><strong>Anaconda下载网站</strong>:<a href="https://www.anaconda.com/download">https://www.anaconda.com/download</a></p></li><li><p><strong>科学上网Mihomo Party</strong>:<a href="http://8.210.142.138:8899/s?token=9c3619f169f77a19d327008b930ee241">http://8.210.142.138:8899/s?token=9c3619f169f77a19d327008b930ee241</a></p></li><li><p><strong>搜狗输入法经验帖</strong>:<a href="https://blog.csdn.net/smssy/article/details/149114807?spm=1001.2101.3001.6650.3&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EYuanLiJiHua%7EPosition-3-149114807-blog-138773050.235%5Ev43%5Econtrol&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EYuanLiJiHua%7EPosition-3-149114807-blog-138773050.235%5Ev43%5Econtrol&utm_relevant_index=6">https://blog.csdn.net/smssy/article/details/149114807</a></p></li></ol><h2 id="师兄的WSL准备清单">师兄的WSL准备清单</h2><p>附上一些当时师兄告诉我要准备的清单:</p><blockquote><h3 id="1-本地开发">1. 本地开发</h3><p>本地开发主要运行不需要依赖GPU进行训练的前、后处理工作</p><p><strong>需要工具:</strong></p><ul><li>任意浏览器(如Chrome)</li><li>VS Code</li><li>安装WSL2</li><li>在微软商店安装Ubuntu 22.04(自带Python 3.10.12)</li><li>pip3 install jupyter</li></ul><h3 id="2-远程开发">2. 远程开发</h3><p>远程开发主要通过SSH连接本地服务器,使用服务器的GPU训练、测试模型</p><p><strong>需要工具:</strong></p><ul><li>任意浏览器</li><li>任意SSH和FTP客户端(如Xshell/Xftp)</li><li>VS Code(安装SSH扩展)</li><li></li></ul></blockquote><h2 id="安装成功的喜悦">安装成功的喜悦</h2><p>安装成功时看到这张图片,感觉非常美丽(也很感动):</p><div style="text-align: center; font-family: 'Arial', sans-serif;"> <div style="margin-bottom: 20px;"> <img src="images\learn_pic\2025-08-27_ubuntu.png" alt="安装成功啦~" style="width: 90%; height: auto; border-radius: 10px;"> <div style="font-size: 14px; color: #333; margin-top: 5px;"> 安装成功啦~ </div> </div></div><h2 id="额外小技巧分享">额外小技巧分享</h2><p>还有一些实用小妙招,一并分享在这里:</p><ol><li><p><strong>实现Windows和Linux系统时间统一</strong>(都是北京东八区):<a href="https://zhuanlan.zhihu.com/p/492885761">https://zhuanlan.zhihu.com/p/492885761</a></p></li><li><p><strong>安装百度网盘</strong>(使用dpkg -i进行安装):<a href="https://zhuanlan.zhihu.com/p/71108771">https://zhuanlan.zhihu.com/p/71108771</a></p></li></ol><h2 id="最后的一点鼓励">最后的一点鼓励</h2><p>最后的最后,其实本人还想说一句,千万不要被配置环境打倒了!不要让几个小时折腾环境的时间磨灭了你想要学习编程的热情!<br>不否认,对于很多写程序能力还不错的程序猿,配置环境也不是一件容易的事情。<br>实在不行,宁愿淘宝几十块钱解决一下,也别失去了对编程的信心。没什么好丢脸的,十几块钱节约了几个小时,还免去了糟糕的心情(虽然没有自己历练到,但并不代表不能偷师)。<br>我为什么说这个,别笑我,当时的双系统Ubuntu22.04总是差最后一步装不上去,就是淘宝解决的(不过他当时说的也只能装24.04,其实我觉得我也能装这个版本,但已经付钱了)。</p>]]></content>
<categories>
<category> Learn </category>
</categories>
</entry>
<entry>
<title>学习资料网站(欢迎收藏)</title>
<link href="/%E5%AD%A6%E7%82%B9%E4%B9%A0-2025-08-27-learnsource.html"/>
<url>/%E5%AD%A6%E7%82%B9%E4%B9%A0-2025-08-27-learnsource.html</url>
<content type="html"><![CDATA[<p>Numpy的主页:<a href="https://numpy.org/">https://numpy.org/</a><br>Numpy绝对初学者教程:<a href="https://numpy.org/doc/stable/user/absolute_beginners.html">https://numpy.org/doc/stable/user/absolute_beginners.html</a></p><p>零基础入门深度学习博客: <a href="https://www.zybuluo.com/hanbingtao/note/433855">https://www.zybuluo.com/hanbingtao/note/433855</a></p><p>打字网站推荐,边打字边读英文原著: <a href="https://entertrained.app/">https://entertrained.app/</a></p><p>全屏时钟: <a href="https://cn.piliapp.com/time/">https://cn.piliapp.com/time/</a></p><p>scikit-learn基础: <a href="https://www.cnblogs.com/wang_yb/p/17871294.html">https://www.cnblogs.com/wang_yb/p/17871294.html</a></p><p>Hello算法书: <a href="https://www.hello-algo.com/chapter_hello_algo/">https://www.hello-algo.com/chapter_hello_algo/</a></p><p>MATLAB教程: <a href="https://ww2.mathworks.cn/help/matlab/index.html?s_tid=hc_panel">https://ww2.mathworks.cn/help/matlab/index.html?s_tid=hc_panel</a></p><p>Zlib镜像访问接口: <a href="https://www.thinkdoc.vip/zlibrary.html">https://www.thinkdoc.vip/zlibrary.html</a></p><p>李沐手动深度学习网页版: <a href="https://zh.d2l.ai/">https://zh.d2l.ai/</a></p><p>神经网络游乐场: <a href="https://playground.tensorflow.org/#activation=tanh&batchSize=10&dataset=circle&regDataset=reg-plane&learningRate=0.03&regularizationRate=0&noise=0&networkShape=4,2&seed=0.24122&showTestData=false&discretize=false&percTrainData=50&x=true&y=true&xTimesY=false&xSquared=false&ySquared=false&cosX=false&sinX=false&cosY=false&sinY=false&collectStats=false&problem=classification&initZero=false&hideText=false&showTestData_hide=false">https://playground.tensorflow.org/#activation=tanh&batchSize=10&dataset=circle&regDataset=reg-plane&learningRate=0.03&regularizationRate=0&noise=0&networkShape=4,2&seed=0.24122&showTestData=false&discretize=false&percTrainData=50&x=true&y=true&xTimesY=false&xSquared=false&ySquared=false&cosX=false&sinX=false&cosY=false&sinY=false&collectStats=false&problem=classification&initZero=false&hideText=false&showTestData_hide=false</a></p>]]></content>
<categories>
<category> Learn </category>
</categories>
</entry>
<entry>
<title>KNN手写数字识别</title>
<link href="/%E5%AD%A6%E7%82%B9%E4%B9%A0-2025-08-27-knn.html"/>
<url>/%E5%AD%A6%E7%82%B9%E4%B9%A0-2025-08-27-knn.html</url>
<content type="html"><![CDATA[<p>本文将介绍 K-Nearest Neighbors(KNN)算法的基本原理,并通过一个实际案例展示如何使用 Python 和 OpenCV 来实现手写数字识别。从数据准备、模型训练到预测评估,全面解析 KNN 在机器学习中的应用。</p><h2 id="K-Nearest-Neighbors(KNN)算法简介">K-Nearest Neighbors(KNN)算法简介</h2><p>K-Nearest Neighbors(KNN)算法是一种基本的分类与回归方法。它的工作原理非常简单直观:通过测量不同特征值之间的距离来进行预测。KNN 算法不考虑数据的分布,它只是简单地根据已标记的数据集中最接近的 K 个数据点的类别,通过投票的方式来预测新数据点的类别。</p><h3 id="算法原理">算法原理</h3><p>KNN 算法的核心思想是<strong>相似性原则</strong>,即相似的事物应该有相似的标签。在分类问题中,给定一个待分类的样本,KNN 算法会:</p><ol><li><strong>计算待分类样本与所有已知类别样本之间的距离</strong>(常用的距离度量包括欧氏距离、曼哈顿距离等)。</li><li><strong>按照距离的远近对样本进行排序</strong>。</li><li><strong>选取距离最近的 K 个样本</strong>(K 是一个正整数,通常由交叉验证来选择最佳值)。</li><li><strong>根据这 K 个样本的已知类别,通过投票机制</strong>来决定待分类样本的类别。</li></ol><p>在回归问题中,KNN 算法会根据最近的 K 个邻居样本的数值,计算待预测样本的预测值,通常采用平均值或加权平均值。</p><h3 id="算法步骤">算法步骤</h3><ol><li><strong>选择参数 K</strong>:K 值的选择对 KNN 算法的性能有很大影响。较小的 K 值意味着模型对噪声更敏感,而较大的 K 值则可能导致模型对数据的局部结构不够敏感。</li><li><strong>距离度量</strong>:选择合适的距离度量方法来计算样本之间的距离。最常用的是欧氏距离,但在某些情况下,其他距离度量(如曼哈顿距离、余弦相似度等)可能更合适。</li><li><strong>寻找最近的 K 个邻居</strong>:对于每个待分类的样本,找到训练集中与其距离最近的 K 个样本。</li><li><strong>决策规则</strong>:对于分类问题,采用多数投票法来确定样本的类别;对于回归问题,计算 K 个邻居的平均值作为预测值。</li></ol><h3 id="优缺点">优缺点</h3><p><strong>优点</strong>:</p><ul><li><strong>简单易懂</strong>,实现容易。</li><li><strong>无需训练数据</strong>,对数据分布没有假设。</li><li>适合于<strong>多分类</strong>问题。</li></ul><p><strong>缺点</strong>:</p><ul><li><strong>计算成本高</strong>,尤其是在大数据集上,因为需要计算待分类样本与所有训练样本之间的距离。</li><li><strong>存储成本高</strong>,需要存储全部数据集。</li><li>对<strong>不平衡</strong>的数据集表现不佳,可能需要进行采样来平衡数据。</li><li>对<strong>特征尺度</strong>敏感,需要进行特征缩放。</li></ul><h3 id="应用场景">应用场景</h3><p>KNN 算法适用于各种分类和回归问题,尤其是在数据量不是非常大的场景下。它在文本分类、图像识别、推荐系统等领域都有应用。由于其简单性和直观性,KNN 算法常被用作机器学习初学者的第一个算法。</p><h2 id="代码实现:使用-KNN-识别手写数字">代码实现:使用 KNN 识别手写数字</h2><p>以下代码展示了如何使用 OpenCV 和 NumPy 实现手写数字识别。</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"><span class="keyword">import</span> cv2 </span><br><span class="line"></span><br><span class="line"><span class="comment"># 【step1:预处理】读入文件、色彩空间转换</span></span><br><span class="line">img = cv2.imread(<span class="string">'digits.png'</span>)</span><br><span class="line"><span class="comment"># 灰度转换:BGR模式-->灰度图像</span></span><br><span class="line">gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 【step2:拆分为独立数字】</span></span><br><span class="line"><span class="comment"># 将原始图像划分成独立的数字,每个数字大小20*20,共计5000个</span></span><br><span class="line">cells = [np.hsplit(row, <span class="number">100</span>) <span class="keyword">for</span> row <span class="keyword">in</span> np.vsplit(gray, <span class="number">50</span>)]</span><br><span class="line"><span class="comment"># 装进array,形状(50,100,20,20),50行,100列,每个图像20*20大小</span></span><br><span class="line">x = np.array(cells)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 【step3:拆分为训练集和测试集】</span></span><br><span class="line"><span class="comment"># 划分为训练集和测试集:比例各占一半</span></span><br><span class="line">train = x[:, :<span class="number">50</span>] <span class="comment"># 前50列作为训练集</span></span><br><span class="line">test = x[:, <span class="number">50</span>:<span class="number">100</span>] <span class="comment"># 后50列作为测试集</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 【step4:塑形为符合KNN的输入】</span></span><br><span class="line"><span class="comment"># 数据调整,将每个数字的尺寸由20*20调整为1*400(一行400个像素)</span></span><br><span class="line">train = train.reshape(-<span class="number">1</span>, <span class="number">400</span>).astype(np.float32) <span class="comment"># Size = (2500,400)</span></span><br><span class="line">test = test.reshape(-<span class="number">1</span>, <span class="number">400</span>).astype(np.float32) <span class="comment"># Size = (2500,400)</span></span><br><span class="line"><span class="built_in">print</span>(train.shape) <span class="comment"># 输出训练数据的形状</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 【step5:分配标签】</span></span><br><span class="line"><span class="comment"># 分别为训练数据、测试数据分配标签(图像对应的实际值)</span></span><br><span class="line">k = np.arange(<span class="number">10</span>) <span class="comment"># 创建0-9的数组</span></span><br><span class="line">train_labels = np.repeat(k, <span class="number">250</span>)[:, np.newaxis] <span class="comment"># 每个数字重复250次作为训练标签</span></span><br><span class="line">test_labels = np.repeat(k, <span class="number">250</span>)[:, np.newaxis] <span class="comment"># 每个数字重复250次作为测试标签</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 【step6:KNN工作】</span></span><br><span class="line"><span class="comment"># 核心代码:初始化、训练、预测</span></span><br><span class="line">knn = cv2.ml.KNearest_create() <span class="comment"># 创建KNN实例</span></span><br><span class="line">knn.train(train, cv2.ml.ROW_SAMPLE, train_labels) <span class="comment"># 训练模型</span></span><br><span class="line">ret, result, neighbours, dist = knn.findNearest(test, k=<span class="number">5</span>) <span class="comment"># 预测测试集</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 【step7:验证结果】</span></span><br><span class="line"><span class="comment"># 通过测试集校验准确率</span></span><br><span class="line">matches = result == test_labels <span class="comment"># 比较预测结果与实际标签</span></span><br><span class="line">correct = np.count_nonzero(matches) <span class="comment"># 计算正确预测的数量</span></span><br><span class="line">accuracy = correct * <span class="number">100.0</span> / result.size <span class="comment"># 计算准确率</span></span><br><span class="line"><span class="built_in">print</span>(<span class="string">"当前使用KNN识别手写数字的准确率为:"</span>, accuracy) <span class="comment"># 输出准确率</span></span><br></pre></td></tr></table></figure><h2 id="代码详解">代码详解</h2><h3 id="1-导入库">1. 导入库</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br></pre></td></tr></table></figure><p>这两行代码导入了必要的库。<code>cv2</code> 是 OpenCV 的库,用于图像处理和机器学习。<code>numpy</code> 是一个强大的数学库,用于处理数组和矩阵。</p><h3 id="2-图像加载与预处理">2. 图像加载与预处理</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 使用绝对路径</span></span><br><span class="line">image_path = <span class="string">"/home/yan/Summer_Learn/P1/digits.png"</span></span><br><span class="line">img = cv2.imread(image_path)</span><br></pre></td></tr></table></figure><p>这里定义了图像文件的路径,并使用 <code>cv2.imread</code> 函数读取图像。<code>cv2.imread</code> 会将图像加载为一个三维数组(高度、宽度、颜色通道)。</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)</span><br></pre></td></tr></table></figure><p>这行代码将图像从 BGR 颜色空间转换为灰度图像。灰度图像是一个二维数组,每个像素值表示该像素的亮度。</p><h3 id="3-分割数字图像">3. 分割数字图像</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">cells = [np.hsplit(row, <span class="number">100</span>) <span class="keyword">for</span> row <span class="keyword">in</span> np.vsplit(gray, <span class="number">50</span>)]</span><br></pre></td></tr></table></figure><p>这行代码将灰度图像分割成多个小块(cell)。<code>np.vsplit</code> 将图像垂直分割成 50 行,<code>np.hsplit</code> 将每一行水平分割成 100 列。这样,整个图像被分割成 50×100=5000 个小块,每个小块是一个数字。</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">x = np.array(cells)</span><br></pre></td></tr></table></figure><p>将分割后的图像块列表转换为一个 NumPy 数组,方便后续操作。</p><h3 id="4-划分训练集和测试集">4. 划分训练集和测试集</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">train = x[:, :<span class="number">50</span>] <span class="comment"># 前50列作为训练集</span></span><br><span class="line">test = x[:, <span class="number">50</span>:<span class="number">100</span>] <span class="comment"># 后50列作为测试集</span></span><br></pre></td></tr></table></figure><p>将 50×100 的图像块分为训练集和测试集。前 50 列(50×50=2500 个图像块)作为训练集,后 50 列(50×50=2500 个图像块)作为测试集。</p><h3 id="5-数据重塑与类型转换">5. 数据重塑与类型转换</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">train = train.reshape(-<span class="number">1</span>, <span class="number">400</span>).astype(np.float32)</span><br><span class="line">test = test.reshape(-<span class="number">1</span>, <span class="number">400</span>).astype(np.float32)</span><br></pre></td></tr></table></figure><p>将训练集和测试集的每个图像块重新调整为一维数组,长度为 400(因为每个图像块是 20×20 像素)。<code>-1</code> 表示自动计算行数,<code>astype(np.float32)</code> 将数据类型转换为浮点数,这是 KNN 算法的要求。</p><h3 id="6-创建标签">6. 创建标签</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">k = np.arange(<span class="number">10</span>)</span><br><span class="line">train_labels = np.repeat(k, <span class="number">250</span>)[:, np.newaxis]</span><br><span class="line">test_labels = np.repeat(k, <span class="number">250</span>)[:, np.newaxis]</span><br></pre></td></tr></table></figure><p>定义训练集和测试集的标签。<code>np.arange(10)</code> 生成一个从 0 到 9 的数组,表示 10 个数字类别。<code>np.repeat(k, 250)</code> 将每个数字类别重复 250 次,因为每个数字类别有 250 个样本。<code>[:, np.newaxis]</code> 将数组从一维扩展为二维,以满足 KNN 算法的要求。</p><h3 id="7-训练-KNN-模型">7. 训练 KNN 模型</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">knn = cv2.ml.KNearest_create()</span><br><span class="line">knn.train(train, cv2.ml.ROW_SAMPLE, train_labels)</span><br></pre></td></tr></table></figure><p>创建一个 KNN 模型,并使用训练集数据和标签进行训练。<code>cv2.ml.ROW_SAMPLE</code> 表示每一行是一个样本。</p><h3 id="8-预测与评估">8. 预测与评估</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">ret, result, neighbours, dist = knn.findNearest(test, k=<span class="number">5</span>)</span><br></pre></td></tr></table></figure><p>使用训练好的 KNN 模型对测试集进行预测。<code>k=5</code> 表示考虑最近的 5 个邻居。<code>findNearest</code> 返回的结果包括:</p><ul><li><code>ret</code>:返回值,通常不使用。</li><li><code>result</code>:预测的标签。</li><li><code>neighbours</code>:最近的 5 个邻居的标签。</li><li><code>dist</code>:最近的 5 个邻居的距离。</li></ul><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">matches = result == test_labels</span><br><span class="line">correct = np.count_nonzero(matches)</span><br><span class="line">accuracy = correct * <span class="number">100.0</span> / result.size</span><br><span class="line"><span class="built_in">print</span>(<span class="string">"当前使用KNN识别手写数字的准确率为:"</span>, accuracy)</span><br></pre></td></tr></table></figure><p>计算模型的准确率。<code>result == test_labels</code> 比较预测的标签和真实的标签,生成一个布尔数组。<code>np.count_nonzero(matches)</code> 统计布尔数组中为 True 的个数,即正确预测的样本数。<code>accuracy</code> 计算准确率,公式为:正确预测的样本数 / 总样本数 × 100%。最后打印出准确率。</p><h2 id="手写数字数据集示例">手写数字数据集示例</h2><p>下面是我们使用的手写数字数据集示例,包含了 0-9 十个数字的各种手写形式:</p><div style="text-align: center; font-family: 'Arial', sans-serif;"> <div style="margin-bottom: 20px;"> <img src="images\learn_pic\2025-08-27_knn.png" alt="手写数字数据集" style="width: 90%; height: auto; border-radius: 10px;"> <div style="font-size: 14px; color: #333; margin-top: 5px;"> 手写数字数据集示例 </div> </div></div><h2 id="常见问题解答">常见问题解答</h2><h3 id="1-方法链中的点号用法">1. 方法链中的点号用法</h3><p>问:这几句里面的点号的用法是可以一直加在后面的吗:<code>train = train.reshape(-1,400).astype(np.float32)</code></p><p>答:点号(<code>.</code>)在这里并不是随意添加的。在 Python 中,点号用于访问对象的属性或方法。在你提到的代码行中,<code>reshape</code> 和 <code>astype</code> 是 <code>numpy</code> 数组对象的方法。<code>reshape</code> 方法用于改变数组的形状,而 <code>astype</code> 方法用于改变数组的数据类型。这些方法必须通过点号来调用,因为它们是数组对象的一部分。这种连续调用多个方法的方式称为"方法链",是一种常见的编程风格。</p><h3 id="2-标签标注的原理">2. 标签标注的原理</h3><p>问:在标签标注的时候,它咋知道每个手写对应的真实值是什么?还是说因为所给手写图片的特殊性决定的?</p><p>答:在手写数字识别任务中,通常使用的是<strong>已经标注好的数据集</strong>,比如 MNIST 数据集。这些数据集中的每个手写数字图像都有一个对应的标签,表示它代表的数字。这些标签是人为提供的,通常是数据集创建者在收集数据时就标注好的。</p><p>在你提供的代码中,标签是通过 <code>np.repeat(k,250)[:,np.newaxis]</code> 这行代码生成的,它假设每个数字(0-9)都有 250 个样本,并且每个数字的样本都被重复 250 次来创建标签数组。<strong>这种假设成立的前提是原始图像已经被精心排列和组织好了</strong>—前 50 列是训练集,后 50 列是测试集,并且每一行都只包含同一个数字的不同书写样本。</p><h3 id="3-数据重塑的原因">3. 数据重塑的原因</h3><p>问:为什么要调节成一维数组?</p><p>答:将图像块调整为一维数组是为了适应机器学习模型的输入要求。大多数机器学习模型,特别是像 KNN 这样的简单模型,都期望输入数据是一维的。这样做可以将每个图像块展平成一个长向量,其中每个元素都是图像中的一个像素值。这样处理后,每个图像块都变成了一个 400 维的向量(20x20 像素的图像块),这使得模型可以更容易地处理和比较这些数据。</p><h2 id="总结">总结</h2><p>KNN 算法是一种简单而有效的机器学习算法,特别适合初学者入门机器学习。通过本文的介绍和代码实现,你应该对 KNN 算法的原理和应用有了基本的了解。手写数字识别是一个经典的机器学习问题,通过这个问题,你可以学习到数据预处理、模型训练和评估等机器学习的基本流程。</p><p>虽然 KNN 算法简单易懂,但在处理大规模数据集时可能面临性能瓶颈。因此,在实际应用中,我们需要根据具体需求和数据特点来选择合适的算法和数据结构。</p>]]></content>
<categories>
<category> Learn </category>
</categories>
</entry>
<entry>
<title>神经网络01:FashionMNIST数据集介绍与展示</title>
<link href="/%E5%AD%A6%E7%82%B9%E4%B9%A0-2025-07-19-net01.html"/>
<url>/%E5%AD%A6%E7%82%B9%E4%B9%A0-2025-07-19-net01.html</url>
<content type="html"><![CDATA[<h2 id="介绍-FashionMNIST-数据集">介绍 FashionMNIST 数据集</h2><p>FashionMNIST 是一个用于图像分类任务的基准数据集,由 Zalando Research 提供,旨在替代经典的 MNIST 手写数字数据集。该数据集包含 70,000 张 28×28 像素的灰度图像,分为 10 个类别,每个类别代表一种不同的服装或配饰。FashionMNIST 数据集因其多样性和适中的难度,被广泛用于计算机视觉和机器学习领域的研究和教学,尤其是图像分类任务。</p><h3 id="数据集的组成">数据集的组成</h3><p>FashionMNIST 数据集由以下两个主要部分组成:</p><ol><li><p><strong>训练集</strong>:</p><ul><li>包含 60,000 张图像,用于训练模型。</li><li>每张图像的尺寸为 28×28 像素,是灰度图像(单通道)。</li></ul></li><li><p><strong>测试集</strong>:</p><ul><li>包含 10,000 张图像,用于评估模型的性能。</li><li>每张图像的尺寸同样为 28×28 像素,也是灰度图像(单通道)。</li></ul></li></ol><h3 id="数据集的类别">数据集的类别</h3><p>FashionMNIST 数据集的图像被分为以下 10 个类别,每个类别代表一种不同的服装或配饰:</p><table><thead><tr><th>标签</th><th>类别</th></tr></thead><tbody><tr><td>0</td><td>T恤/上衣</td></tr><tr><td>1</td><td>裤子</td></tr><tr><td>2</td><td>套头衫</td></tr><tr><td>3</td><td>连衣裙</td></tr><tr><td>4</td><td>外套</td></tr><tr><td>5</td><td>凉鞋</td></tr><tr><td>6</td><td>T恤/衬衫</td></tr><tr><td>7</td><td>运动鞋</td></tr><tr><td>8</td><td>包</td></tr><tr><td>9</td><td>踝靴</td></tr></tbody></table><h3 id="数据集的特点">数据集的特点</h3><p>FashionMNIST 数据集具有以下特点,使其成为图像分类任务的理想选择:</p><ol><li><p><strong>多样性</strong>:</p><ul><li>数据集中的图像涵盖了多种服装和配饰,具有较高的多样性。这使得模型能够学习到不同类别之间的显著特征差异。</li></ul></li><li><p><strong>平衡性</strong>:</p><ul><li>每个类别包含相同数量的图像(训练集每个类别 6,000 张,测试集每个类别 1,000 张),确保了数据的平衡性。这种平衡性有助于模型在不同类别之间进行公平的分类。</li></ul></li><li><p><strong>灰度图像</strong>:</p><ul><li>图像是灰度的,每个像素的值范围为 0 到 255。这种简单的图像格式使得数据集易于处理和加载,同时也降低了计算复杂度。</li></ul></li><li><p><strong>适中的难度</strong>:</p><ul><li>与 MNIST 手写数字数据集相比,FashionMNIST 的图像更加复杂,但难度适中。它既不会过于简单,也不会过于困难,适合用于教学和研究。</li></ul></li><li><p><strong>广泛的应用</strong>:</p><ul><li>FashionMNIST 数据集被广泛用于图像分类任务的研究,包括但不限于卷积神经网络(<code>CNN</code>)、循环神经网络(<code>RNN</code>)和传统机器学习算法。它也常用于比较不同算法的性能。</li></ul></li></ol><h3 id="数据集的用途">数据集的用途</h3><p>FashionMNIST 数据集在计算机视觉和机器学习领域有广泛的应用,主要包括以下方面:</p><ol><li><p><strong>图像分类</strong>:</p><ul><li>该数据集主要用于图像分类任务,训练和评估模型对不同服装和配饰的分类能力。</li></ul></li><li><p><strong>算法研究</strong>:</p><ul><li>由于其适中的难度和多样性,FashionMNIST 数据集常用于研究和比较不同机器学习算法的性能,包括深度学习算法和传统机器学习算法。</li></ul></li><li><p><strong>教学和实践</strong>:</p><ul><li>FashionMNIST 数据集被广泛用于教学和实践项目,帮助初学者学习如何处理图像数据、构建和训练模型以及评估模型性能。</li></ul></li><li><p><strong>迁移学习</strong>:</p><ul><li>该数据集也常用于迁移学习任务,通过在 FashionMNIST 上预训练模型,然后将其应用于其他更复杂的图像分类任务。</li></ul></li></ol><h2 id="数据集的加载与可视化">数据集的加载与可视化</h2><p>接下来,我们将通过代码展示如何使用 PyTorch 和 torchvision 库加载 FashionMNIST 数据集,并对数据进行预处理和可视化。</p><h3 id="加载数据集">加载数据集</h3><p>使用 <code>torchvision.datasets.FashionMNIST</code> 加载数据集,数据集被存储在指定的路径下(<code>root='./data'</code>),并且在加载时应用了一系列的预处理操作,包括调整图像大小(<code>transforms.Resize(size=224)</code>)和将图像转换为张量(<code>transforms.ToTensor()</code>)。数据集被设置为训练集(<code>train=True</code>),并且在本地不存在数据集时会自动从网络下载(<code>download=True</code>)。</p><h3 id="创建数据加载器">创建数据加载器</h3><p>使用 <code>torch.utils.data.DataLoader</code> 创建了一个数据加载器,用于在训练过程中逐批次加载数据。数据加载器的批次大小设置为 64(<code>batch_size=64</code>),并且在每个 epoch 开始时会随机打乱数据(<code>shuffle=True</code>)。</p><h3 id="获取一个批次的数据">获取一个批次的数据</h3><p>通过遍历数据加载器,获取了第一个批次的数据(<code>b_x</code> 和 <code>b_y</code>),分别表示图像和对应的标签。</p><h3 id="数据转换">数据转换</h3><p>将批次的图像张量转换为 <code>NumPy</code> 数组,并移除了第1维(通道维),因为 FashionMNIST 数据集中的图像是灰度图像,通道数为1。将批次的标签张量也转换为 <code>NumPy</code> 数组,便于后续处理。</p><h3 id="可视化">可视化</h3><p>使用 <code>Matplotlib</code> 库创建了一个图像窗口,显示了一个批次的图像。每个图像都被显示为灰度图,并且图像的类别标签被作为标题显示在每个子图上。通过调整子图之间的间距和关闭坐标轴,使得图像显示更加清晰。</p><h3 id="代码及结果展示部分">代码及结果展示部分</h3><p>代码如下:</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 导入必要的库</span></span><br><span class="line"><span class="keyword">from</span> torchvision.datasets <span class="keyword">import</span> FashionMNIST <span class="comment"># 用于加载FashionMNIST数据集</span></span><br><span class="line"><span class="keyword">from</span> torchvision <span class="keyword">import</span> transforms <span class="comment"># 用于数据预处理</span></span><br><span class="line"><span class="keyword">import</span> torch.utils.data <span class="keyword">as</span> Data <span class="comment"># 用于创建数据加载器</span></span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np <span class="comment"># 用于数值计算</span></span><br><span class="line"><span class="keyword">import</span> matplotlib.pyplot <span class="keyword">as</span> plt <span class="comment"># 用于图像可视化</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 加载FashionMNIST数据集</span></span><br><span class="line"><span class="comment"># root='./data':指定数据集的存储路径</span></span><br><span class="line"><span class="comment"># train=True:表示加载训练集</span></span><br><span class="line"><span class="comment"># transform=...:对数据进行预处理,包括调整图像大小为224×224,并将其转换为张量</span></span><br><span class="line"><span class="comment"># download=True:如果本地没有数据集,则从网络下载</span></span><br><span class="line">train_data = FashionMNIST(root=<span class="string">'./data'</span>,</span><br><span class="line"> train=<span class="literal">True</span>,</span><br><span class="line"> transform=transforms.Compose([transforms.Resize(size=<span class="number">224</span>), transforms.ToTensor()]),</span><br><span class="line"> download=<span class="literal">True</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建数据加载器</span></span><br><span class="line"><span class="comment"># dataset=train_data:指定数据集</span></span><br><span class="line"><span class="comment"># batch_size=64:每个批次的样本数量</span></span><br><span class="line"><span class="comment"># shuffle=True:在每个epoch开始时,随机打乱数据</span></span><br><span class="line"><span class="comment"># num_workers=0:指定加载数据时使用的子进程数量,0表示在主进程中加载</span></span><br><span class="line">train_loader = Data.DataLoader(dataset=train_data,</span><br><span class="line"> batch_size=<span class="number">64</span>,</span><br><span class="line"> shuffle=<span class="literal">True</span>,</span><br><span class="line"> num_workers=<span class="number">0</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 从数据加载器中获取一个批次的数据</span></span><br><span class="line"><span class="comment"># 使用for循环遍历数据加载器,step表示批次的索引,b_x和b_y分别表示批次的图像和标签</span></span><br><span class="line"><span class="comment"># 通过if step > 0: break,确保只获取第一个批次的数据</span></span><br><span class="line"><span class="keyword">for</span> step, (b_x, b_y) <span class="keyword">in</span> <span class="built_in">enumerate</span>(train_loader):</span><br><span class="line"> <span class="keyword">if</span> step > <span class="number">0</span>:</span><br><span class="line"> <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 将批次的图像张量转换为Numpy数组,并移除第1维(通道维)</span></span><br><span class="line"><span class="comment"># b_x.squeeze():移除张量的第1维(通道维),因为FashionMNIST是灰度图像,通道数为1</span></span><br><span class="line"><span class="comment"># .numpy():将张量转换为Numpy数组</span></span><br><span class="line">batch_x = b_x.squeeze().numpy()</span><br><span class="line"></span><br><span class="line"><span class="comment"># 将批次的标签张量转换为Numpy数组</span></span><br><span class="line"><span class="comment"># .numpy():将张量转换为Numpy数组</span></span><br><span class="line">batch_y = b_y.numpy()</span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取训练集的类别标签</span></span><br><span class="line"><span class="comment"># train_data.classes:返回数据集的类别标签列表</span></span><br><span class="line">class_label = train_data.classes</span><br><span class="line"></span><br><span class="line"><span class="comment"># 打印批次图像的维度</span></span><br><span class="line"><span class="comment"># batch_x.shape:返回Numpy数组的维度</span></span><br><span class="line"><span class="built_in">print</span>(<span class="string">"The size of batch in train data:"</span>, batch_x.shape)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 可视化一个批次的图像</span></span><br><span class="line"><span class="comment"># 创建一个图像窗口,figsize=(12, 5)指定窗口大小</span></span><br><span class="line">plt.figure(figsize=(<span class="number">12</span>, <span class="number">5</span>))</span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用for循环遍历批次中的每个图像</span></span><br><span class="line"><span class="comment"># np.arange(len(batch_y)):生成一个从0到批次大小的整数序列</span></span><br><span class="line"><span class="keyword">for</span> ii <span class="keyword">in</span> np.arange(<span class="built_in">len</span>(batch_y)):</span><br><span class="line"> <span class="comment"># 创建子图,4行16列,ii + 1表示子图的索引</span></span><br><span class="line"> plt.subplot(<span class="number">4</span>, <span class="number">16</span>, ii + <span class="number">1</span>)</span><br><span class="line"> </span><br><span class="line"> <span class="comment"># 显示图像,cmap=plt.cm.gray表示使用灰度颜色映射</span></span><br><span class="line"> plt.imshow(batch_x[ii, :, :], cmap=plt.cm.gray)</span><br><span class="line"> </span><br><span class="line"> <span class="comment"># 设置子图的标题,显示图像的类别标签</span></span><br><span class="line"> plt.title(class_label[batch_y[ii]], size=<span class="number">10</span>)</span><br><span class="line"> </span><br><span class="line"> <span class="comment"># 关闭坐标轴</span></span><br><span class="line"> plt.axis(<span class="string">"off"</span>)</span><br><span class="line"> </span><br><span class="line"> <span class="comment"># 调整子图之间的间距</span></span><br><span class="line"> plt.subplots_adjust(wspace=<span class="number">0.05</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 显示图像窗口</span></span><br><span class="line">plt.show()</span><br></pre></td></tr></table></figure><p>输出结果如下:</p><div style="text-align: center; font-family: 'Arial', sans-serif;"> <div style="margin-bottom: 20px;"> <img src="images\learn_pic\2025-07-19_Fashion_MNIST_intro.jpg" alt="Fashion_MNIST数据集64张展示" style="width: 90%; height: auto; border-radius: 10px;"> <div style="font-size: 14px; color: #333; margin-top: 5px;"> Fashion_MNIST数据集64张展示 </div> </div></div>]]></content>
<categories>
<category> Learn </category>
</categories>
</entry>
<entry>
<title>图像边缘检测03:角点检测的三种方法</title>
<link href="/%E5%AD%A6%E7%82%B9%E4%B9%A0-2025-07-11-detection03.html"/>
<url>/%E5%AD%A6%E7%82%B9%E4%B9%A0-2025-07-11-detection03.html</url>
<content type="html"><![CDATA[<h1>角点检测的三种方法</h1><p>在计算机视觉和图像处理领域,角点检测技术至关重要,它被广泛应用于图像匹配、目标识别以及三维重建等任务。今天,我将重点介绍三种主流的角点检测方法:Harris角点检测、Shi-Tomasi角点检测以及亚像素级角点检测。通过Python语言的实践,我们来深入比较它们的精度和应用场景。</p><h2 id="Harris-角点检测">Harris 角点检测</h2><h3 id="Harris-角点检测算法简介">Harris 角点检测算法简介</h3><ol><li><strong>背景</strong><br>Harris角点检测算法于1988年由Chris Harris和Mike Stephens提出,是一种基于图像梯度信息的角点检测方法。它凭借计算简单、检测稳定且对噪声鲁棒性强等优势,被广泛应用于多个领域。</li><li><strong>原理</strong><ul><li><strong>梯度计算</strong>:使用Sobel算子或Scharr算子计算图像每个像素点的水平和垂直方向梯度,梯度信息反映了图像在每个像素点的变化率。</li><li><strong>自相关矩阵计算</strong>:计算图像中每个像素点邻域内的自相关矩阵,该矩阵是一个2×2的矩阵,能够反映邻域内的梯度变化情况。公式如下:<br>[<br>M = \begin{bmatrix} \sum I_x^2 & \sum I_x I_y \ \sum I_x I_y & \sum I_y^2 \end{bmatrix}<br>]</li><li><strong>角点响应函数计算</strong>:通过角点响应函数 (R = \det(M) - k \cdot \text{trace}^2(M)) 判断一个点是否为角点。其中,(\det(M)) 是自相关矩阵的行列式,(\text{trace}(M)) 是自相关矩阵的迹,(k) 是一个常数(通常取0.04到0.06之间)。如果 (R) 的值大于某个阈值,则认为该点为角点。</li></ul></li><li><strong>优势</strong><br>Harris角点检测算法的计算效率高、检测结果稳定且对噪声鲁棒性强。它能够精准地定位角点,适用于各种复杂场景。</li></ol><h3 id="cornerHarris-函数"><code>cornerHarris()</code> 函数</h3><p>在Python中,OpenCV库提供了<code>cv2.cornerHarris()</code>函数,用于实现Harris角点检测算法。函数原型如下:</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">cv2.cornerHarris(src, blockSize, ksize, k, dst=<span class="literal">None</span>)</span><br></pre></td></tr></table></figure><ul><li><strong>src</strong>:输入图像,必须是单通道的灰度图像。</li><li><strong>blockSize</strong>:用于计算自相关矩阵的邻域大小。通常取值为2、3或5。</li><li><strong>ksize</strong>:Sobel算子的孔径大小,通常为3。</li><li><strong>k</strong>:Harris角点检测算法中的自由参数,通常取值在0.04到0.06之间。</li><li><strong>dst</strong>:可选参数,输出的角点响应图像。</li></ul><p>该函数能够快速实现Harris角点检测,通过调整参数(如邻域大小、Sobel算子的孔径大小等),可以灵活地适应不同的图像处理需求。</p><h3 id="Harris-角点检测实例展示">Harris 角点检测实例展示</h3><p>Python代码如下:</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># cornerHarris()</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> cv2 <span class="keyword">as</span> cv</span><br><span class="line"></span><br><span class="line">img = cv.imread(<span class="string">"src.jpg"</span>, <span class="number">0</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用 Harris 角点检测算法对图像进行角点检测</span></span><br><span class="line"><span class="comment"># 参数解释:</span></span><br><span class="line"><span class="comment"># img:输入图像,必须是灰度图像</span></span><br><span class="line"><span class="comment"># 2:检测窗口的大小,表示在多大的邻域内检测角点</span></span><br><span class="line"><span class="comment"># 3:Sobel 算子的大小,用于计算图像的梯度</span></span><br><span class="line"><span class="comment"># 0.06:Harris 检测器的自由参数,影响角点检测的灵敏度</span></span><br><span class="line">dst = cv.cornerHarris(img, <span class="number">2</span>, <span class="number">3</span>, <span class="number">0.06</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 对检测结果进行阈值化操作,将角点突出显示</span></span><br><span class="line"><span class="comment"># 参数解释:</span></span><br><span class="line"><span class="comment"># dst:Harris 角点检测的结果图像</span></span><br><span class="line"><span class="comment"># 0:阈值化操作的阈值</span></span><br><span class="line"><span class="comment"># 255:阈值化后的最大值</span></span><br><span class="line"><span class="comment"># cv.THRESH_BINARY:阈值化类型,大于阈值的像素值设为255,其余设为0</span></span><br><span class="line">_, Harris_corner = cv.threshold(dst, <span class="number">0</span>, <span class="number">255</span>, cv.THRESH_BINARY)</span><br><span class="line"></span><br><span class="line">cv.imshow(<span class="string">"Harris_corner.jpg"</span>, Harris_corner)</span><br><span class="line">cv.imwrite(<span class="string">"Harris_corner.jpg"</span>, Harris_corner)</span><br><span class="line">cv.waitKey(<span class="number">0</span>)</span><br><span class="line">cv.destroyAllWindows()</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>结果图片如下:</p><div style="text-align: center; font-family: 'Arial', sans-serif;"> <!-- 第一张和第二张图片及其描述 --> <div style="display: flex; justify-content: center; margin-bottom: 20px;"> <div style="margin: 0 10px; text-align: center;"> <img src="/images/learn_pic/2025-07-11_Harris_corner_src_1.jpg" alt="原图像" style="width: 60%; height: auto; border-radius: 10px;"> <div style="font-size: 14px; color: #333; margin-top: 5px;">原图像</div> </div> <div style="margin: 0 10px; text-align: center;"> <img src="/images/learn_pic/2025-07-11_Harris_corner_2.jpg" alt="Harris角点检测图像" style="width: 60%; height: auto; border-radius: 10px;"> <div style="font-size: 14px; color: #333; margin-top: 5px;">Harris角点检测图像</div> </div> </div></div><p>同样在这里要把我们<strong>老演员</strong>拿出来遛一遛(doge</p><div style="text-align: center; font-family: 'Arial', sans-serif;"> <!-- 第一张和第二张图片及其描述 --> <div style="display: flex; justify-content: center; margin-bottom: 20px;"> <div style="margin: 0 10px; text-align: center;"> <img src="/images/learn_pic/2025-07-11-Harris_corner_src_2.jpg" alt="原图像" style="width: 100%; height: auto; border-radius: 10px;"> <div style="font-size: 14px; color: #333; margin-top: 5px;">原图像</div> </div> <div style="margin: 0 10px; text-align: center;"> <img src="/images/learn_pic/2025-07-11_Harris_corner_1.jpg" alt="Harris角点检测图像" style="width: 100%; height: auto; border-radius: 10px;"> <div style="font-size: 14px; color: #333; margin-top: 5px;">Harris角点检测图像</div> </div> </div></div><h3 id="Harris-角点检测的应用场景">Harris 角点检测的应用场景</h3><p>Harris角点检测算法在图像匹配、目标识别和三维重建等领域都有广泛的应用。例如,在图像匹配任务中,它能够精准地定位图像中的角点,为后续的特征匹配提供重要依据;在目标识别中,它提取的角点特征可用于目标定位和识别;在三维重建中,它提取的特征点可用于计算图像之间的匹配关系。</p><h2 id="Shi-Tomasi-角点检测">Shi-Tomasi 角点检测</h2><h3 id="Shi-Tomasi-角点检测算法简介">Shi-Tomasi 角点检测算法简介</h3><ol><li><strong>背景</strong><br>Shi-Tomasi角点检测算法由Shi和Tomasi在1994年提出,是对Harris角点检测算法的改进。它通过优化角点响应函数,进一步提高了角点检测的稳定性和准确性,特别适用于目标跟踪和运动估计等应用。</li><li><strong>原理</strong><ul><li><strong>梯度计算</strong>:计算图像在每个像素点的水平方向和垂直方向的梯度,梯度信息反映了图像在每个像素点的变化率。</li><li><strong>自相关矩阵计算</strong>:计算图像中每个像素点邻域内的自相关矩阵。公式如下:<br>[<br>M = \begin{bmatrix} \sum I_x^2 & \sum I_x I_y \ \sum I_x I_y & \sum I_y^2 \end{bmatrix}<br>]</li><li><strong>角点响应函数计算</strong>:Shi-Tomasi算法通过计算自相关矩阵的最小特征值来判断一个点是否为角点。公式为 (R = \min(\lambda_1, \lambda_2)),其中,(\lambda_1) 和 (\lambda_2) 是自相关矩阵的两个特征值。如果 (R) 的值大于某个阈值,则认为该点为角点。</li></ul></li><li><strong>优势</strong><br>Shi-Tomasi角点检测算法的计算效率高、检测结果稳定且对噪声鲁棒性强。它能够更准确地检测出角点。</li></ol><h3 id="goodFeaturesToTrack-函数"><code>goodFeaturesToTrack()</code> 函数</h3><p>在Python中,OpenCV库提供了<code>cv2.goodFeaturesToTrack()</code>函数,用于实现Shi-Tomasi角点检测算法。函数原型如下:</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">cv2.goodFeaturesToTrack(image, maxCorners, qualityLevel, minDistance, corners=<span class="literal">None</span>, mask=<span class="literal">None</span>, blockSize=<span class="literal">None</span>, useHarrisDetector=<span class="literal">None</span>, k=<span class="literal">None</span>)</span><br></pre></td></tr></table></figure><ul><li><strong>image</strong>:输入图像,必须是单通道的灰度图像。</li><li><strong>maxCorners</strong>:希望检测到的最大角点数量。</li><li><strong>qualityLevel</strong>:角点检测的质量水平,范围在0到1之间。它表示最小的特征值与最大特征值的比率。</li><li><strong>minDistance</strong>:检测到的角点之间的最小欧几里得距离。</li><li><strong>corners</strong>:可选参数,输出的角点列表。</li><li><strong>mask</strong>:可选参数,指定检测角点的区域。</li><li><strong>blockSize</strong>:计算自相关矩阵的邻域大小,默认值为3。</li><li><strong>useHarrisDetector</strong>:布尔值,是否使用Harris角点检测算法。默认为<code>False</code>,即使用Shi-Tomasi算法。</li><li><strong>k</strong>:Harris角点检测算法中的自由参数,仅当<code>useHarrisDetector=True</code>时有效。</li></ul><p>该函数能够快速实现Shi-Tomasi角点检测,通过调整参数(如最大角点数量、质量水平、最小距离等),可以灵活地适应不同的图像处理需求。</p><h3 id="Shi-Tomasi角点检测实例展示">Shi-Tomasi角点检测实例展示</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Shi-Tomasi</span></span><br><span class="line"><span class="comment"># goodFeaturesToTrack()</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"><span class="keyword">import</span> cv2 <span class="keyword">as</span> cv</span><br><span class="line"></span><br><span class="line"><span class="comment"># 定义角点检测的参数</span></span><br><span class="line">max_corners = <span class="number">20</span> <span class="comment"># 最大角点数量</span></span><br><span class="line">quality_level = <span class="number">0.01</span> <span class="comment"># 角点检测的质量水平(阈值)</span></span><br><span class="line">min_dist = <span class="number">50</span> <span class="comment"># 角点之间的最小欧几里得距离</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 读取图像,以灰度模式加载</span></span><br><span class="line"><span class="comment"># 参数 'src.jpg' 是图像文件的路径,0 表示以灰度模式读取</span></span><br><span class="line">img = cv.imread(<span class="string">'src.jpg'</span>, <span class="number">0</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用 Shi-Tomasi 角点检测算法检测角点</span></span><br><span class="line"><span class="comment"># 参数解释:</span></span><br><span class="line"><span class="comment"># img:输入图像,必须是灰度图像</span></span><br><span class="line"><span class="comment"># max_corners:检测到的最大角点数量</span></span><br><span class="line"><span class="comment"># quality_level:角点检测的质量水平,值越低,检测到的角点越多</span></span><br><span class="line"><span class="comment"># min_dist:角点之间的最小欧几里得距离</span></span><br><span class="line">corners = cv.goodFeaturesToTrack(img, max_corners, quality_level, min_dist)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 将检测到的角点坐标转换为整数类型</span></span><br><span class="line"><span class="comment"># np.int32 是 32 位整数类型,用于确保角点坐标值不会被截断</span></span><br><span class="line">corners = np.int32(corners)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 遍历检测到的角点,并在图像上绘制圆形标记</span></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> corners:</span><br><span class="line"> <span class="comment"># ravel() 将角点坐标数组展平为一维数组</span></span><br><span class="line"> x, y = i.ravel()</span><br><span class="line"> <span class="comment"># 在图像上绘制圆形标记</span></span><br><span class="line"> <span class="comment"># 参数解释:</span></span><br><span class="line"> <span class="comment"># (x, y):圆心坐标</span></span><br><span class="line"> <span class="comment"># 5:圆的半径</span></span><br><span class="line"> <span class="comment"># (128, 128, 128):圆的颜色(灰度值)</span></span><br><span class="line"> <span class="comment"># -1:填充圆</span></span><br><span class="line"> cv.circle(img, (x, y), <span class="number">5</span>, (<span class="number">128</span>, <span class="number">128</span>, <span class="number">128</span>), -<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line">cv.imwrite(<span class="string">'Shi_Tomasi_corner_2.jpg'</span>, img)</span><br><span class="line">cv.imshow(<span class="string">'Shi_Tomasi_corner_2.jpg'</span>, img)</span><br><span class="line">cv.waitKey(<span class="number">0</span>)</span><br><span class="line">cv.destroyAllWindows()</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>结果展示如下:</p><div style="text-align: center; font-family: 'Arial', sans-serif;"> <!-- 第一张和第二张图片及其描述 --> <div style="display: flex; justify-content: center; margin-bottom: 20px;"> <div style="margin: 0 10px; text-align: center;"> <img src="/images/learn_pic/2025-07-11_Shi_Tomasi_corner_src_2.jpg" alt="原图像" style="width: 100%; height: auto; border-radius: 10px;"> <div style="font-size: 14px; color: #333; margin-top: 5px;">原图像</div> </div> <div style="margin: 0 10px; text-align: center;"> <img src="/images/learn_pic/2025-07-11_Shi_Tomasi_corner_2.jpg" alt="Shi-Tomasi角点检测图像" style="width: 100%; height: auto; border-radius: 10px;"> <div style="font-size: 14px; color: #333; margin-top: 5px;">Shi-Tomasi角点检测图像</div> </div> </div></div><h3 id="Shi-Tomasi-角点检测的应用场景">Shi-Tomasi 角点检测的应用场景</h3><p>Shi-Tomasi角点检测算法在目标跟踪、图像拼接和三维重建等领域都有广泛的应用。例如,在目标跟踪任务中,它能够精准地定位图像中的角点,为后续的目标跟踪提供重要依据;在图像拼接中,它提取的特征点可用于计算图像之间的匹配关系。</p><h2 id="亚像素级角点检测">亚像素级角点检测</h2><h3 id="亚像素级角点检测简介">亚像素级角点检测简介</h3><ol><li><strong>背景</strong><br>亚像素级角点检测是一种将角点定位精度提升到亚像素级别的技术。它通过优化算法,在像素级别检测的基础上进一步细化角点的位置,从而提高角点检测的精度。在目标跟踪、三维重建和图像拼接等对精度要求较高的应用中具有重要意义。</li><li><strong>原理</strong><ul><li><strong>像素级别角点检测</strong>:使用传统的角点检测算法(如Harris、Shi-Tomasi或FAST)检测出像素级别的角点。</li><li><strong>亚像素级优化</strong>:在像素级别检测到的角点附近,使用迭代优化算法进一步搜索更精确的角点位置。通常使用零阶矩和一阶矩的组合来计算角点的亚像素位置。优化算法的目标是最小化角点附近的梯度变化,从而找到更精确的角点位置。</li><li><strong>收敛条件</strong>:当迭代优化算法达到预设的迭代次数或角点位置的变化小于某个阈值时,算法停止迭代。</li></ul></li><li><strong>优势</strong><br>亚像素级角点检测的主要优势在于其高精度和对噪声的鲁棒性。它能够显著提高角点检测的精度,适用于对精度要求较高的应用。</li></ol><h3 id="cornerSubPix-函数"><code>cornerSubPix()</code> 函数</h3><p>在Python中,OpenCV库提供了<code>cv2.cornerSubPix()</code>函数,用于实现亚像素级角点检测。函数原型如下:</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">cv2.cornerSubPix(image, corners, winSize, zeroZone, criteria)</span><br></pre></td></tr></table></figure><ul><li><strong>image</strong>:输入图像,必须是单通道的灰度图像。</li><li><strong>corners</strong>:像素级别检测到的角点列表,通常由<code>cv2.goodFeaturesToTrack()</code>或其他角点检测函数返回。</li><li><strong>winSize</strong>:搜索窗口的大小,格式为<code>(winSize, winSize)</code>,表示以每个角点为中心的搜索窗口的半径。</li><li><strong>zeroZone</strong>:死区的大小,格式为<code>(zeroZone, zeroZone)</code>,表示在搜索窗口中心的区域内不考虑梯度信息。如果为<code>(-1, -1)</code>,则表示没有死区。</li><li><strong>criteria</strong>:迭代优化算法的终止条件,通常是一个包含最大迭代次数和精度阈值的元组。例如,<code>criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)</code>表示最大迭代次数为30,精度阈值为0.001。</li></ul><p>该函数能够在像素级别检测的基础上进一步优化角点位置,从而提高角点检测的精度。通过调整参数(如搜索窗口大小、死区大小和迭代终止条件等),可以灵活地适应不同的图像处理需求。</p><h3 id="示例">示例</h3><p>以下是一个使用OpenCV的<code>cv2.cornerSubPix()</code>函数进行亚像素级角点检测的简单示例,并与<code>Shi-Tomasi</code>角点检测的<code>goodFeaturesToTrack()</code>函数进行坐标精度的对比。</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 比较 Shi-Tomasi 角点检测与亚像素角点检测的精度</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> cv2 <span class="keyword">as</span> cv</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line">src = cv.imread(<span class="string">'src.jpg'</span>, <span class="number">0</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 定义 Shi-Tomasi 角点检测的参数</span></span><br><span class="line">max_corners = <span class="number">20</span> <span class="comment"># 最大角点数量</span></span><br><span class="line">quality_level = <span class="number">0.01</span> <span class="comment"># 角点检测的质量水平,值越低,检测到的角点越多</span></span><br><span class="line">min_dist = <span class="number">50</span> <span class="comment"># 角点之间的最小欧几里得距离</span></span><br><span class="line">block_size = <span class="number">3</span> <span class="comment"># 梯度计算的窗口大小</span></span><br><span class="line">use_harris = <span class="literal">False</span> <span class="comment"># 是否使用 Harris 角点检测器</span></span><br><span class="line">k = <span class="number">0.04</span> <span class="comment"># Harris 角点检测器的自由参数(仅在 use_harris=True 时有效)</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建图像的拷贝,用于绘制角点</span></span><br><span class="line">copy1 = np.copy(src) <span class="comment"># 用于绘制 Shi-Tomasi 角点</span></span><br><span class="line">copy2 = np.copy(src) <span class="comment"># 用于绘制亚像素角点</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用 Shi-Tomasi 角点检测算法检测角点</span></span><br><span class="line"><span class="comment"># 参数解释:</span></span><br><span class="line"><span class="comment"># src:输入图像,必须是灰度图像</span></span><br><span class="line"><span class="comment"># max_corners:检测到的最大角点数量</span></span><br><span class="line"><span class="comment"># quality_level:角点检测的质量水平</span></span><br><span class="line"><span class="comment"># min_dist:角点之间的最小欧几里得距离</span></span><br><span class="line"><span class="comment"># None:不指定掩模(默认检测整个图像)</span></span><br><span class="line"><span class="comment"># blockSize:梯度计算的窗口大小</span></span><br><span class="line"><span class="comment"># useHarrisDetector:是否使用 Harris 角点检测器</span></span><br><span class="line"><span class="comment"># k:Harris 角点检测器的自由参数</span></span><br><span class="line">corners = cv.goodFeaturesToTrack(src, max_corners, quality_level, min_dist, <span class="literal">None</span>, blockSize=block_size, useHarrisDetector=use_harris, k=k)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 定义绘制角点的圆的半径</span></span><br><span class="line">radius = <span class="number">8</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 遍历检测到的角点,并在图像上绘制圆形标记</span></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(corners.shape[<span class="number">0</span>]):</span><br><span class="line"> <span class="comment"># 将浮点数坐标转换为整数类型,因为 cv2.circle 需要整数坐标</span></span><br><span class="line"> x, y = <span class="built_in">int</span>(corners[i, <span class="number">0</span>, <span class="number">0</span>]), <span class="built_in">int</span>(corners[i, <span class="number">0</span>, <span class="number">1</span>])</span><br><span class="line"> <span class="comment"># 在图像上绘制圆形标记</span></span><br><span class="line"> <span class="comment"># 参数解释:</span></span><br><span class="line"> <span class="comment"># (x, y):圆心坐标</span></span><br><span class="line"> <span class="comment"># radius:圆的半径</span></span><br><span class="line"> <span class="comment"># (128, 128, 128):圆的颜色(灰度值)</span></span><br><span class="line"> <span class="comment"># -1:填充圆</span></span><br><span class="line"> cv.circle(copy1, (x, y), radius, (<span class="number">128</span>, <span class="number">128</span>, <span class="number">128</span>), -<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 将绘制了 Shi-Tomasi 角点的图像保存到文件</span></span><br><span class="line">cv.imwrite(<span class="string">"Shi_tomasi.jpg"</span>, copy1)</span><br><span class="line">cv.imshow(<span class="string">'Shi_tomasi.jpg'</span>, copy1)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 打印 Shi-Tomasi 角点坐标</span></span><br><span class="line"><span class="built_in">print</span>(<span class="string">"Shi-Tomasi 角点坐标"</span>)</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(corners.shape[<span class="number">0</span>]):</span><br><span class="line"> <span class="comment"># 打印角点坐标,保留浮点数的精度</span></span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"坐标"</span>, i, <span class="string">": ("</span>, corners[i, <span class="number">0</span>, <span class="number">0</span>], <span class="string">","</span>, corners[i, <span class="number">0</span>, <span class="number">1</span>], <span class="string">")"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 定义亚像素角点检测的参数</span></span><br><span class="line">win_size = (<span class="number">5</span>, <span class="number">5</span>) <span class="comment"># 搜索窗口的大小</span></span><br><span class="line">zero_zone = (-<span class="number">1</span>, -<span class="number">1</span>) <span class="comment"># 零区域的大小(-1 表示没有零区域)</span></span><br><span class="line">criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_COUNT, <span class="number">40</span>, <span class="number">0.001</span>) <span class="comment"># 终止条件</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用亚像素角点检测算法进一步精确角点位置</span></span><br><span class="line"><span class="comment"># 参数解释:</span></span><br><span class="line"><span class="comment"># src:输入图像,必须是灰度图像</span></span><br><span class="line"><span class="comment"># corners:初始角点坐标(由 Shi-Tomasi 检测得到)</span></span><br><span class="line"><span class="comment"># win_size:搜索窗口的大小</span></span><br><span class="line"><span class="comment"># zero_zone:零区域的大小</span></span><br><span class="line"><span class="comment"># criteria:终止条件</span></span><br><span class="line">corners = cv.cornerSubPix(src, corners, win_size, zero_zone, criteria)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 遍历亚像素角点,并在图像上绘制圆形标记</span></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(corners.shape[<span class="number">0</span>]):</span><br><span class="line"> <span class="comment"># 将浮点数坐标转换为整数类型,因为 cv2.circle 需要整数坐标</span></span><br><span class="line"> x, y = <span class="built_in">int</span>(corners[i, <span class="number">0</span>, <span class="number">0</span>]), <span class="built_in">int</span>(corners[i, <span class="number">0</span>, <span class="number">1</span>])</span><br><span class="line"> <span class="comment"># 在图像上绘制圆形标记</span></span><br><span class="line"> cv.circle(copy2, (x, y), radius, (<span class="number">128</span>, <span class="number">128</span>, <span class="number">128</span>), -<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line">cv.imwrite(<span class="string">"Sub_pixel.jpg"</span>, copy2)</span><br><span class="line">cv.imshow(<span class="string">"Sub_pixel.jpg"</span>, copy2)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 打印亚像素角点坐标,保留高精度的小数点</span></span><br><span class="line"><span class="built_in">print</span>(<span class="string">"亚像素角点坐标打印"</span>)</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(corners.shape[<span class="number">0</span>]):</span><br><span class="line"> <span class="comment"># 使用格式化字符串打印角点坐标,保留小数点后五位</span></span><br><span class="line"> <span class="built_in">print</span>(<span class="string">f"坐标 <span class="subst">{i}</span>: (<span class="subst">{corners[i, <span class="number">0</span>, <span class="number">0</span>]:<span class="number">.5</span>f}</span>, <span class="subst">{corners[i, <span class="number">0</span>, <span class="number">1</span>]:<span class="number">.5</span>f}</span>)"</span>)</span><br><span class="line"></span><br><span class="line">cv.waitKey(<span class="number">0</span>)</span><br><span class="line">cv.destroyAllWindows()</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>两种方法图像对比(好像用肉眼看不出来,毕竟是小数的区别:</p><div style="text-align: center; font-family: 'Arial', sans-serif;"> <!-- 第一张和第二张图片及其描述 --> <div style="display: flex; justify-content: center; margin-bottom: 20px;"> <div style="margin: 0 10px; text-align: center;"> <img src="/images/learn_pic/2025-07-11_Shi_Tomasi.jpg" alt="Shi-Tomasi角点检测图像" style="width: 100%; height: auto; border-radius: 10px;"> <div style="font-size: 14px; color: #333; margin-top: 5px;">Shi-Tomasi角点检测图像</div> </div> <div style="margin: 0 10px; text-align: center;"> <img src="/images/learn_pic/2025-07-11_Sub_Pixel.jpg" alt="亚像素级角点检测图像" style="width: 100%; height: auto; border-radius: 10px;"> <div style="font-size: 14px; color: #333; margin-top: 5px;">亚像素级角点检测图像</div> </div> </div></div><p>以下为 Shi-Tomasi 角点检测<strong>坐标打印</strong>结果:</p><figure class="highlight text"><table><tr><td class="code"><pre><span class="line">Shi-Tomasi 角点坐标</span><br><span class="line">坐标 0 : ( 449.0 , 285.0 )</span><br><span class="line">坐标 1 : ( 551.0 , 228.0 )</span><br><span class="line">坐标 2 : ( 347.0 , 228.0 )</span><br><span class="line">坐标 3 : ( 551.0 , 112.0 )</span><br><span class="line">坐标 4 : ( 347.0 , 112.0 )</span><br><span class="line">坐标 5 : ( 449.0 , 55.0 )</span><br><span class="line">坐标 6 : ( 144.0 , 141.0 )</span><br><span class="line">坐标 7 : ( 275.0 , 174.0 )</span><br><span class="line">坐标 8 : ( 71.0 , 174.0 )</span><br><span class="line">坐标 9 : ( 409.0 , 106.0 )</span><br><span class="line">坐标 10 : ( 173.0 , 289.0 )</span><br><span class="line">坐标 11 : ( 173.0 , 58.0 )</span><br><span class="line">坐标 12 : ( 199.0 , 203.0 )</span><br><span class="line">坐标 13 : ( 147.0 , 203.0 )</span><br><span class="line">坐标 14 : ( 487.0 , 229.0 )</span><br><span class="line">坐标 15 : ( 411.0 , 229.0 )</span><br><span class="line">坐标 16 : ( 198.0 , 144.0 )</span><br><span class="line">坐标 17 : ( 487.0 , 111.0 )</span><br><span class="line">坐标 18 : ( 525.0 , 170.0 )</span><br><span class="line">坐标 19 : ( 378.0 , 170.0 )</span><br></pre></td></tr></table></figure><p>以下为亚像素角点<strong>坐标打印</strong>结果:</p><figure class="highlight text"><table><tr><td class="code"><pre><span class="line">亚像素角点坐标打印</span><br><span class="line">坐标 0: (449.00000, 285.00000)</span><br><span class="line">坐标 1: (551.00000, 228.00000)</span><br><span class="line">坐标 2: (347.00000, 228.00000)</span><br><span class="line">坐标 3: (551.00000, 112.00000)</span><br><span class="line">坐标 4: (347.00000, 112.00000)</span><br><span class="line">坐标 5: (449.00000, 55.00000)</span><br><span class="line">坐标 6: (147.41205, 144.31113)</span><br><span class="line">坐标 7: (275.00000, 174.00000)</span><br><span class="line">坐标 8: (71.00000, 174.00000)</span><br><span class="line">坐标 9: (411.58264, 110.43745)</span><br><span class="line">坐标 10: (173.00000, 289.00000)</span><br><span class="line">坐标 11: (173.00000, 58.00000)</span><br><span class="line">坐标 12: (198.89714, 203.04985)</span><br><span class="line">坐标 13: (147.41327, 203.26128)</span><br><span class="line">坐标 14: (486.85046, 229.60292)</span><br><span class="line">坐标 15: (411.14954, 229.60292)</span><br><span class="line">坐标 16: (198.69484, 143.99083)</span><br><span class="line">坐标 17: (486.85046, 110.39708)</span><br><span class="line">坐标 18: (525.00000, 170.00000)</span><br><span class="line">坐标 19: (378.37875, 170.00000)</span><br></pre></td></tr></table></figure><p>显然,<strong>亚像素级</strong>角点检测的精度更高,坐标精度可以达到<strong>小数点后五位</strong>。</p><h3 id="应用场景">应用场景</h3><p>亚像素级角点检测在目标跟踪、三维重建和图像拼接等领域都有广泛的应用。例如,在目标跟踪任务中,它能够提供更精确的角点位置,从而提高目标跟踪的精度;在三维重建中,它提取的特征点可用于计算图像之间的匹配关系。</p><h2 id="三种角点检测技术的对比">三种角点检测技术的对比</h2><p>为了更好地理解这三种角点检测技术的特点和适用场景,我们从以下几个维度进行对比:</p><table><thead><tr><th>特性</th><th>Harris 角点检测</th><th>Shi-Tomasi 角点检测</th><th>亚像素级角点检测</th></tr></thead><tbody><tr><td><strong>检测精度</strong></td><td>像素级</td><td>像素级</td><td>亚像素级</td></tr><tr><td><strong>计算复杂度</strong></td><td>中</td><td>中</td><td>高</td></tr><tr><td><strong>对噪声的鲁棒性</strong></td><td>强</td><td>强</td><td>更强</td></tr><tr><td><strong>适用场景</strong></td><td>图像匹配<br/>目标识别<br/>三维重建<br/></td><td>目标跟踪<br/>图像拼接<br/>三维重建</td><td>目标跟踪<br/>三维重建<br/>图像拼接</td></tr></tbody></table><p>通过上表可以看出,Harris和Shi-Tomasi角点检测算法适用于大多数常见的计算机视觉任务,并且具有较低的计算复杂度和较强的鲁棒性。而亚像素级角点检测则在精度上有显著提升,适用于对精度要求更高的场景,但计算复杂度也相对较高。</p>]]></content>
<categories>
<category> Learn </category>
</categories>
</entry>
<entry>
<title>图像边缘检测02:最好用的Canny算法</title>
<link href="/%E5%AD%A6%E7%82%B9%E4%B9%A0-2025-07-10-detection02.html"/>
<url>/%E5%AD%A6%E7%82%B9%E4%B9%A0-2025-07-10-detection02.html</url>
<content type="html"><![CDATA[<h1>Canny算法与Canny()函数:基于多步骤优化的边缘检测技术</h1><p> 在图像处理和计算机视觉领域,边缘检测是提取图像特征的关键步骤之一。Canny算法是一种广泛使用的边缘检测方法,以其高精度和鲁棒性而闻名。它通过多步骤优化,能够有效检测出图像中的边缘,同时抑制噪声的影响。Canny算法的提出者John F. Canny在1986年定义了最优边缘检测的三个标准,这些标准为Canny算法的设计提供了理论基础。本文将详细介绍Canny算法及其在Python中通过OpenCV库实现的<code>cv2.Canny()</code>函数。</p><p>老样子,还是以这张图片进行实验(实在貌美无法抵挡)</p><div style="text-align: center; font-family: 'Arial', sans-serif;"> <div style="margin-bottom: 20px;"> <img src="images/top_pic/top_default.jpg" alt="新加坡-天际线" style="width: 90%; height: auto; border-radius: 10px;"> <div style="font-size: 14px; color: #333; margin-top: 5px;"> 新加坡-天际线 </div> </div></div><h2 id="一、Canny算法简介">一、Canny算法简介</h2><h3 id="(一)算法背景">(一)算法背景</h3><p>Canny算法由John F. Canny在1986年提出,是一种经典的边缘检测算法。它旨在通过优化边缘检测的精度和抗噪性能,解决传统边缘检测方法(如Sobel和Laplacian)在噪声环境下的不足。Canny算法通过多步骤处理,能够检测出清晰且连续的边缘,同时抑制噪声带来的误检。</p><h3 id="(二)最优边缘检测的三个标准">(二)最优边缘检测的三个标准</h3><p>Canny算法的设计基于以下三个标准,这些标准确保了边缘检测的高精度和鲁棒性:</p><ol><li><p><strong>低错误率(Low Error Rate)</strong></p><ul><li><strong>定义</strong>:算法应尽可能多地检测到图像中的实际边缘,同时尽量减少因噪声而产生的误检。</li><li><strong>解释</strong>:这意味着边缘检测器需要在噪声环境下保持较高的检测精度,避免将噪声误判为边缘,同时确保真实边缘不被漏检。</li></ul></li><li><p><strong>高定位性(Good Localization)</strong></p><ul><li><strong>定义</strong>:检测到的边缘点应尽可能接近图像中真实边缘的实际位置。</li><li><strong>解释</strong>:边缘检测器需要能够精确地定位边缘,使得检测到的边缘像素与实际边缘之间的距离最小化。</li></ul></li><li><p><strong>最小响应(Minimal Response)</strong></p><ul><li><strong>定义</strong>:图像中的每个边缘应只被标记一次,避免对同一边缘进行多次响应。</li><li><strong>解释</strong>:这意味着边缘检测器需要避免对同一边缘产生多个响应,同时尽量减少因噪声或边缘模糊而产生的虚假边缘。</li></ul></li></ol><h3 id="(三)算法原理">(三)算法原理</h3><p>Canny算法的核心思想是通过以下步骤实现边缘检测:</p><ol><li><p><strong>噪声抑制</strong>:</p><ul><li>在进行边缘检测之前,通常需要对图像进行平滑处理,以减少噪声对边缘检测的影响。Canny算法通常使用高斯滤波器对图像进行平滑处理。</li><li>高斯滤波器通过卷积操作对图像进行平滑,其卷积核是一个高斯函数,能够有效抑制高频噪声。</li></ul></li><li><p><strong>梯度计算</strong>:</p><ul><li>使用Sobel算子或Prewitt算子计算图像在水平方向和垂直方向上的梯度幅值和方向。</li><li>梯度幅值表示图像在每个像素点的变化率,梯度方向表示变化的方向。</li></ul></li><li><p><strong>非极大值抑制</strong>:</p><ul><li>为了确保边缘的连续性和清晰性,Canny算法通过非极大值抑制来去除冗余的边缘点。</li><li>对于每个像素点,如果其梯度幅值不是其邻域内的局部极大值,则将其梯度幅值置为零。这一步骤能够确保边缘的宽度为一个像素。</li></ul></li><li><p><strong>双阈值检测</strong>:</p><ul><li>Canny算法使用两个阈值(高阈值和低阈值)来确定边缘。</li><li>高阈值用于检测强边缘,低阈值用于检测弱边缘。如果一个像素点的梯度幅值高于高阈值,则被确定为强边缘;如果低于低阈值,则被确定为非边缘;如果介于两者之间,则需要通过边缘连接性来判断是否为边缘。</li></ul></li><li><p><strong>边缘连接</strong>:</p><ul><li>对于介于高阈值和低阈值之间的像素点,如果它们与强边缘像素点相连,则被确定为边缘;否则被抑制。</li><li>这一步骤能够有效连接断裂的边缘,同时抑制噪声带来的误检。</li></ul></li></ol><h3 id="(四)算法优势">(四)算法优势</h3><p>Canny算法的主要优势在于其高精度和鲁棒性。通过多步骤优化,Canny算法能够检测出清晰且连续的边缘,同时抑制噪声的影响。此外,Canny算法的边缘检测结果通常比其他方法更稳定,适用于各种复杂场景。</p><h2 id="二、Canny-函数">二、Canny()函数</h2><p>在Python中,OpenCV库提供了<code>cv2.Canny()</code>函数,用于实现Canny算法。以下是该函数的详细说明和使用示例。</p><h3 id="(一)函数原型">(一)函数原型</h3><p>以下是OpenCV中<code>cv2.Canny()</code>函数的基本原型:</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">cv2.Canny(image, threshold1, threshold2, edges=<span class="literal">None</span>, apertureSize=<span class="literal">None</span>, L2gradient=<span class="literal">None</span>)</span><br></pre></td></tr></table></figure><ul><li><strong>image</strong>:输入图像,必须是单通道的灰度图像。</li><li><strong>threshold1</strong>:低阈值,用于双阈值检测。</li><li><strong>threshold2</strong>:高阈值,用于双阈值检测。</li><li><strong>edges</strong>:可选参数,输出的边缘图像。</li><li><strong>apertureSize</strong>:可选参数,Sobel算子的孔径大小,通常为3、5或7。</li><li><strong>L2gradient</strong>:可选参数,布尔值,表示是否使用更精确的L2范数计算梯度幅值。默认为<code>False</code>。</li></ul><h3 id="(二)使用示例">(二)使用示例</h3><p>以下是一个使用OpenCV的<code>cv2.Canny()</code>函数进行边缘检测的简单示例:</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Canny()</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> cv2 <span class="keyword">as</span> cv</span><br><span class="line"></span><br><span class="line">src = cv.imread(<span class="string">"src.jpg"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 对图像进行高斯模糊处理,以减少噪声对边缘检测的影响</span></span><br><span class="line"><span class="comment"># cv.GaussianBlur() 函数用于对图像进行高斯模糊</span></span><br><span class="line"><span class="comment"># 参数 (3, 3) 表示高斯核的大小,0 表示根据核的大小自动计算标准差</span></span><br><span class="line">src = cv.GaussianBlur(src, (<span class="number">3</span>, <span class="number">3</span>), <span class="number">0</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 将图像从 BGR 格式转换为灰度格式</span></span><br><span class="line"><span class="comment"># cv.cvtColor() 函数用于图像颜色空间的转换</span></span><br><span class="line"><span class="comment"># cv.COLOR_BGR2GRAY 表示从 BGR 转换为灰度</span></span><br><span class="line">gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用 Canny 算法进行边缘检测</span></span><br><span class="line"><span class="comment"># cv.Canny() 函数用于实现 Canny 边缘检测算法</span></span><br><span class="line"><span class="comment"># 参数 gray 表示输入的灰度图像</span></span><br><span class="line"><span class="comment"># 参数 70 和 160 分别表示低阈值和高阈值,用于双阈值检测</span></span><br><span class="line">Canny_grad = cv.Canny(gray, <span class="number">70</span>, <span class="number">160</span>)</span><br><span class="line"></span><br><span class="line">cv.imshow(<span class="string">"Canny_grad.jpg"</span>, Canny_grad)</span><br><span class="line">cv.imwrite(<span class="string">"Canny_grad.jpg"</span>, Canny_grad)</span><br><span class="line">cv.waitKey(<span class="number">0</span>)</span><br><span class="line">cv.destroyAllWindows()</span><br></pre></td></tr></table></figure><div style="text-align: center; font-family: 'Arial', sans-serif;"> <div style="margin-bottom: 20px;"> <img src="/images/learn_pic/2025-07-10_Canny_grad.jpg" alt="Canny 边缘检测" style="width: 90%; height: auto; border-radius: 10px;"> <div style="font-size: 14px; color: #333; margin-top: 5px;"> Canny 边缘检测 </div> </div></div><h3 id="(三)函数优势">(三)函数优势</h3><p>使用<code>cv2.Canny()</code>函数的优势在于其简单易用且功能强大。它能够快速实现高精度的边缘检测,同时通过调整参数(如阈值、孔径大小等),可以灵活地适应不同的图像处理需求。</p><h2 id="三、Canny算法的应用场景">三、Canny算法的应用场景</h2><p>Canny算法在计算机视觉和图像处理领域有广泛的应用,以下是一些常见的应用场景:</p><h3 id="(一)图像分割">(一)图像分割</h3><p>在图像分割任务中,边缘检测是关键步骤之一。Canny算法能够检测出清晰且连续的边缘,从而帮助定位图像中的物体边界,为后续的分割操作提供重要依据。</p><h3 id="(二)特征提取">(二)特征提取</h3><p>边缘信息是图像的重要特征之一。通过Canny算法提取的边缘特征可以用于物体识别、目标跟踪等任务。例如,在人脸识别系统中,Canny算法可以用于提取面部轮廓,进而实现更准确的识别。</p><h3 id="(三)图像增强">(三)图像增强</h3><p>在某些情况下,我们可以通过增强图像的边缘信息来提高图像的视觉效果。Canny算法可以用于检测图像的边缘,并将其与原始图像进行融合,从而增强图像的对比度和清晰度。</p><h2 id="四、Canny算法与其他算法的对比">四、Canny算法与其他算法的对比</h2><p>虽然Canny算法、Sobel算法、Scharr算法和Laplacian算法都是常用的边缘检测方法,但它们在原理和应用上有显著差异。以下是Canny算法与其他算法的对比:</p><h3 id="(一)原理">(一)原理</h3><ul><li><strong>Sobel/Scharr/Laplacian算法</strong>:基于一阶或二阶导数,通过计算图像的梯度或拉普拉斯值来检测边缘。</li><li><strong>Canny算法</strong>:通过多步骤优化(噪声抑制、梯度计算、非极大值抑制、双阈值检测和边缘连接)实现边缘检测。</li></ul><h3 id="(二)边缘检测精度">(二)边缘检测精度</h3><ul><li><strong>Sobel/Scharr/Laplacian算法</strong>:精度较高,但容易受到噪声的影响,可能导致边缘断裂或误检。</li><li><strong>Canny算法</strong>:通过多步骤优化,能够检测出清晰且连续的边缘,同时抑制噪声的影响。</li></ul><h3 id="(三)计算复杂度">(三)计算复杂度</h3><ul><li><strong>Sobel/Scharr/Laplacian算法</strong>:计算复杂度较低,适合实时处理。</li><li><strong>Canny算法</strong>:计算复杂度较高,但仍然适合大多数应用场景。</li></ul><h3 id="(四)抗噪性能">(四)抗噪性能</h3><ul><li><strong>Sobel/Scharr/Laplacian算法</strong>:对噪声较为敏感,容易误检边缘。</li><li><strong>Canny算法</strong>:通过高斯滤波器进行噪声抑制,抗噪性能更强。</li></ul>]]></content>
<categories>
<category> Learn </category>
</categories>
</entry>
<entry>
<title>图像边缘检测01:几种常用的算法与对应函数</title>
<link href="/%E5%AD%A6%E7%82%B9%E4%B9%A0-2025-07-10-detection01.html"/>
<url>/%E5%AD%A6%E7%82%B9%E4%B9%A0-2025-07-10-detection01.html</url>
<content type="html"><![CDATA[<p>在一切开始之前,请允许我先<strong>隆重</strong>介绍一下在这篇文章里面用来做实验的图片,它也是我博客主页的封面:</p><div style="text-align: center; font-family: 'Arial', sans-serif;"> <div style="margin-bottom: 20px;"> <img src="images/top_pic/top_default.jpg" alt="新加坡-天际线" style="width: 90%; height: auto; border-radius: 10px;"> <div style="font-size: 14px; color: #333; margin-top: 5px;"> 新加坡-天际线 </div> </div></div><blockquote><p>“暮色漫染新加坡的天际线,金融区的钢铁丛林在余晖里苏醒。玻璃幕墙裁碎夕阳,将金芒洒向睡莲摇曳的镜面。建筑如未来棱晶,莲花池接住整座城的倒影 —— 这是钢铁与柔波的和鸣,都市脉搏在水面轻颤,每片莲叶都托着金融中心的璀璨,每缕暮光都织就花园城市的浪漫,滨海湾把 “硬核” 与 “诗意”,熬成一帧流动的、属于狮城的梦。”</p></blockquote><h2 id="Sobel算法:基于一阶导数的边缘检测">Sobel算法:基于一阶导数的边缘检测</h2><h3 id="(一)算法背景">(一)算法背景</h3><p>图像中的边缘通常对应着像素亮度的显著变化。Sobel算法是一种经典且高效的边缘检测方法,通过计算图像在水平和垂直方向上的梯度(一阶导数)来定位边缘。</p><h3 id="(二)算法原理">(二)算法原理</h3><p>Sobel算法的核心是使用两个特定的3x3卷积核,分别计算图像在水平(x)和垂直(y)方向上的近似梯度:</p><ul><li><strong>水平方向卷积核 (检测垂直边缘)</strong>:<br>[<br>G_x =<br>\begin{bmatrix}<br>-1 & 0 & 1 \<br>-2 & 0 & 2 \<br>-1 & 0 & 1 \<br>\end{bmatrix}<br>]</li><li><strong>垂直方向卷积核 (检测水平边缘)</strong>:<br>[<br>G_y =<br>\begin{bmatrix}<br>-1 & -2 & -1 \<br>0 & 0 & 0 \<br>1 & 2 & 1 \<br>\end{bmatrix}<br>]</li></ul><p>对图像中的每个像素,将其邻域像素值分别与这两个核进行卷积运算,得到梯度分量 (G_x) 和 (G_y)。</p><h3 id="(三)边缘强度与方向">(三)边缘强度与方向</h3><ul><li><strong>边缘强度 (梯度幅值)</strong>:综合两个方向的梯度计算每个像素点的边缘强度:<br>[<br>G = \sqrt{G_x^2 + G_y^2}<br>]<br>为简化计算,实践中常用近似公式:<br>[<br>G \approx |G_x| + |G_y|<br>]<br>值 (G) 越大,该点属于边缘的可能性越高。</li><li><strong>边缘方向</strong>:梯度方向指示了边缘的法线方向(垂直于边缘走向):<br>[<br>\theta = \arctan\left(\frac{G_y}{G_x}\right)<br>]<br>(\theta) 的范围通常在 (-90^\circ) 到 (90^\circ) 之间。</li></ul><h2 id="Sobel函数:OpenCV实现">Sobel函数:OpenCV实现</h2><p>OpenCV提供了 <code>cv2.Sobel()</code> 函数方便地实现Sobel边缘检测。</p><h3 id="(一)函数原型">(一)函数原型</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">cv2.Sobel(src, ddepth, dx, dy, dst=<span class="literal">None</span>, ksize=<span class="literal">None</span>, scale=<span class="literal">None</span>, delta=<span class="literal">None</span>, borderType=<span class="literal">None</span>)</span><br></pre></td></tr></table></figure><ul><li><strong><code>src</code></strong>: 输入图像(通常为灰度图)。</li><li><strong><code>ddepth</code></strong>: 输出图像的深度(常用 <code>cv2.CV_64F</code> 或 <code>cv2.CV_8U</code>)。</li><li><strong><code>dx</code></strong>: x方向(水平)导数的阶数(通常设为1)。</li><li><strong><code>dy</code></strong>: y方向(垂直)导数的阶数(通常设为1)。</li><li><strong><code>dst</code></strong>: 输出图像(可选)。</li><li><strong><code>ksize</code></strong>: Sobel核大小(常用3)。</li><li><strong><code>scale</code></strong>: 缩放导数值(可选)。</li><li><strong><code>delta</code></strong>: 导数值偏移量(可选)。</li><li><strong><code>borderType</code></strong>: 边界填充方式(默认为 <code>cv2.BORDER_DEFAULT</code>)。</li></ul><h3 id="(二)使用示例">(二)使用示例</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Scharr()</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> cv2 <span class="keyword">as</span> cv</span><br><span class="line"></span><br><span class="line">src = cv.imread(<span class="string">"src.jpg"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 对图像进行高斯模糊处理,以减少噪声对边缘检测的影响</span></span><br><span class="line">src = cv.GaussianBlur(src, (<span class="number">3</span>, <span class="number">3</span>), <span class="number">0</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 将图像从 BGR 格式转换为灰度格式</span></span><br><span class="line">gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用 Scharr 算法计算图像在水平方向的梯度</span></span><br><span class="line"><span class="comment"># cv.Scharr() 函数用于计算图像的梯度,比 Sobel 算法精度更高</span></span><br><span class="line"><span class="comment"># 参数 gray 表示输入图像,-1 表示输出图像深度与输入图像相同</span></span><br><span class="line"><span class="comment"># 参数 1 和 0 分别表示水平方向的导数阶数和垂直方向的导数阶数</span></span><br><span class="line">gradx = cv.Scharr(gray, -<span class="number">1</span>, <span class="number">1</span>, <span class="number">0</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用 Scharr 算法计算图像在垂直方向的梯度</span></span><br><span class="line"><span class="comment"># 参数 0 和 1 分别表示水平方向的导数阶数和垂直方向的导数阶数</span></span><br><span class="line">grady = cv.Scharr(gray, -<span class="number">1</span>, <span class="number">0</span>, <span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 将水平方向和垂直方向的梯度图像进行加权融合</span></span><br><span class="line"><span class="comment"># cv.addWeighted() 函数用于将两个图像进行加权融合</span></span><br><span class="line"><span class="comment"># 参数 0.5 和 0.5 分别表示两个图像的权重,最后一个参数 0 表示偏移量</span></span><br><span class="line">grad = cv.addWeighted(gradx, <span class="number">0.5</span>, grady, <span class="number">0.5</span>, <span class="number">0</span>)</span><br><span class="line"></span><br><span class="line">cv.imshow(<span class="string">"Scharr_gradx.jpg"</span>, gradx)</span><br><span class="line">cv.imshow(<span class="string">"Scharr_grady.jpg"</span>, grady)</span><br><span class="line">cv.imshow(<span class="string">"Scharr_grad.jpg"</span>, grad)</span><br><span class="line">cv.imwrite(<span class="string">"Scharr_gradx.jpg"</span>, gradx)</span><br><span class="line">cv.imwrite(<span class="string">"Scharr_grady.jpg"</span>, grady)</span><br><span class="line">cv.imwrite(<span class="string">"Scharr_grad.jpg"</span>, grad)</span><br><span class="line">cv.waitKey(<span class="number">0</span>)</span><br><span class="line">cv.destroyAllWindows()</span><br><span class="line"></span><br></pre></td></tr></table></figure><div style="text-align: center; font-family: 'Arial', sans-serif;"> <!-- 第一张和第二张图片及其描述 --> <div style="display: flex; justify-content: center; margin-bottom: 20px;"> <div style="margin: 0 10px;"> <img src="/images/learn_pic/2025-07-10_Sobel_gradx.jpg" alt="水平方向梯度图像" style="width: 100%; height: auto;"> <div style="font-size: 14px; color: #333; margin-top: 5px; text-align: center;">水平方向梯度图像</div> </div> <div style="margin: 0 10px;"> <img src="/images/learn_pic/2025-07-10_Sobel_grady.jpg" alt="垂直方向梯度图像" style="width: 100%; height: auto;"> <div style="font-size: 14px; color: #333; margin-top: 5px; text-align: center;">垂直方向梯度图像</div> </div> </div> <!-- 第三张图片及其描述 --> <div style="margin: 10px;"> <img src="/images/learn_pic/2025-07-10_Sobel_grad.jpg" alt="融合后梯度图像" style="width: 90%; height: auto; border-radius: 10px;"> <div style="font-size: 14px; color: #333; margin-top: 5px; text-align: center;">融合后梯度图像,展示了通过Scharr算子计算得到的图像梯度。</div> </div></div><h3 id="(三)特点">(三)特点</h3><ul><li><strong>优点</strong>:实现简单、计算效率高,能有效检测水平和垂直边缘。</li><li><strong>缺点</strong>:梯度计算是近似值,精度相对较低;对噪声有一定敏感性;只能检测特定方向的边缘。</li></ul><hr><h2 id="Scharr算法:Sobel的优化版本">Scharr算法:Sobel的优化版本</h2><h3 id="(一)算法背景-2">(一)算法背景</h3><p>Sobel算法在检测非水平和垂直方向边缘时精度有所下降。Scharr算法通过优化卷积核设计,提供了更高的旋转对称性和梯度计算精度。</p><h3 id="(二)算法原理-2">(二)算法原理</h3><p>Scharr算法结构与Sobel相同,但使用了不同的卷积核系数,能更精确地近似图像梯度:</p><ul><li><strong>水平方向卷积核</strong>:<br>[<br>G_x =<br>\begin{bmatrix}<br>-3 & 0 & 3 \<br>-10 & 0 & 10 \<br>-3 & 0 & 3 \<br>\end{bmatrix}<br>]</li><li><strong>垂直方向卷积核</strong>:<br>[<br>G_y =<br>\begin{bmatrix}<br>-3 & -10 & -3 \<br>0 & 0 & 0 \<br>3 & 10 & 3 \<br>\end{bmatrix}<br>]</li></ul><p>边缘强度 ((G = \sqrt{G_x^2 + G_y^2}) 或 (G \approx |G_x| + |G_y|)) 和方向 ((\theta = \arctan(G_y / G_x))) 的计算方式与Sobel一致。</p><h3 id="(三)算法优势">(三)算法优势</h3><p>相比Sobel算子,Scharr算子在相同大小的核(3x3)下:</p><ul><li><strong>精度更高</strong>:对边缘方向的响应更准确。</li><li><strong>旋转对称性更好</strong>:检测不同角度边缘的性能更均衡。</li><li><strong>抗噪性略优</strong>:系数设计有助于在保持精度的同时略微抑制噪声影响。</li></ul><h2 id="Scharr函数:OpenCV实现">Scharr函数:OpenCV实现</h2><p>OpenCV提供了专门的 <code>cv2.Scharr()</code> 函数。</p><h3 id="(一)函数原型-2">(一)函数原型</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">cv2.Scharr(src, ddepth, dx, dy, dst=<span class="literal">None</span>, scale=<span class="literal">None</span>, delta=<span class="literal">None</span>, borderType=<span class="literal">None</span>)</span><br></pre></td></tr></table></figure><p>参数含义与 <code>cv2.Sobel()</code> 类似(<code>dx</code> 和 <code>dy</code> 通常也为1):</p><ul><li><strong><code>src</code></strong>, <strong><code>ddepth</code></strong>, <strong><code>dx</code></strong>, <strong><code>dy</code></strong>, <strong><code>dst</code></strong>, <strong><code>scale</code></strong>, <strong><code>delta</code></strong>, <strong><code>borderType</code></strong>.</li><li><em>注意</em>:Scharr函数<strong>没有</strong> <code>ksize</code> 参数,因为它固定使用优化的3x3核。</li></ul><h3 id="(二)使用示例-2">(二)使用示例</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Laplacian()</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> cv2 <span class="keyword">as</span> cv</span><br><span class="line"></span><br><span class="line">src = cv.imread(<span class="string">"src.jpg"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 将图像从 BGR 格式转换为灰度格式</span></span><br><span class="line"><span class="comment"># cv.cvtColor() 函数用于图像颜色空间的转换</span></span><br><span class="line"><span class="comment"># cv.COLOR_BGR2GRAY 表示从 BGR 转换为灰度</span></span><br><span class="line">gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用 Laplacian 算法计算图像的拉普拉斯梯度</span></span><br><span class="line"><span class="comment"># cv.Laplacian() 函数用于计算图像的拉普拉斯值,即二阶导数</span></span><br><span class="line"><span class="comment"># 参数 gray 表示输入图像</span></span><br><span class="line"><span class="comment"># 参数 -1 表示输出图像的深度与输入图像相同</span></span><br><span class="line"><span class="comment"># 默认情况下,Laplacian 使用 3x3 的卷积核进行计算</span></span><br><span class="line">Laplacian_grad = cv.Laplacian(gray, -<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line">cv.imshow(<span class="string">"Laplacian_grad.jpg"</span>, Laplacian_grad)</span><br><span class="line">cv.imwrite(<span class="string">"Laplacian_grad.jpg"</span>, Laplacian_grad)</span><br><span class="line">cv.waitKey(<span class="number">0</span>)</span><br><span class="line">cv.destroyAllWindows()</span><br><span class="line"></span><br></pre></td></tr></table></figure><div style="text-align: center; font-family: 'Arial', sans-serif;"> <!-- 第一张和第二张图片及其描述 --> <div style="display: flex; justify-content: center; margin-bottom: 20px;"> <div style="margin: 0 10px;"> <img src="/images/learn_pic/2025-07-10_Sobel_gradx.jpg" alt="水平方向梯度图像" style="width: 100%; height: auto;"> <div style="font-size: 14px; color: #333; margin-top: 5px; text-align: center;">水平方向梯度图像</div> </div> <div style="margin: 0 10px;"> <img src="/images/learn_pic/2025-07-10_Sobel_grady.jpg" alt="垂直方向梯度图像" style="width: 100%; height: auto;"> <div style="font-size: 14px; color: #333; margin-top: 5px; text-align: center;">垂直方向梯度图像</div> </div> </div> <!-- 第三张图片及其描述 --> <div style="margin: 10px;"> <img src="/images/learn_pic/2025-07-10_Scharr_grad.jpg" alt="融合后梯度图像" style="width: 90%; height: auto; border-radius: 10px;"> <div style="font-size: 14px; color: #333; margin-top: 5px; text-align: center;">融合后梯度图像,展示了通过Scharr算子计算得到的图像梯度。</div> </div></div><h3 id="(三)特点-2">(三)特点</h3><ul><li><strong>优点</strong>:在3x3核下精度显著优于Sobel;计算效率与Sobel相当;API简单易用。</li><li><strong>缺点</strong>:同样对噪声敏感;核大小固定为3x3,灵活性不如可调ksize的Sobel;本质上仍是基于一阶导数的近似。</li></ul><hr><h2 id="Laplacian算法:基于二阶导数的边缘检测">Laplacian算法:基于二阶导数的边缘检测</h2><h3 id="(一)算法背景-3">(一)算法背景</h3><p>Sobel和Scharr基于一阶导数(梯度变化率),而Laplacian算法基于二阶导数(梯度变化率的变化率)。它在图像灰度值发生<em>突变</em>(如边缘)的位置会产生零交叉或极值响应。</p><h3 id="(二)算法原理-3">(二)算法原理</h3><p>Laplacian算法直接计算图像的拉普拉斯算子(Laplacian Operator),即二阶导数的和:<br>[<br>\Delta f = \frac{\partial^2 f}{\partial x^2} + \frac{\partial^2 f}{\partial y^2}<br>]<br>离散图像中通过卷积核实现。常用核有:</p><ol><li><strong>4邻域 (更常用)</strong>:<br>[<br>\begin{bmatrix}<br>0 & 1 & 0 \<br>1 & -4 & 1 \<br>0 & 1 & 0 \<br>\end{bmatrix}<br>]</li><li><strong>8邻域 (对对角线边缘响应更强)</strong>:<br>[<br>\begin{bmatrix}<br>1 & 1 & 1 \<br>1 & -8 & 1 \<br>1 & 1 & 1 \<br>\end{bmatrix}<br>]</li></ol><h3 id="(三)边缘检测">(三)边缘检测</h3><ol><li><strong>计算拉普拉斯响应</strong>:用上述核之一对图像进行卷积,得到每个像素的拉普拉斯值 (L)。</li><li><strong>边缘定位</strong>:<ul><li>理论边缘点位于 (L=0) 的位置(零交叉点)。</li><li>实践中常通过寻找 (L) 的绝对值较大(超过阈值)的点来检测边缘,或者结合零交叉检测方法。</li></ul></li></ol><h3 id="(四)算法特点">(四)算法特点</h3><ul><li><strong>优点</strong>:对图像中的灰度突变(如细线、孤立点、边缘)非常敏感;能检测任意方向的边缘(各向同性);无需分别计算x/y方向。</li><li><strong>缺点</strong>:对噪声极其敏感(二阶导数放大噪声);容易产生双像素宽边缘;检测到的边缘位置可能不如一阶方法精确。</li></ul><h2 id="Laplacian函数:OpenCV实现">Laplacian函数:OpenCV实现</h2><p>OpenCV提供了 <code>cv2.Laplacian()</code> 函数。</p><h3 id="(一)函数原型-3">(一)函数原型</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">cv2.Laplacian(src, ddepth, dst=<span class="literal">None</span>, ksize=<span class="literal">None</span>, scale=<span class="literal">None</span>, delta=<span class="literal">None</span>, borderType=<span class="literal">None</span>)</span><br></pre></td></tr></table></figure><ul><li><strong><code>src</code></strong>: 输入图像(通常为灰度图)。</li><li><strong><code>ddepth</code></strong>: 输出图像的深度(常用 <code>cv2.CV_64F</code>)。</li><li><strong><code>dst</code></strong>: 输出图像(可选)。</li><li><strong><code>ksize</code></strong>: 计算二阶导数使用的孔径大小(核大小)。<strong>关键参数</strong>:<code>ksize=1</code> 表示使用上述4邻域核。常用 <code>ksize=3</code> (默认) 或 <code>ksize=5</code> (更平滑)。<code>ksize=1</code> 时实际使用 <code>ksize=3</code> 的4邻域核。</li><li><strong><code>scale</code></strong>: 缩放计算得到的拉普拉斯值(可选)。</li><li><strong><code>delta</code></strong>: 偏移量(可选)。</li><li><strong><code>borderType</code></strong>: 边界填充方式(默认为 <code>cv2.BORDER_DEFAULT</code>)。</li></ul><h3 id="(二)使用示例-3">(二)使用示例</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Laplacian()</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> cv2 <span class="keyword">as</span> cv</span><br><span class="line"></span><br><span class="line">src = cv.imread(<span class="string">"src.jpg"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 将图像从 BGR 格式转换为灰度格式</span></span><br><span class="line"><span class="comment"># cv.cvtColor() 函数用于图像颜色空间的转换</span></span><br><span class="line"><span class="comment"># cv.COLOR_BGR2GRAY 表示从 BGR 转换为灰度</span></span><br><span class="line">gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用 Laplacian 算法计算图像的拉普拉斯梯度</span></span><br><span class="line"><span class="comment"># cv.Laplacian() 函数用于计算图像的拉普拉斯值,即二阶导数</span></span><br><span class="line"><span class="comment"># 参数 gray 表示输入图像</span></span><br><span class="line"><span class="comment"># 参数 -1 表示输出图像的深度与输入图像相同</span></span><br><span class="line"><span class="comment"># 默认情况下,Laplacian 使用 3x3 的卷积核进行计算</span></span><br><span class="line">Laplacian_grad = cv.Laplacian(gray, -<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line">cv.imshow(<span class="string">"Laplacian_grad.jpg"</span>, Laplacian_grad)</span><br><span class="line">cv.imwrite(<span class="string">"Laplacian_grad.jpg"</span>, Laplacian_grad)</span><br><span class="line">cv.waitKey(<span class="number">0</span>)</span><br><span class="line">cv.destroyAllWindows()</span><br><span class="line"></span><br></pre></td></tr></table></figure><div style="text-align: center; font-family: 'Arial', sans-serif;"> <div style="margin-bottom: 20px;"> <img src="/images/learn_pic/2025-07-10_Laplacian_grad.jpg" alt="拉普拉斯梯度图像" style="width: 90%; height: auto; border-radius: 10px;"> <div style="font-size: 14px; color: #333; margin-top: 5px;"> 拉普拉斯梯度图像 </div> </div></div><h3 id="(三)特点-3">(三)特点</h3><ul><li><strong>优点</strong>:API简洁;能检测各方向边缘;对突变敏感。</li><li><strong>缺点</strong>:结果对噪声非常敏感;需要仔细选择<code>ksize</code>和后续阈值处理;输出通常需要取绝对值并转换为8位图像显示。</li></ul><hr><h2 id="Sobel-Scharr-Laplacian-算法对比">Sobel, Scharr, Laplacian 算法对比</h2><p>下表总结了三种边缘检测算法的主要特点与差异:</p><table><thead><tr><th style="text-align:left">特性</th><th style="text-align:left">Sobel 算法</th><th style="text-align:left">Scharr 算法</th><th style="text-align:left">Laplacian 算法</th></tr></thead><tbody><tr><td style="text-align:left"><strong>计算基础</strong></td><td style="text-align:left">一阶导数 (近似梯度)</td><td style="text-align:left">一阶导数 (优化近似梯度)</td><td style="text-align:left"><strong>二阶导数</strong> (拉普拉斯算子)</td></tr><tr><td style="text-align:left"><strong>主要目的</strong></td><td style="text-align:left">检测边缘 (梯度幅值大的地方)</td><td style="text-align:left">检测边缘 (梯度幅值大的地方)</td><td style="text-align:left">检测灰度突变 (零交叉/极值点)</td></tr><tr><td style="text-align:left"><strong>方向性</strong></td><td style="text-align:left">能计算边缘方向 (θ)</td><td style="text-align:left">能计算边缘方向 (θ)</td><td style="text-align:left"><strong>各向同性</strong> (无特定方向)</td></tr><tr><td style="text-align:left"><strong>精度 (3x3核)</strong></td><td style="text-align:left">中等</td><td style="text-align:left"><strong>高</strong> (优于Sobel)</td><td style="text-align:left">高 (对突变敏感)</td></tr><tr><td style="text-align:left"><strong>抗噪性</strong></td><td style="text-align:left">低</td><td style="text-align:left">低 (略优于Sobel)</td><td style="text-align:left"><strong>非常低</strong> (噪声放大严重)</td></tr><tr><td style="text-align:left"><strong>边缘响应</strong></td><td style="text-align:left">单边缘响应 (单峰)</td><td style="text-align:left">单边缘响应 (单峰)</td><td style="text-align:left"><strong>双边缘响应</strong> (零交叉)</td></tr><tr><td style="text-align:left"><strong>检测边缘类型</strong></td><td style="text-align:left">主要水平和垂直方向</td><td style="text-align:left">各方向 (精度更均衡)</td><td style="text-align:left">所有方向</td></tr><tr><td style="text-align:left"><strong>对细线/点响应</strong></td><td style="text-align:left">弱</td><td style="text-align:left">弱</td><td style="text-align:left"><strong>强</strong></td></tr><tr><td style="text-align:left"><strong>计算复杂度</strong></td><td style="text-align:left">低</td><td style="text-align:left">低 (与Sobel相当)</td><td style="text-align:left">低</td></tr><tr><td style="text-align:left"><strong>OpenCV函数</strong></td><td style="text-align:left"><code>cv2.Sobel()</code></td><td style="text-align:left"><code>cv2.Scharr()</code></td><td style="text-align:left"><code>cv2.Laplacian()</code></td></tr><tr><td style="text-align:left"><strong>主要参数</strong></td><td style="text-align:left"><code>dx</code>, <code>dy</code>, <code>ksize</code></td><td style="text-align:left"><code>dx</code>, <code>dy</code> (核固定3x3)</td><td style="text-align:left"><code>ksize</code> (控制平滑与核类型)</td></tr><tr><td style="text-align:left"><strong>典型应用场景</strong></td><td style="text-align:left">快速初步边缘检测</td><td style="text-align:left">需要更高精度的3x3核边缘检测</td><td style="text-align:left">检测细线、孤立点、斑点、边缘增强</td></tr></tbody></table><p><strong>选择建议</strong>:</p><ul><li>需要快速检测基本边缘且方向明确? -> <strong><code>Sobel</code></strong> (可调<code>ksize</code>增加平滑)。</li><li>需要更精确的3x3核边缘检测? -> <strong><code>Scharr</code></strong>。</li><li>需要检测所有方向的突变、细线或孤立点? -> <strong><code>Laplacian</code></strong> (但务必结合平滑滤波如高斯模糊,即LoG)。</li><li>实际应用中,常将Sobel/Scharr的结果(梯度幅值)与Laplacian的零交叉信息结合,或使用更高级算法(如Canny)。</li></ul><p>至于更高级的 <strong>Canny 算法</strong> ,我们将在下一篇单独成文介绍。</p>]]></content>
<categories>
<category> Learn </category>
</categories>
</entry>
<entry>
<title>图像处理基本操作-02</title>
<link href="/%E5%AD%A6%E7%82%B9%E4%B9%A0-2025-07-08-practice01.html"/>
<url>/%E5%AD%A6%E7%82%B9%E4%B9%A0-2025-07-08-practice01.html</url>
<content type="html"><![CDATA[<h1>基础图像处理 - 实战01</h1><h2 id="使用方框滤波与添加噪声">使用方框滤波与添加噪声</h2><h2 id="1-方框滤波(Box-Filter)">1. 方框滤波(Box Filter)</h2><p> 方框滤波是一种简单而有效的线性滤波技术,广泛应用于图像去噪和平滑处理。它通过计算图像中每个像素邻域内像素值的平均值,将中心像素替换为该平均值,从而实现图像的平滑效果。这种方法的核心在于利用局部邻域内的像素信息来减少噪声的影响,同时保持图像的整体结构。</p><h3 id="1-1-工作原理">1.1 工作原理</h3><p> 方框滤波使用一个矩形窗口(通常是一个正方形)滑动遍历图像的每个像素。对于每个像素,滤波器计算窗口内所有像素值的平均值,并将该平均值赋给中心像素。这种方法简单高效,但可能会导致图像边缘模糊,因为边缘信息在平滑过程中可能会被削弱。</p><h3 id="1-2-应用场景">1.2 应用场景</h3><ul><li><strong>图像去噪</strong>:方框滤波可以有效去除随机噪声,如椒盐噪声或高斯噪声。</li><li><strong>图像平滑</strong>:通过平滑图像,减少细节,便于后续处理。</li><li><strong>预处理</strong>:在计算机视觉任务中,方框滤波常用于图像的预处理阶段,以增强图像质量。</li></ul><h2 id="2-校验噪声(Noise-Validation)">2. 校验噪声(Noise Validation)</h2><p> 校验噪声是指对图像中的噪声进行分析和验证的过程。噪声验证的目的是检测图像中是否存在噪声,以及噪声的类型和强度。通过校验噪声,可以更好地选择合适的去噪算法,从而提高图像处理的效果。</p><h3 id="2-1-噪声类型">2.1 噪声类型</h3><p> 常见的噪声类型包括:</p><ul><li><strong>椒盐噪声</strong>:由随机分布的白色和黑色像素组成,通常由图像采集设备的故障引起。</li><li><strong>高斯噪声</strong>:服从高斯分布的噪声,通常由传感器的热噪声引起。</li><li><strong>周期性噪声</strong>:具有规律性的噪声,通常由外部干扰源引起。</li></ul><h3 id="2-2-校验方法">2.2 校验方法</h3><ul><li><strong>统计分析</strong>:通过计算图像的均值、方差等统计量,判断是否存在噪声。</li><li><strong>频域分析</strong>:通过傅里叶变换将图像从空间域转换到频域,分析频谱特征。</li><li><strong>视觉检查</strong>:通过观察图像的细节,判断是否存在明显的噪声点。</li></ul><h3 id="2-3-应用场景">2.3 应用场景</h3><ul><li><strong>图像质量评估</strong>:在图像采集和传输过程中,校验噪声可以评估图像质量。</li><li><strong>去噪算法选择</strong>:根据噪声类型和强度,选择合适的去噪算法。</li><li><strong>图像增强</strong>:通过校验噪声,优化图像增强算法的参数。</li></ul><h2 id="3-示例代码">3. 示例代码</h2><h3 id="3-1-添加椒盐噪声">3.1 添加椒盐噪声</h3><p> 以下是一个使用 Python 和 OpenCV 实现方框滤波中添加椒盐噪声的示例代码:</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 添加椒盐噪声并对比图像</span></span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"><span class="keyword">import</span> random</span><br><span class="line"><span class="keyword">import</span> cv2 <span class="keyword">as</span> cv</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">add_sp_noise</span>(<span class="params">image, prob</span>):</span><br><span class="line"> <span class="string">"""</span></span><br><span class="line"><span class="string"> 添加椒盐噪声</span></span><br><span class="line"><span class="string"> :param image: 输入图像</span></span><br><span class="line"><span class="string"> :param prob: 噪声比例(0 到 1 之间)</span></span><br><span class="line"><span class="string"> :return: 添加噪声后的图像</span></span><br><span class="line"><span class="string"> """</span></span><br><span class="line"> <span class="comment"># 创建一个与输入图像形状相同、数据类型为 uint8 的全零数组,用于存储添加噪声后的图像</span></span><br><span class="line"> output = np.zeros(image.shape, np.uint8)</span><br><span class="line"> <span class="comment"># 计算阈值,用于判断像素是噪声点还是保留原值</span></span><br><span class="line"> thres = <span class="number">1</span> - prob</span><br><span class="line"> <span class="comment"># 遍历图像的每一行</span></span><br><span class="line"> <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(image.shape[<span class="number">0</span>]):</span><br><span class="line"> <span class="comment"># 遍历图像的每一列</span></span><br><span class="line"> <span class="keyword">for</span> j <span class="keyword">in</span> <span class="built_in">range</span>(image.shape[<span class="number">1</span>]):</span><br><span class="line"> <span class="comment"># 生成一个 0 到 1 之间的随机数</span></span><br><span class="line"> rdn = random.random()</span><br><span class="line"> <span class="comment"># 如果随机数小于噪声概率,则将该像素值设为 0(黑点,即“椒”噪声)</span></span><br><span class="line"> <span class="keyword">if</span> rdn < prob:</span><br><span class="line"> output[i][j] = <span class="number">0</span></span><br><span class="line"> <span class="comment"># 如果随机数大于阈值,则将该像素值设为 255(白点,即“盐”噪声)</span></span><br><span class="line"> <span class="keyword">elif</span> rdn > thres:</span><br><span class="line"> output[i][j] = <span class="number">255</span></span><br><span class="line"> <span class="comment"># 否则,保留原图像的像素值</span></span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> output[i][j] = image[i][j]</span><br><span class="line"> <span class="comment"># 返回添加噪声后的图像</span></span><br><span class="line"> <span class="keyword">return</span> output</span><br><span class="line"></span><br><span class="line"><span class="comment"># 读取图像</span></span><br><span class="line">pic01_original = cv.imread(<span class="string">"2025-07-08_02.jpg"</span>)</span><br><span class="line"><span class="comment"># 添加椒盐噪声</span></span><br><span class="line">pic01_noise = add_sp_noise(pic01_original, <span class="number">0.02</span>)</span><br><span class="line"><span class="comment"># 显示原始图像</span></span><br><span class="line">cv.imshow(<span class="string">"pic01_original"</span>, pic01_original)</span><br><span class="line"><span class="comment"># 显示添加噪声后的图像</span></span><br><span class="line">cv.imshow(<span class="string">"pic01_noise"</span>, pic01_noise)</span><br><span class="line"><span class="comment"># 保存噪声图像</span></span><br><span class="line">cv.imwrite(<span class="string">"2025-07-08_01.jpg"</span>, pic01_noise)</span><br><span class="line"><span class="comment"># 等待按键</span></span><br><span class="line">cv.waitKey(<span class="number">0</span>)</span><br><span class="line"><span class="comment"># 关闭所有窗口</span></span><br><span class="line">cv.destroyAllWindows()</span><br></pre></td></tr></table></figure><p> 运行结果如下图所示,可以看出,添加了椒盐噪声的图像就像撒上了椒盐一样(笑):</p><div style="text-align: center; font-family: 'Arial', sans-serif;"> <!-- 第一张和第二张图片及其描述 --> <div style="display: flex; justify-content: center; margin-bottom: 20px;"> <div style="margin: 0 10px; text-align: center;"> <img src="/images/learn_pic/2025-07-08_02.jpg" alt="原图像" style="width: 100%; height: auto; border-radius: 10px;"> <div style="font-size: 14px; color: #333; margin-top: 5px;">原图像</div> </div> <div style="margin: 0 10px; text-align: center;"> <img src="/images/learn_pic/2025-07-08_01.jpg" alt="椒盐噪声图像" style="width: 100%; height: auto; border-radius: 10px;"> <div style="font-size: 14px; color: #333; margin-top: 5px;">椒盐噪声图像</div> </div> </div></div><h3 id="3-2-添加高斯噪声">3.2 添加高斯噪声</h3><p> 以下是一个使用 Python 和 OpenCV 实现方框滤波中添加高斯噪声的示例代码:</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"><span class="keyword">import</span> cv2 <span class="keyword">as</span> cv</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">gasuss_noise</span>(<span class="params">image, mean, var</span>):</span><br><span class="line"> <span class="string">"""</span></span><br><span class="line"><span class="string"> 添加高斯噪声</span></span><br><span class="line"><span class="string"> :param image: 输入图像</span></span><br><span class="line"><span class="string"> :param mean: 噪声的均值</span></span><br><span class="line"><span class="string"> :param var: 噪声的方差</span></span><br><span class="line"><span class="string"> :return: 添加噪声后的图像</span></span><br><span class="line"><span class="string"> """</span></span><br><span class="line"> <span class="comment"># 将图像像素值归一化到 [0, 1] 范围内,便于后续处理</span></span><br><span class="line"> image = np.array(image / <span class="number">255</span>, dtype=<span class="built_in">float</span>)</span><br><span class="line"> <span class="comment"># 生成与图像形状相同的高斯噪声</span></span><br><span class="line"> noise = np.random.normal(mean, var ** <span class="number">0.5</span>, image.shape)</span><br><span class="line"> <span class="comment"># 将高斯噪声加到归一化后的图像上</span></span><br><span class="line"> out = image + noise</span><br><span class="line"> <span class="comment"># 检查加噪声后的图像是否有小于 0 的值</span></span><br><span class="line"> <span class="keyword">if</span> out.<span class="built_in">min</span>() < <span class="number">0</span>:</span><br><span class="line"> low_clip = -<span class="number">1.</span> <span class="comment"># 如果有小于 0 的值,设置下限为 -1</span></span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> low_clip = <span class="number">0.</span> <span class="comment"># 否则,下限为 0</span></span><br><span class="line"> <span class="comment"># 将加噪声后的图像值限制在 [low_clip, 1.0] 范围内</span></span><br><span class="line"> out = np.clip(out, low_clip, <span class="number">1.0</span>)</span><br><span class="line"> <span class="comment"># 将归一化的图像值重新缩放到 [0, 255] 范围,并转换为 uint8 数据类型</span></span><br><span class="line"> out = np.uint8(out * <span class="number">255</span>)</span><br><span class="line"> <span class="comment"># 返回添加高斯噪声后的图像</span></span><br><span class="line"> <span class="keyword">return</span> out</span><br><span class="line"></span><br><span class="line"><span class="comment"># 读取图像</span></span><br><span class="line">pic02_original = cv.imread(<span class="string">"test_pic02.png"</span>)</span><br><span class="line"><span class="comment"># 添加高斯噪声</span></span><br><span class="line">pic02_noise = gasuss_noise(pic02_original, <span class="number">0.2</span>, <span class="number">0.1</span>)</span><br><span class="line"><span class="comment"># 显示原始图像</span></span><br><span class="line">cv.imshow(<span class="string">"pic02_original"</span>, pic02_original)</span><br><span class="line"><span class="comment"># 显示添加噪声后的图像</span></span><br><span class="line">cv.imshow(<span class="string">"pic02_noise"</span>, pic02_noise)</span><br><span class="line"><span class="comment"># 保存噪声图像</span></span><br><span class="line">cv.imwrite(<span class="string">"pic02_noise.jpg"</span>, pic02_noise)</span><br><span class="line"><span class="comment"># 等待按键</span></span><br><span class="line">cv.waitKey(<span class="number">0</span>)</span><br><span class="line"><span class="comment"># 关闭所有窗口</span></span><br><span class="line">cv.destroyAllWindows()</span><br></pre></td></tr></table></figure><p> 运行结果如下图所示:</p><div style="text-align: center; font-family: 'Arial', sans-serif;"> <!-- 第一张和第二张图片及其描述 --> <div style="display: flex; justify-content: center; margin-bottom: 20px;"> <div style="margin: 0 10px; text-align: center;"> <img src="/images/learn_pic/2025-07-08_02.jpg" alt="原图像" style="width: 100%; height: auto; border-radius: 10px;"> <div style="font-size: 14px; color: #333; margin-top: 5px;">原图像</div> </div> <div style="margin: 0 10px; text-align: center;"> <img src="/images/learn_pic/2025-07-08_03.jpg" alt="高斯噪声图像" style="width: 100%; height: auto; border-radius: 10px;"> <div style="font-size: 14px; color: #333; margin-top: 5px;">高斯噪声图像</div> </div> </div></div><h2 id="4-方框滤波的-kernel-参数">4. 方框滤波的 <code>kernel</code> 参数</h2><p> 方框滤波的 <code>kernel</code> 参数决定了去除噪声的程度:</p><ul><li>若为 <code>1x1</code>,则输出的图像就是原图像。</li><li>使用的 <code>kernel</code> 越大,噪声越不明显,但相应的图片也越模糊。</li><li>方框滤波计算的是以计算点为中心,<code>kernel</code> 大小的区域取平均值所得到的像素。</li><li>当 <code>kernel</code> 的大小选择为 <code>15x15</code> 时,线条等细节已经被完全模糊</li></ul>]]></content>
<categories>
<category> Learn </category>
</categories>
</entry>
<entry>
<title>图像处理基本操作-01</title>
<link href="/%E5%AD%A6%E7%82%B9%E4%B9%A0-2025-07-07-img-processing.html"/>
<url>/%E5%AD%A6%E7%82%B9%E4%B9%A0-2025-07-07-img-processing.html</url>
<content type="html"><![CDATA[<h2 id="一、前言">一、前言</h2><p> 在图像处理领域,Python语言搭配OpenCV库是常用的工具组合。本文将介绍一些基本的图像处理操作,包括图像的读取、显示、保存、属性获取、通道操作以及简单的数学和逻辑运算。</p><ol><li>图像的读取、显示和保存</li><li>图像属性的获取</li><li>通道的分离与合并</li><li>使用NumPy生成随机图像</li><li>图像逻辑运算及其真值表</li></ol><h2 id="二、图像读取与显示">二、图像读取与显示</h2><h3 id="2-1-图像读取函数:cv-imread">2.1 图像读取函数:<code>cv.imread()</code></h3><p><strong>功能</strong>:从文件系统加载图像<br><strong>输入</strong>:文件路径字符串<br><strong>输出</strong>:NumPy数组表示的图像数据<br><strong>格式</strong>:<code>cv.imread(path)</code></p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2 <span class="keyword">as</span> cv <span class="comment"># 导入OpenCV库</span></span><br><span class="line">image = cv.imread(<span class="string">"F:\SomeFiles\MSL_Project\pic1.jpg"</span>) <span class="comment"># 读取图像,此处为绝对路径</span></span><br></pre></td></tr></table></figure><p><strong>注意</strong> 可以使用原始字符串或者双反斜杠<code>//</code>来避免转义问题</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2 <span class="keyword">as</span> cv</span><br><span class="line">image1 = cv.imread(<span class="string">r"F:\SomeFiles\MSL_Project\pic1.jpg"</span>)</span><br><span class="line">image2 = cv.imread(<span class="string">"F:\\SomeFiles\\MSL_Project\\pic1.jpg"</span>)</span><br></pre></td></tr></table></figure><h3 id="2-2-图像显示函数">2.2 图像显示函数</h3><table><thead><tr><th>函数</th><th>功能</th><th>输入</th><th>输出</th></tr></thead><tbody><tr><td><code>namedWindow()</code></td><td>创建显示窗口</td><td>窗口名称</td><td>无</td></tr><tr><td><code>imshow()</code></td><td>在窗口中显示图像</td><td>窗口名称, 图像数据</td><td>无</td></tr><tr><td><code>waitKey()</code></td><td>等待键盘输入</td><td>等待时间(ms)</td><td>按键值</td></tr><tr><td><code>destroyAllWindows()</code></td><td>关闭所有窗口</td><td>无</td><td>无</td></tr></tbody></table><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2 <span class="keyword">as</span> cv</span><br><span class="line">image = cv.imread(<span class="string">r"F:\SomeFiles\MSL_Project\pic1.jpg"</span>) <span class="comment"># 读取图像</span></span><br><span class="line">cv.namedWindow(<span class="string">"image"</span>) <span class="comment"># 创建名为"image"的窗口</span></span><br><span class="line">cv.imshow(<span class="string">"image"</span>, image) <span class="comment"># 在窗口中显示图像</span></span><br><span class="line">cv.waitKey(<span class="number">0</span>) <span class="comment"># 无限等待按键</span></span><br><span class="line">cv.destroyAllWindows() <span class="comment"># 关闭所有窗口</span></span><br></pre></td></tr></table></figure><h2 id="三、图像保存">三、图像保存</h2><h3 id="3-1-图像保存函数:cv-imwrite">3.1 图像保存函数:<code>cv.imwrite()</code></h3><p><strong>功能</strong>:将图像保存到文件系统<br><strong>输入</strong>:文件路径, 图像数据<br><strong>输出</strong>:布尔值(保存成功与否)<br><strong>格式</strong>:<code>cv.imwrite(path, image)</code></p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2 <span class="keyword">as</span> cv</span><br><span class="line">image = cv.imread(<span class="string">r"F:\SomeFiles\MSL_Project\pic1.jpg"</span>) <span class="comment"># 读取图像</span></span><br><span class="line">cv.imwrite(<span class="string">r"F:\SomeFiles\MSL_Project\pic2.jpg"</span>, image) <span class="comment"># 保存图像</span></span><br></pre></td></tr></table></figure><h2 id="四、图像属性获取">四、图像属性获取</h2><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2 <span class="keyword">as</span> cv</span><br><span class="line"><span class="comment"># 使用原始字符串避免转义问题</span></span><br><span class="line">image = cv.imread(<span class="string">r"F:\SomeFiles\MSL_Project\pic1.jpg"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取图像属性</span></span><br><span class="line"><span class="built_in">print</span>(<span class="string">"image.shape"</span>, image.shape) <span class="comment"># 输出图像维度(高度, 宽度, 通道数)</span></span><br><span class="line"><span class="built_in">print</span>(<span class="string">"image.size"</span>, image.size) <span class="comment"># 输出图像像素总数</span></span><br><span class="line"><span class="built_in">print</span>(<span class="string">"image.dtype"</span>, image.dtype) <span class="comment"># 输出图像数据类型</span></span><br></pre></td></tr></table></figure><h2 id="五、图像通道操作">五、图像通道操作</h2><h3 id="5-1-通道分离函数:cv-split">5.1 通道分离函数:<code>cv.split()</code></h3><p><strong>功能</strong>:将多通道图像分离为单通道图像<br><strong>输入</strong>:多通道图像<br><strong>输出</strong>:单通道图像元组<br><strong>格式</strong>:<code>b, g, r = cv.split(image)</code></p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2 <span class="keyword">as</span> cv</span><br><span class="line"><span class="comment"># 使用双反斜杠避免转义问题</span></span><br><span class="line">image = cv.imread(<span class="string">"F:\\SomeFiles\\MSL_Project\\pic1.jpg"</span>)</span><br><span class="line">b, g, r = cv.split(image) <span class="comment"># 分离BGR通道</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 显示各通道图像</span></span><br><span class="line">cv.imshow(<span class="string">"Blue Channel"</span>, b)</span><br><span class="line">cv.imshow(<span class="string">"Green Channel"</span>, g)</span><br><span class="line">cv.imshow(<span class="string">"Red Channel"</span>, r)</span><br><span class="line">cv.waitKey(<span class="number">0</span>)</span><br><span class="line">cv.destroyAllWindows()</span><br></pre></td></tr></table></figure><h3 id="5-2-通道合并函数:cv-merge">5.2 通道合并函数:<code>cv.merge()</code></h3><p><strong>功能</strong>:将单通道图像合并为多通道图像<br><strong>输入</strong>:通道列表<br><strong>输出</strong>:合并后的多通道图像<br><strong>格式</strong>:<code>image = cv.merge([b, g, r])</code></p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2 <span class="keyword">as</span> cv</span><br><span class="line">image = cv.imread(<span class="string">r"F:\SomeFiles\MSL_Project\pic1.jpg"</span>)</span><br><span class="line">b, g, r = cv.split(image) <span class="comment"># 分离通道</span></span><br><span class="line">image_bgr = cv.merge([b, g, r]) <span class="comment"># 合并通道</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 比较原始图像与合并后图像</span></span><br><span class="line">cv.imshow(<span class="string">"Original"</span>, image)</span><br><span class="line">cv.imshow(<span class="string">"Merged"</span>, image_bgr)</span><br><span class="line">cv.waitKey(<span class="number">0</span>)</span><br><span class="line">cv.destroyAllWindows()</span><br></pre></td></tr></table></figure><h2 id="六、NumPy图像生成">六、NumPy图像生成</h2><h3 id="6-1-生成随机灰度图">6.1 生成随机灰度图</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2 <span class="keyword">as</span> cv</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建256x256随机灰度图像</span></span><br><span class="line">image_gray = np.random.randint(<span class="number">0</span>, <span class="number">256</span>, size=[<span class="number">256</span>, <span class="number">256</span>], dtype=np.uint8)</span><br><span class="line"></span><br><span class="line">cv.imshow(<span class="string">'Random Gray'</span>, image_gray)</span><br><span class="line">cv.waitKey(<span class="number">0</span>)</span><br><span class="line">cv.destroyAllWindows()</span><br></pre></td></tr></table></figure><h3 id="6-2-生成随机彩色图">6.2 生成随机彩色图</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2 <span class="keyword">as</span> cv</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建256x256随机彩色图像</span></span><br><span class="line">image_color = np.random.randint(<span class="number">0</span>, <span class="number">256</span>, size=[<span class="number">256</span>, <span class="number">256</span>, <span class="number">3</span>], dtype=np.uint8)</span><br><span class="line"></span><br><span class="line">cv.imshow(<span class="string">'Random Color'</span>, image_color)</span><br><span class="line">cv.waitKey(<span class="number">0</span>)</span><br><span class="line">cv.destroyAllWindows()</span><br></pre></td></tr></table></figure><h2 id="七、图像逻辑运算">七、图像逻辑运算</h2><h3 id="7-1-按位运算函数">7.1 按位运算函数</h3><table><thead><tr><th>函数</th><th>功能</th><th>输入</th><th>输出</th></tr></thead><tbody><tr><td><code>cv.bitwise_and()</code></td><td>按位与运算</td><td>两个输入图像</td><td>与运算结果</td></tr><tr><td><code>cv.bitwise_or()</code></td><td>按位或运算</td><td>两个输入图像</td><td>或运算结果</td></tr><tr><td><code>cv.bitwise_not()</code></td><td>按位非运算</td><td>单个输入图像</td><td>非运算结果</td></tr><tr><td><code>cv.bitwise_xor()</code></td><td>按位异或运算</td><td>两个输入图像</td><td>异或运算结果</td></tr></tbody></table><h3 id="7-2-逻辑运算真值表">7.2 逻辑运算真值表</h3><h4 id="按位与-AND-真值表">按位与 (AND) 真值表</h4><table><thead><tr><th style="text-align:center">A</th><th style="text-align:center">B</th><th style="text-align:center">A AND B</th></tr></thead><tbody><tr><td style="text-align:center">0</td><td style="text-align:center">0</td><td style="text-align:center">0</td></tr><tr><td style="text-align:center">0</td><td style="text-align:center">1</td><td style="text-align:center">0</td></tr><tr><td style="text-align:center">1</td><td style="text-align:center">0</td><td style="text-align:center">0</td></tr><tr><td style="text-align:center">1</td><td style="text-align:center">1</td><td style="text-align:center">1</td></tr></tbody></table><h4 id="按位或-OR-真值表">按位或 (OR) 真值表</h4><table><thead><tr><th style="text-align:center">A</th><th style="text-align:center">B</th><th style="text-align:center">A OR B</th></tr></thead><tbody><tr><td style="text-align:center">0</td><td style="text-align:center">0</td><td style="text-align:center">0</td></tr><tr><td style="text-align:center">0</td><td style="text-align:center">1</td><td style="text-align:center">1</td></tr><tr><td style="text-align:center">1</td><td style="text-align:center">0</td><td style="text-align:center">1</td></tr><tr><td style="text-align:center">1</td><td style="text-align:center">1</td><td style="text-align:center">1</td></tr></tbody></table><h4 id="按位非-NOT-真值表">按位非 (NOT) 真值表</h4><table><thead><tr><th style="text-align:center">A</th><th style="text-align:center">NOT A</th></tr></thead><tbody><tr><td style="text-align:center">0</td><td style="text-align:center">1</td></tr><tr><td style="text-align:center">1</td><td style="text-align:center">0</td></tr></tbody></table><h4 id="按位异或-XOR-真值表">按位异或 (XOR) 真值表</h4><table><thead><tr><th style="text-align:center">A</th><th style="text-align:center">B</th><th style="text-align:center">A XOR B</th></tr></thead><tbody><tr><td style="text-align:center">0</td><td style="text-align:center">0</td><td style="text-align:center">0</td></tr><tr><td style="text-align:center">0</td><td style="text-align:center">1</td><td style="text-align:center">1</td></tr><tr><td style="text-align:center">1</td><td style="text-align:center">0</td><td style="text-align:center">1</td></tr><tr><td style="text-align:center">1</td><td style="text-align:center">1</td><td style="text-align:center">0</td></tr></tbody></table><h3 id="7-3-按位运算示例">7.3 按位运算示例</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2 <span class="keyword">as</span> cv</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line"><span class="comment"># 读取图像(使用原始字符串避免转义)</span></span><br><span class="line">image1 = cv.imread(<span class="string">r"F:\SomeFiles\MSL_Project\pic1.jpg"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建掩模图像</span></span><br><span class="line">image2 = np.zeros(image1.shape, dtype=np.uint8) <span class="comment"># 创建全黑图像</span></span><br><span class="line">image2[<span class="number">100</span>:<span class="number">400</span>, <span class="number">100</span>:<span class="number">400</span>] = <span class="number">255</span> <span class="comment"># 在指定区域设置为白色</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 按位与操作</span></span><br><span class="line">image_and = cv.bitwise_and(image1, image2)</span><br><span class="line">cv.imshow(<span class="string">"AND Result"</span>, image_and)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 按位或操作</span></span><br><span class="line">image_or = cv.bitwise_or(image1, image2)</span><br><span class="line">cv.imshow(<span class="string">"OR Result"</span>, image_or)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 按位非操作</span></span><br><span class="line">image_not = cv.bitwise_not(image1)</span><br><span class="line">cv.imshow(<span class="string">"NOT Result"</span>, image_not)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 按位异或操作</span></span><br><span class="line">image_xor = cv.bitwise_xor(image1, image2)</span><br><span class="line">cv.imshow(<span class="string">"XOR Result"</span>, image_xor)</span><br><span class="line"></span><br><span class="line">cv.waitKey(<span class="number">0</span>)</span><br><span class="line">cv.destroyAllWindows()</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> Learn </category>
</categories>
</entry>
<entry>
<title>功能教程及测试</title>
<link href="/%E7%A2%8E%E7%A2%8E%E5%BF%B5-2025-06-28-test-code.html"/>
<url>/%E7%A2%8E%E7%A2%8E%E5%BF%B5-2025-06-28-test-code.html</url>
<content type="html"><![CDATA[<h2 id="想当于一个给自己看的教程">想当于一个给自己看的教程</h2><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="built_in">print</span>(<span class="string">"hello world"</span>) <span class="built_in">print</span>(<span class="string">"hello world"</span>) <span class="built_in">print</span>(<span class="string">"hello world"</span>) <span class="built_in">print</span>(<span class="string">"hello world"</span>) <span class="built_in">print</span>(<span class="string">"hello world"</span>)<span class="built_in">print</span>(<span class="string">"hello world"</span>)<span class="built_in">print</span>(<span class="string">"hello world"</span>)<span class="built_in">print</span>(<span class="string">"hello world"</span>)<span class="built_in">print</span>(<span class="string">"hello world"</span>)<span class="built_in">print</span>(<span class="string">"hello world"</span>)</span><br><span class="line">a=<span class="number">1</span></span><br><span class="line">b=<span class="number">2</span></span><br><span class="line"><span class="built_in">print</span>(<span class="string">'a+b='</span>,a+b)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">"hello world"</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">"hello world"</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">"hello world"</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">"hello world"</span>)<span class="built_in">print</span>(<span class="string">"hello world"</span>)<span class="built_in">print</span>(<span class="string">"hello world"</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">"hello world"</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">"hello world"</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">"hello world"</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">"hello world"</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">"hello world"</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">"hello world"</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">"hello world"</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">"hello world"</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">"hello world"</span>)</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>这是尊龙先生<br><img src="/images/test_pic/test1.jpg" alt="尊龙" /></p><p>这是一张风景图<br><img src="/images/test_pic/test2.jpg" alt=""></p><img src="https://www.w3schools.cn/wp-content/uploads/linux/linux.png"><p> </p><p> 这是空格(段首空两格)</p>]]></content>
<categories>
<category> Think </category>
</categories>
</entry>
<entry>
<title>新的起点</title>
<link href="/%E7%A2%8E%E7%A2%8E%E5%BF%B5-2025-06-27-Hello.html"/>
<url>/%E7%A2%8E%E7%A2%8E%E5%BF%B5-2025-06-27-Hello.html</url>
<content type="html"><![CDATA[<h1>启程:我的博客 | YAN-CodeSpring 🌱</h1><blockquote><p><strong>2025年6月27日</strong> · 记录此刻 ✨</p></blockquote><p> 终于,我的个人博客在今天正式上线了!🎉🚀</p><p> 这其实不是我第一次尝试搭建博客。上一次的尝试,最终败给了自己当时那个"惊世骇俗"(或者说,过于中二😂)的博客名字。痛定思痛,这次我决定重新开始,并赋予它一个承载着期许的名字:<strong>YAN-CodeSpring</strong>。</p><h2 id="关于这个名字-🤔💡">关于这个名字 🤔💡</h2><ul><li><strong>YAN</strong>:我的姓氏,一个朴素的起点。</li><li><strong>Code</strong>:代码,不言而喻,这是我开创个人博客的主要目的。💻</li><li><strong>Spring</strong>:这个词对我有着双重意义:<ul><li><strong>春天</strong> 🌸:希望我写下的代码能像春风一样,为这个世界带来些许美好、生机与活力。</li><li><strong>泉水</strong> 💧:更希望自己在代码的世界里,能保持"文思泉涌"的状态,灵感源源不断!<br>这个名字与我用了很久的网名"言如泉"也形成了奇妙的呼应✨。不得不说,从"取名菜鸟"到最终拍板定下这个名字,过程堪比"网名生成器历险记"——让AI生成了无数选项,兜兜转转,最终发现还是自己灵光一闪的这个最合心意!💯 满意!</li></ul></li></ul><p> 为了让这个名字更鲜活,我还请"豆包"基于"CodeSpring"的意象生成了头像。结果让我非常惊喜🎨,它现在正骄傲地挂在我的GitHub主页上,感觉完美契合!顺便展示一下,嘻嘻~ 😊</p><img src="/images/think_pic/2025-06-27.jpg" alt="CodeSpring" style="width: 300px; height: auto;" ><h2 id="博客的-皮肤-还在挑选中-🎨🤔">博客的"皮肤"还在挑选中 🎨🤔</h2><p> 此刻你看到的,还是Hexo默认的<code>landscape</code>主题,朴实无华。美化工作嘛…容我过几天再折腾⏳。</p><p> 内心其实有点小纠结😖:我超爱<code>butterfly</code>主题那种动态、华丽的效果✨,扑面而来的高级感;但另一方面,<code>next</code>主题的极致简洁和清爽📄,又感觉更适合把博客当做简历。真是鱼与熊掌,难分难舍啊!🤷♂️</p><h2 id="未来会写些什么?-📝🔮">未来会写些什么? 📝🔮</h2><p> 嗯…既然这里大概率是我的"赛博自留地"🌌,人迹罕至,那写点"赛博日记"也未尝不可📖。虽然比打开写作软件码字多那么几步,但我应该会分享一些读书感悟📚、生活随想💭。</p><p> 更重要的,我希望它能成为我<strong>技术成长的见证者</strong>👨💻和<strong>心路历程的记录者</strong>❤️。所以,主要的内容方向会是<strong>机器学习</strong>🤖和<strong>计算机视觉</strong>👁️的学习笔记、项目心得。如果时间允许⏰,我也想把这次搭建博客过程中踩过的坑🕳️、解决的方案🔧整理出来(嗯,先画个饼🍪)。</p><p> 对了,有个小想法💡:是否能有办法给某些特定的页面或分类加上<strong>密码访问</strong>呢?🔐嘿嘿,你猜对了——谁还没点想"夹带私货"(比如放放我推?🌟)的小心思呢?🤫 这个功能得研究研究🔍。</p><h2 id="写在最后-🏁">写在最后 🏁</h2><p> 蓝图很美好🌈,规划很丰满📈(,现实很骨感💀)。不过今天嘛…最重要的任务已经完成啦!🎯</p><h3 id="——下班!😎💼">——下班!😎💼</h3><br><br><hr><div align="center"> <h2> English Version | 英文版本 </h2> <p><em>Scroll down for English translation ▼</em></p></div><hr><h1>Embarking: My Blog | YAN-CodeSpring 🌱</h1><blockquote><p><strong>June 27, 2025</strong> · Marking the Moment ✨</p></blockquote><p> Finally, my personal blog is officially live today! 🎉🚀</p><p> To be honest, this isn’t my first attempt at building a blog. My previous venture ultimately succumbed to what I can only describe as a… “spectacularly unconventional” (or perhaps, overly cringey 😂) blog name. Learning from that experience, I decided to start fresh and give it a name that carries my aspirations: <strong>YAN-CodeSpring</strong>.</p><h2 id="About-the-Name-🤔💡">About the Name 🤔💡</h2><ul><li><strong>YAN</strong>: My surname, a simple starting point.</li><li><strong>Code</strong>: Self-explanatory, the core purpose behind creating this personal blog. 💻</li><li><strong>Spring</strong>: This word holds a dual meaning for me:<ul><li><strong>The Season</strong> 🌸: Hoping the code I write can bring a touch of beauty, vitality, and renewal to the world.</li><li><strong>The Fountain</strong> 💧: Wishing for a constant, bubbling flow of inspiration and ideas in my coding journey!<br>This name also resonates beautifully with my long-time online alias, “言如泉” (Yán Rú Quán - Words Like a Spring) ✨. The journey from a “naming novice” to settling on this wasn’t easy! I tasked AI with generating countless options, but circling back, the one that sparked in my own mind felt just right. 💯 Truly satisfied!</li></ul></li></ul><p> To bring this concept to life visually, I asked “Doubao” to generate an avatar based on the “CodeSpring” imagery 🎨. The result delighted me! It now proudly adorns my GitHub profile, feeling like the perfect match. Couldn’t resist showing it off! 😊 Teehee~</p><img src="/images/think_pic/2025-06-27.jpg" alt="CodeSpring" style="width: 300px; height: auto;" ><h2 id="The-Blog’s-“Skin”-is-Still-Under-Consideration-🎨🤔">The Blog’s “Skin” is Still Under Consideration 🎨🤔</h2><p> What you see now is the default Hexo <code>landscape</code> theme – simple and unadorned. The beautification process… well, let’s tackle that in a few days ⏳.</p><p> Internally, I’m a bit torn 😖: I absolutely adore the dynamic, visually rich aesthetics of the <code>butterfly</code> theme ✨, it oozes sophistication. On the other hand, the extreme minimalism and clean lines of the <code>next</code> theme 📄 feel like it would be a more suitable canvas if I want to present this blog as a resume. Truly a dilemma – elegance versus practicality! 🤷♂️</p><h2 id="What-Lies-Ahead-📝🔮">What Lies Ahead? 📝🔮</h2><p> Well… since this space will likely be my own “cyber clearing” 🌌, sparsely populated, why not sprinkle in some “cyber diary” entries 📖? Though it involves a few more steps than just opening a writing app, I plan to share book reflections 📚 and life musings 💭 here.</p><p> More crucially, I envision this blog becoming a <strong>witness to my technical growth</strong> 👨💻 and a <strong>repository for my journey</strong> ❤️. So, the primary focus will be on notes and insights related to <strong>Machine Learning</strong> 🤖 and <strong>Computer Vision</strong> 👁️. If time permits ⏰, I’d also like to document the hurdles 🕳️ and solutions 🔧 I found while building this very site (consider this my first… feature promise? 🍪).</p><p> Oh, a little idea 💡: Would it be possible to implement <strong>password-protected access</strong> 🔐 for certain specific pages or categories? Heh, you guessed it – who doesn’t have a few little secrets (like maybe… my favorite idols? 🌟) they’d like to tuck away? 🤫 That’s a feature worth exploring 🔍.</p><h2 id="Wrapping-Up-🏁">Wrapping Up 🏁</h2><p> The blueprint is grand 🌈, the plans are ambitious 📈 (, reality might be harsh 💀). But for today… the most important task is done! 🎯</p><h3 id="——Logging-off-😎💼">——Logging off! 😎💼</h3>]]></content>
<categories>
<category> Think </category>
</categories>
</entry>
</search>