<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Han&#39;s Notebook</title>
  
  <subtitle>祝大家心明眼亮。</subtitle>
  <link href="https://archived.yanghan.life/atom.xml" rel="self"/>
  
  <link href="https://archived.yanghan.life/"/>
  <updated>2022-09-06T10:00:10.366Z</updated>
  <id>https://archived.yanghan.life/</id>
  
  <author>
    <name>YANG Han</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Semi-Supervised Learning 部分技巧简介</title>
    <link href="https://archived.yanghan.life/2020/03/21/Semi-Supervised-Learning-%E9%83%A8%E5%88%86%E6%8A%80%E5%B7%A7%E7%AE%80%E4%BB%8B/"/>
    <id>https://archived.yanghan.life/2020/03/21/Semi-Supervised-Learning-%E9%83%A8%E5%88%86%E6%8A%80%E5%B7%A7%E7%AE%80%E4%BB%8B/</id>
    <published>2020-03-21T12:27:42.000Z</published>
    <updated>2022-09-06T10:00:10.366Z</updated>
    
    <content type="html"><![CDATA[<p>最近看了几篇 Semi-Supervised Learning 的文章，感觉要达到 state-of-the-art 的话就是要把几个比较有效的技巧比较好地结合到一起，所以写一篇文章分别介绍一下这些技巧。文章总体的结构内容基于 MixMatch: A Holistic Approach to Semi-Supervised Learning (NeurIPS 2019)，及一些适当的延伸。</p><span id="more"></span><p>我们主要讨论就是 Transductive Learning 的场景，即 在训练时 \((X_{label}, Y_{label}, X_{unlabel})\)，来预测 $Y_{label}$ 。Inductive Learning 的场景就更像是 supervised learning，即在训练时只用$(X_{label}, Y_{label})$ ，在预测的时候才用到$X_{unlabel}$ 来预测 $Y_{unlabel}$。半监督学习 的主要想法就是，认为需要预测的内容的特征（例如 分布信息）可以帮助学到一个更好的 classifier。</p><h2 id="Entropy-Minimization"><a href="#Entropy-Minimization" class="headerlink" title="Entropy Minimization"></a>Entropy Minimization</h2><p>在 Semi-Supervised Learning 中，一个常见的 Assumption 是，分类器的 decision boundary 不应该穿过数据分布中的 high-density 区域。一种直接的方式就是增加一个 loss term 来直接显式地降低 entropy。</p><h3 id="Sharpening"><a href="#Sharpening" class="headerlink" title="Sharpening"></a>Sharpening</h3><p>对于unlabeled的数据，可以用 Sharpening 的技巧来隐式地降低Entropy。</p><p>$$<br>\text {Sharpen}\left(p_{i}, T\right):=p_{i}^{\frac{1}{T}} / \sum_{j=1}^{C} p_{j}^{\frac{1}{T}}<br>$$</p><p>其中，$p$ 是 categorical distribution，$T$ 是一个 hyperparameter。当$T \rightarrow 0$ 时， $\text {Sharpen}\left(p_{i}, T\right)$ 就会接近于 Dirac Distribution (one-hot)。</p><p>由于 $\text {Sharpen}\left(p_{i}, T\right)$ 会被用作模型对于 unlabeled 数据的预测的 target，所以较低的 $T$ 会使得模型更倾向于输出低 entropy 的结果。</p><h2 id="Consistency-Regularization"><a href="#Consistency-Regularization" class="headerlink" title="Consistency Regularization"></a>Consistency Regularization</h2><p>简单来说就是把 data augmentation 的技巧运用到 Semi-Supervised Learning 中来。基于的基本想法就是对于一个sample，即使它被增强过，分类器输出的 class distribution 也应该是不变的。</p><h3 id="Regularization-with-stochastic-perturbations"><a href="#Regularization-with-stochastic-perturbations" class="headerlink" title="Regularization with stochastic perturbations"></a>Regularization with stochastic perturbations</h3><p>这是最简单直接的方式，增加一个loss来控制不同的 stochastic transformations 对输出带来的影响:</p><p>$$<br>| p_{\text {model }}(y | \text { Augment }(x) ; \theta)-p_{\text {model }}(y | \text { Augment }(x) ; \theta) |_{2}^{2}<br>$$</p><h3 id="Label-Guessing"><a href="#Label-Guessing" class="headerlink" title="Label Guessing"></a>Label Guessing</h3><p>对于一个 unlabeled 的 sample，可以先用模型输出来为这个 sample 猜出一个 label 出来。然后这个猜出来的 label 可以用在 unsupervised loss 中（即 \(\mathcal{L}<em>\mathcal{U}=\frac{1}{L\left|\mathcal{U}^\prime \right|}   \sum</em>{u, q \in \mathcal{U}^\prime}\left|q - p_{model}(y | u ; \theta)\right|_2^2 \) ， $u$ 是 unlabeled item， $q$ 是 guessing label (distribution)）。</p><p>实践中可以用 $K$ 个 augmented 的 $u_b$ 的模型预测的平均作为 guessing label 来增加稳定性：</p><p>\[<br>\mathcal{L}<em>{\mathcal{U}}=\frac{1}{\left|\mathcal{U}^{\prime}\right|} \sum</em>{u, q \in \mathcal{U}^{\prime}}|q-p_{model}(y | u ; \theta)|_{2}^{2}.<br>\]</p><p>也有文章有讨论了 mean-teacher （即 averaging model weights instead of predictions），认为这种方式会更好。</p><h3 id="Exponential-Moving-Average-EMA"><a href="#Exponential-Moving-Average-EMA" class="headerlink" title="Exponential Moving Average (EMA)"></a>Exponential Moving Average (EMA)</h3><p>即是对模型输出的 predictions 进行 EMA 的计算来作为新的 target。</p><p>$$<br>Z = \alpha Z + (1-\alpha)z<br>$$</p><p>$$<br>\tilde{z} = \frac{Z}{1-\alpha^t}<br>$$</p><p>$Z$ 被初始为 $\mathbf{0}_{N\times C}$，$z$ 是每个 epoch 模型对于每个 sample 输出，$t$ 是epoch，$\tilde{z}$是经过 bias correction 的 target vector。</p><h3 id="Virtual-Adversarial-Training-VAT"><a href="#Virtual-Adversarial-Training-VAT" class="headerlink" title="Virtual Adversarial Training (VAT)"></a>Virtual Adversarial Training (VAT)</h3><blockquote><p>Virtual adversarial loss is defined as the robustness of the conditional label distribution around each input data point against local perturbation.</p></blockquote><p>首先 adversarial training 就是在输入中加入一个扰动 $r_{adv}$，使得模型的输出发生尽可能大程度的变化，即：</p><p>$$<br>L_{\mathrm{adv}}\left(x_{l}, \theta\right):=D\left[q\left(y | x_{l}\right), p\left(y | x_{l}+r_{\mathrm{adv}}, \theta\right)\right]<br>$$</p><p>其中</p><p>$$<br>r_{\mathrm{adv}}:=\underset{r ;|r| \leq \epsilon}{\arg \max } D\left[q\left(y | x_{l}\right), p\left(y | x_{l}+r, \theta\right)\right]<br>$$</p><p>$D[q, p]$是描述两个分布之间的 divergence 的非负函数，例如 Cross Entropy。</p><p>至于 Virtual Adversarial Training，就是用当前模型的输出 $p(y|x, \hat\theta)$ 来近似数据label的真实概率分布 $q(y|x)$ 。这样就定义了一种 virtual adversarial perturbation. 这样就很容易写出对应的 loss item:</p><p>$$<br>\operatorname{LDS}\left(x_*, \theta\right):=D\left[p\left(y | x_*, \hat{\theta}\right), p\left(y | x_*+r_{\text {vadv }}, \theta\right)\right]<br>$$</p><p>其中 $$r_{\mathrm{vadv}}:=\underset{r ;|r|<em>{2} \leq \epsilon}{\arg \max } D\left[p\left(y | x</em>{*}, \hat{\theta}\right), p\left(y | x_{*}+r\right)\right]$$</p><p>$x_*$ 包含了 $x_{label}, x_{unlabel}$.</p><h2 id="Generic-Regularization"><a href="#Generic-Regularization" class="headerlink" title="Generic Regularization"></a>Generic Regularization</h2><p>有一些 Regularizaion 的方法就是给模型加上一些 constraint 使它避免“记住”训练数据，从而更好地 generalize 到别的 unseen data。最为常见的一种做法就是给模型参数加上一个 $L_2$-weight-decay。</p><h3 id="Mixup"><a href="#Mixup" class="headerlink" title="Mixup"></a>Mixup</h3><p>简单来说，mixup 就是构造这样的虚拟training samples:</p><p>$$<br>\tilde{x} = \lambda x_i + (1-\lambda)x_j, \ \tilde{y} = \lambda y_i + (1-\lambda)y_j. \<br>$$</p><p>其中 $x_i, x_j$ 是原始的输入vector，$y_i, y_j$ 是 one-hot label encoding，$\lambda \in [0, 1]$。</p><p>把 mixup 应用到 Semi-Supervised Learning 的话，可以把 labeled data 和 unlabeled data 一起 mixup 起来，其中 unlabeled sample 的 $y$ 可以换成 guessing label $q$。另外还可以加一个小trick就是让$\lambda \in [0.5, 1]$，使得虚拟的 sample 可以更靠近真实的数据。这种情况下mixup其实应该属于 Consistency Regularization.</p><h2 id="Other-Related-Things"><a href="#Other-Related-Things" class="headerlink" title="Other Related Things"></a>Other Related Things</h2><h3 id="Why-L-2-loss"><a href="#Why-L-2-loss" class="headerlink" title="Why $L_2$ loss"></a>Why $L_2$ loss</h3><p>用 cross entropy 的时候，需要先用 Softmax 计算出概率，但是如果所有输出值都加上一个常数的话，softmax 的结果是不变的。所以为了让两个向量尽可能相等，$L_2$ 是更为严格的限制。</p><h3 id="Warmup-of-lambda"><a href="#Warmup-of-lambda" class="headerlink" title="Warmup of $\lambda$"></a>Warmup of $\lambda$</h3><p>整体的 loss function 是由监督loss和unlabeled data的loss组合起来的 $L = L_X + \lambda L_U$，所以中间会有一个$\lambda$ 来控制两者的比例。相比于直接将$\lambda$设置为一个常数，一些实验中发现将它从0慢慢 linear warmup 到它的 final value 可以提升最后的分类 accuracy。</p><h2 id="References"><a href="#References" class="headerlink" title="References"></a>References</h2><ol><li><a href="https://arxiv.org/pdf/1905.02249.pdf">MixMatch: A Holistic Approach to Semi-Supervised Learning</a></li><li><a href="https://papers.nips.cc/paper/2740-semi-supervised-learning-by-entropy-minimization.pdf">Semi-supervised Learning by Entropy Minimization</a></li><li><a href="https://arxiv.org/pdf/1610.02242.pdf">Temporal Ensembling for Semi-Supervised Learning</a></li><li><a href="https://arxiv.org/pdf/1703.01780.pdf">Mean teachers are better role models: Weight-averaged consistency targets improve semi-supervised deep learning results</a></li><li><a href="https://arxiv.org/pdf/1710.09412.pdf">mixup: Beyond Empirical Risk Minimization</a></li></ol>]]></content>
    
    
    <summary type="html">&lt;p&gt;最近看了几篇 Semi-Supervised Learning 的文章，感觉要达到 state-of-the-art 的话就是要把几个比较有效的技巧比较好地结合到一起，所以写一篇文章分别介绍一下这些技巧。文章总体的结构内容基于 MixMatch: A Holistic Approach to Semi-Supervised Learning (NeurIPS 2019)，及一些适当的延伸。&lt;/p&gt;</summary>
    
    
    
    <category term="学习笔记" scheme="https://archived.yanghan.life/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="学习笔记" scheme="https://archived.yanghan.life/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="ML" scheme="https://archived.yanghan.life/tags/ML/"/>
    
    <category term="Semi-Supervised Learning" scheme="https://archived.yanghan.life/tags/Semi-Supervised-Learning/"/>
    
  </entry>
  
  <entry>
    <title>Notes about GCN Sampling</title>
    <link href="https://archived.yanghan.life/2019/09/08/Notes-about-GCN-Sampling/"/>
    <id>https://archived.yanghan.life/2019/09/08/Notes-about-GCN-Sampling/</id>
    <published>2019-09-08T12:47:54.000Z</published>
    <updated>2022-09-06T10:00:10.366Z</updated>
    
    <content type="html"><![CDATA[<p>I read several papers about Sampling Algorithm in Graph Convolution Network training last week. So I wrote this note to simply record them.</p><span id="more"></span><table><thead><tr><th>METHOD</th><th>SAMPLING SCHEME</th><th>CONFERENCE</th></tr></thead><tbody><tr><td>GraphSAGE</td><td>node-wise</td><td>NIPS 17</td></tr><tr><td>FastGCN</td><td>layer-wise</td><td>ICLR 18</td></tr><tr><td>StochasticGCN</td><td>node-wise</td><td>ICML 18</td></tr><tr><td>AdaptiveSampling</td><td>layer-wise</td><td>NIPS 18</td></tr><tr><td>ClusterGCN</td><td>?node-wise</td><td>KDD 19</td></tr></tbody></table><h2 id="GraphSAGE"><a href="#GraphSAGE" class="headerlink" title="GraphSAGE"></a>GraphSAGE</h2><p><a href="https://arxiv.org/abs/1706.02216">Paper Link</a></p><p><img src="/assets/graphsage.png" alt="GraphSAGE"></p><p>This sampling method is uniform sampling and very easy to understand. It uses a Top-down approach, which means that when it is calculating a node’s output, the algorithm finds the node’s neighbors layer by layer, until the node’s representation vector can be calculated.</p><p><img src="/assets/graphsage-codes.png" alt="GraphSAGE Pseudocode"></p><p>In my opinion, this paper was not made for Sampling. It just use a simple and naive uniform sampling trick to avoid too heavy calculation. The main purpose of this paper might just be to make the train of GCN can be processed in a <code>batch</code> manner.</p><h2 id="FastGCN"><a href="#FastGCN" class="headerlink" title="FastGCN"></a>FastGCN</h2><p><a href="https://arxiv.org/abs/1801.10247">Paper Link</a></p><p><img src="/assets/fastgcn.png" alt="FastGCN"><br>This paper rewrites the Message Passing formula in am integral form, then uses Monte Carlo Sampling to approximate the integral value.</p><p>$$<br>\tilde{h}<em>{t+1}(v)=\int \hat{A}(v, u) h^{(l)}(u) W^{(l)} d P(u),  h^{(l+1)}(v)=\sigma\left(\tilde{h}^{(l+1)}(v)\right), \quad l=0, \ldots, M-1 $$<br>$$<br>L=\mathrm{E}</em>{v \sim P}\left[g\left(h^{(M)}(v)\right)\right]=\int g\left(h^{(M)}(v)\right) d P(v)  $$<br>$$<br>\tilde{h}<em>{t</em>{l+1}}^{(l+1)}(v) :=\frac{1}{t_{l}} \sum_{j=1}^{t_{l}} \hat{A}\left(v, u_{j}^{(l)}\right) h_{t_{l}}^{(l)}\left(u_{j}^{(l)}\right) W^{(l)},  h_{t_{l+1}}^{(l+1)}(v) :=\sigma\left(\tilde{h}<em>{t</em>{l+1}}^{(l+1)}(v)\right), \quad l=0, \ldots, M-1<br>$$</p><p>It uses a factor proportional to the degree of the node as the importance sampling factor.</p><p><img src="/assets/fastgcn-codes.png" alt="FastGCN Pseudocode"></p><h2 id="StochasticGCN"><a href="#StochasticGCN" class="headerlink" title="StochasticGCN"></a>StochasticGCN</h2><p><a href="https://arxiv.org/abs/1710.10568">Paper Link</a></p><p>The algorithm is not complicated in this paper, but this paper provides many theoretical results and proofs.</p><p><img src="/assets/stogcn.png" alt="Stochastic GCN"></p><p>The algorithm is that when aggregating neighbors’ features/activations, only a few of neighbors will actually computes their activations, while the others will use the historical activations as approximation.</p><p>It can be easily derived that in the end when the message passing reaches a stationary point, the sampling variance will be eliminated to zero.</p><p><img src="/assets/stogcn-codes.png" alt="Stochastic GCN Pseudocode"></p><p>Along with the insight, the paper also provides the convergence guarantee and variance analysis. It has high theoretical value.</p><h2 id="Adaptive-Sampling"><a href="#Adaptive-Sampling" class="headerlink" title="Adaptive Sampling"></a>Adaptive Sampling</h2><p><a href="https://arxiv.org/abs/1809.05343">Paper Link</a></p><p>This paper follows the layer-wise style as FastGCN. It rewrites the message passing formula as</p><p>$$<br>\begin{array}{c}{h^{(l+1)}\left(v_{i}\right)=\sigma_{W^{(l)}}\left(N\left(v_{i}\right) \mathbb{E}<em>{q\left(u</em>{j} | v_{1}, \cdots, v_{n}\right)}\left[\frac{p\left(u_{j} | v_{i}\right)}{q\left(u_{j} | v_{1}, \cdots, v_{n}\right)} h^{(l)}\left(u_{j}\right)\right]\right)} \ {h^{(l+1)}\left(v_{i}\right)=\sigma_{W^{(l)}}\left(N\left(v_{i}\right) \hat{\mu}<em>{q}\left(v</em>{i}\right)\right)} \ {\hat{\mu}<em>{q}\left(v</em>{i}\right)=\frac{1}{n} \sum_{j=1}^{n} \frac{p\left(\hat{u}<em>{j} | v</em>{1}, \cdots, v_{n}\right)}{q\left(\hat{u}<em>{j} | v</em>{1}, \cdots, v_{n}\right)} h^{(l)}\left(\hat{u}<em>{j}\right), \quad \hat{u}</em>{j} \sim q\left(\hat{u}<em>{j} | v</em>{1}, \cdots, v_{n}\right)}\end{array}<br>$$.</p><p>So the important thing is to model the $q\left(u_{j} | v_{1}, \cdots, v_{n}\right)$.</p><p>In order to minimize the sampling variance, the optimal $q(u_j)$ can be modeled as</p><p>$$<br>\qquad \operatorname{Var}<em>{q}\left(\hat{\mu}</em>{q}\left(v_{i}\right)\right)=\frac{1}{n} \mathbb{E}<em>{q\left(u</em>{j}\right)}\left[\frac{\left(p\left(u_{j} | v_{i}\right)\left|h^{(l)}\left(u_{j}\right)\right|-\mu_{q}\left(v_{i}\right) q\left(u_{j}\right)\right)^{2}}{q^{2}\left(u_{j}\right)}\right] \  \qquad q^{*}\left(u_{j}\right)=\frac{p\left(u_{j} | v_{i}\right)\left|h^{(l)}\left(u_{j}\right)\right|}{\sum_{j=1}^{N} p\left(u_{j} | v_{i}\right)\left|h^{(l)}\left(u_{j}\right)\right|}<br>$$</p><p>But the $q(u_j)$ can’t be calculated before the layer was constructed. So the paper proposes to approximate it as</p><p>$$<br>q^{*}\left(u_{j}\right)=\frac{p\left(u_{j} | v_{i}\right)\left|g\left(x\left(u_{j}\right)\right)\right|}{\sum_{j=1}^{N} p\left(u_{j} | v_{i}\right)\left|g\left(x\left(u_{j}\right)\right)\right|}<br>$$<br>.</p><p>So the $q(u_i)$ can be calculated as<br>$$<br>q^{*}\left(u_{j}\right)=\frac{\sum_{i=1}^{n} p\left(u_{j} | v_{i}\right)\left|g\left(x\left(u_{j}\right)\right)\right|}{\sum_{j=1}^{N} \sum_{i=1}^{n} p\left(u_{j} | v_{i}\right)\left|g\left(x\left(v_{j}\right)\right)\right|}<br>$$<br>.</p><p>This paper doesn’t provide as many theoretical proofs as Stochastic GCN.</p><h2 id="Cluster-GCN"><a href="#Cluster-GCN" class="headerlink" title="Cluster GCN"></a>Cluster GCN</h2><p><a href="https://arxiv.org/abs/1905.07953">Paper Link</a></p><p>This paper doesn’t contain any theoretical proof, but reports better results than all previous work, which means it’s algorithm is intuitive but empirically efficient.</p><p>The algorithm is easy to follow according to the pseudocode below. No need for explanation.</p><p><img src="/assets/cluster-gcn.png" alt="ClusterGCN Pseudocode"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;I read several papers about Sampling Algorithm in Graph Convolution Network training last week. So I wrote this note to simply record them.&lt;/p&gt;</summary>
    
    
    
    <category term="Notes" scheme="https://archived.yanghan.life/categories/Notes/"/>
    
    
    <category term="Notes" scheme="https://archived.yanghan.life/tags/Notes/"/>
    
    <category term="GNN" scheme="https://archived.yanghan.life/tags/GNN/"/>
    
    <category term="ML" scheme="https://archived.yanghan.life/tags/ML/"/>
    
    <category term="Sampling" scheme="https://archived.yanghan.life/tags/Sampling/"/>
    
  </entry>
  
  <entry>
    <title>两道有趣的离散数学题目</title>
    <link href="https://archived.yanghan.life/2019/01/15/%E4%B8%A4%E9%81%93%E6%9C%89%E8%B6%A3%E7%9A%84%E7%A6%BB%E6%95%A3%E6%95%B0%E5%AD%A6%E9%A2%98%E7%9B%AE/"/>
    <id>https://archived.yanghan.life/2019/01/15/%E4%B8%A4%E9%81%93%E6%9C%89%E8%B6%A3%E7%9A%84%E7%A6%BB%E6%95%A3%E6%95%B0%E5%AD%A6%E9%A2%98%E7%9B%AE/</id>
    <published>2019-01-15T04:25:00.000Z</published>
    <updated>2022-09-06T10:00:10.366Z</updated>
    
    <content type="html"><![CDATA[<p>有两道比较有趣的题目，为了防止忘掉，记录一下。</p><span id="more"></span><h2 id="1-实数集uncountable"><a href="#1-实数集uncountable" class="headerlink" title="1 实数集uncountable"></a>1 实数集uncountable</h2><p>这里countable的定义就是与集合里的元素能与自然数集一一对应，比如说偶数集和自然数集有2n和n的对应关系，所以说这两个集合大小相等，都是$\aleph 0$.</p><p>这道题目是当年大二时候的离散数学课后习题，最近刚好跟人聊天聊到相关的话题，回忆了一下怎么证明。</p><p>这里记录几个简单的结论/题目。</p><h3 id="1-1-有理数集countable"><a href="#1-1-有理数集countable" class="headerlink" title="1.1 有理数集countable"></a>1.1 有理数集countable</h3><p>法一（直观）：</p><p>对于有理数m/n, 按</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">1/1</span><br><span class="line">1/2 2/1</span><br><span class="line">1/3 2/2 3/1</span><br><span class="line">1/4 2/3 3/2 4/1</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>排列去数，可以与自然数一一对应。</p><p>法二：</p><p>对于任意一个既约有理数m/n，构造映射$y=2^n3^m$，y是自然数，那么对于不同的m/n，一定有不同的自然数y。所以自然数集小于等于有理数集。</p><p>反过来，自然数是有理数的子集，所以自然数集又不大于有理数集。</p><p>综上，两集合基数相等，所以有理数集是可数集。</p><h3 id="1-2-若集合A-B都countable，则-A-cup-B-countable"><a href="#1-2-若集合A-B都countable，则-A-cup-B-countable" class="headerlink" title="1.2 若集合A, B都countable，则$A \cup B$ countable"></a>1.2 若集合A, B都countable，则$A \cup B$ countable</h3><p>一、若$A \subseteq B$ 或者 $A \supseteq B$, 显然。</p><p>二、若$A \backslash B \neq \phi$ 且 $B \backslash A \neq \phi$</p><p>$$<br>A \cup B = A \cup (B \backslash A)<br>$$</p><p>$A$ countable, 对应$f:A \rightarrow N$</p><p>$B \backslash A$ countable, 对应$g:(B \backslash A) \rightarrow N$.</p><p>定义 $h:A \cup B \rightarrow N$:</p><p>$$<br>h(x)=<br>\begin{cases}<br>2f(x)&amp; x \in A\<br>2g(x)+1&amp; x \in B \backslash A<br>\end{cases}<br>$$</p><p>即可证明 $A \cup B$ countable.</p><h3 id="1-3-0-1-的无理数uncountable"><a href="#1-3-0-1-的无理数uncountable" class="headerlink" title="1.3 (0, 1)的无理数uncountable"></a>1.3 (0, 1)的无理数uncountable</h3><p>假设(0,1)的实数countable, </p><p>则对于(0,1)的实数集：X {x1,x2,x3,…,xn}</p><p>总能找到一个实数H=0.abcdefg….. , 使得</p><p>a != x1小数点后第一位</p><p>b != x2小数点后第二位</p><p>c != x3小数点后第三位</p><p>…</p><p>由此得出$H \notin X$</p><p>产生矛盾, 所以(0,1)的实数集uncountable.</p><p>实数=有理数+无理数</p><p>有理数countable，所以无理数uncountable.</p><p>由(0,1)的实数集uncountable可知实数集uncountable.</p><h2 id="2-Stolen-Necklace-Problem"><a href="#2-Stolen-Necklace-Problem" class="headerlink" title="2 Stolen Necklace Problem"></a>2 Stolen Necklace Problem</h2><p>这道题目来自<a href="https://youtu.be/yuVqxCSsE7c">3Blue1Brown的Sneaky Topology</a>。这里简单总结一下要点。</p><p>题目：把一串有n种宝石的项链平分给两个人（每种宝石有偶数个），那么在项链上至多切n刀即可完成。</p><h3 id="2-1-Borsuk-Ulam-Theorem"><a href="#2-1-Borsuk-Ulam-Theorem" class="headerlink" title="2.1 Borsuk-Ulam Theorem"></a>2.1 Borsuk-Ulam Theorem</h3><p><a href="https://en.wikipedia.org/wiki/Borsuk%E2%80%93Ulam_theorem">Borsuk-Ulam Theorem</a></p><p>简单地拿三维空间里的球体来说，通过一个连续函数将其映射到一个二维平面 $f: R^3 \rightarrow R^2$ ，必然可以找到一对在两极的点(antipodes 对跖点)在映射后是二维平面上的同一个点。$f(x) = f(-x)$</p><p>简单证明一下：</p><p>构造$g(x)$，</p><p>$$ g(x) = f(x) - f(-x) $$</p><p>$$ g(x) = -g(-x) $$</p><p>所以对于赤道上的点，$g(x)$的图像是围绕原点的一个圈。将赤道这条纬线连续向北极移动，到北极的时候$g(x)$的值是一个点。在这个连续的过程中$g(x)$的图像必然经过原点，这就证明了$g(x)$有零点，原命题得证。</p><h3 id="2-2-回到原题目"><a href="#2-2-回到原题目" class="headerlink" title="2.2 回到原题目"></a>2.2 回到原题目</h3><p>假设项链总长度为1，切两刀后的三段长度为$x,y,z$。那么<br>$$ x^2+y^2+z^2=1 $$<br>意味着每种切法都对应球上一点。<br>antipodes对应的切法相同，但是分法互换。(e.g. xz给A，y给B 和 y给A，xz给B 这两种分法)<br>$f(x)=f(-x)$意味着AB两人分得的内容相同，互换后不变。</p><p>这样，Borsuk-Ulam Theorem 就证明了 2种宝石的 Stolen Necklace Problem 可以用2刀解决。</p><p>Borsuk-Ulam Theorem 和 Stolen Necklace Problem 都可以推广到n.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;有两道比较有趣的题目，为了防止忘掉，记录一下。&lt;/p&gt;</summary>
    
    
    
    <category term="学习笔记" scheme="https://archived.yanghan.life/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="学习笔记" scheme="https://archived.yanghan.life/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="数学" scheme="https://archived.yanghan.life/tags/%E6%95%B0%E5%AD%A6/"/>
    
    <category term="离散数学" scheme="https://archived.yanghan.life/tags/%E7%A6%BB%E6%95%A3%E6%95%B0%E5%AD%A6/"/>
    
    <category term="拓扑学" scheme="https://archived.yanghan.life/tags/%E6%8B%93%E6%89%91%E5%AD%A6/"/>
    
  </entry>
  
  <entry>
    <title>基于OpenCV 3的柱面全景拼接</title>
    <link href="https://archived.yanghan.life/2018/06/15/%E5%9F%BA%E4%BA%8EOpenCV3%E7%9A%84%E6%9F%B1%E9%9D%A2%E5%85%A8%E6%99%AF%E6%8B%BC%E6%8E%A5/"/>
    <id>https://archived.yanghan.life/2018/06/15/%E5%9F%BA%E4%BA%8EOpenCV3%E7%9A%84%E6%9F%B1%E9%9D%A2%E5%85%A8%E6%99%AF%E6%8B%BC%E6%8E%A5/</id>
    <published>2018-06-15T14:54:18.000Z</published>
    <updated>2022-09-06T10:22:15.281Z</updated>
    
    <content type="html"><![CDATA[<p>全景图拼接是利用同一场景的多张图像通过重叠部分寻找匹配关系，从而生成整个场景图像的技术。 全景图的拼接方法有很多，如按场景和运动的种类可以分为单视点全景拼接和多视点全景拼接。对于平面场景和只通过相机旋转拍摄的场景来说，可以使用求每两幅图像之间的一个Homography变换来映射到一张图像的方法，还可以使用恢复相机的旋转的方式得到最终的全景图。当相机固定只有水平方向旋转时，也可以使用柱面或球面坐标映射的方式求得全景图。</p><span id="more"></span><h2 id="实验目标"><a href="#实验目标" class="headerlink" title="实验目标"></a>实验目标</h2><p>实现一个Panorama类，实现给定一组序列图片和焦距，输出拼接的全景图像的功能。</p><h2 id="算法原理"><a href="#算法原理" class="headerlink" title="算法原理"></a>算法原理</h2><h3 id="柱面投影"><a href="#柱面投影" class="headerlink" title="柱面投影"></a>柱面投影</h3><h4 id="目标"><a href="#目标" class="headerlink" title="目标"></a>目标</h4><p>把平面图像投影到柱面上。</p><h4 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h4><p>$$<br>x^{‘}=f<em>atan\left(\frac {x-0.5</em>width}{f}\right)+f<em>atan\left( \frac {0.5</em>width}{f}\right)<br>$$</p><p>$$<br>y^{‘}=\frac {f*(y-0.5<em>height)}{\sqrt {(x-0.5</em>width)^2+f^2}}+0.5*height<br>$$<br><img src="/assets/611478ffgy1fse7jh2glmj20ea08bglv.jpg" alt="transform"></p><h3 id="特征抽取与匹配"><a href="#特征抽取与匹配" class="headerlink" title="特征抽取与匹配"></a>特征抽取与匹配</h3><h4 id="目标-1"><a href="#目标-1" class="headerlink" title="目标"></a>目标</h4><p>对每两幅相邻的柱面图像进行特征提取和匹配，寻找两幅相邻图像的对应关系。</p><h4 id="原理-1"><a href="#原理-1" class="headerlink" title="原理"></a>原理</h4><p>SIFT 特征是基于物体上的一些局部外观的兴趣点而与影像的大小和旋转无关。对于光线、噪声、些微视角改变的容忍度也相当高。<br>通过SIFT特征的提取，然后用BruteForceMatch或者KnnMatch可以对SIFT计算出匹配。<br>用匹配的特征点可以训练出homography。</p><h3 id="计算变换，进行拼接"><a href="#计算变换，进行拼接" class="headerlink" title="计算变换，进行拼接"></a>计算变换，进行拼接</h3><h4 id="目标-2"><a href="#目标-2" class="headerlink" title="目标"></a>目标</h4><p>使用得到的匹配关系，求出每两幅柱面图像的平移变换，利用平移变换将所有图像拼接到一起。得到全景图。</p><h4 id="原理-2"><a href="#原理-2" class="headerlink" title="原理"></a>原理</h4><p>通过RANSAC之后的匹配特征点，可以从中计算得出homography。利用这个homography，可以算出图片的变换，利用此变换可以将两幅图像拼接在一起。</p><h2 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h2><h3 id="接口"><a href="#接口" class="headerlink" title="接口"></a>接口</h3><p>核心代码是实现如下的接口：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CylindricalPanorama</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="keyword">bool</span> <span class="title">makePanorama</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function">        std::vector&lt;cv::Mat&gt;&amp; img_vec, cv::Mat&amp; img_out, <span class="keyword">double</span> f</span></span></span><br><span class="line"><span class="params"><span class="function">    )</span> </span>= <span class="number">0</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="流程"><a href="#流程" class="headerlink" title="流程"></a>流程</h3><ol><li>对列表中所有图片进行柱面投影，并存下来</li><li>对于上一次的拼接结果和下一张图片求SIFT特征点</li><li>匹配SIFT特征点</li><li>计算homography</li><li>利用homography进行变换，拼接</li><li>重复上述步骤直到用完所有图片，完成全景拼接</li></ol><p>这里有一个细节是如果从左往右拼接的话，最好是把左边的图片变换到右边图片的坐标系中，<br>这样可以方便之后的特征点匹配和homography的计算。</p><h4 id="柱面投影-1"><a href="#柱面投影-1" class="headerlink" title="柱面投影"></a>柱面投影</h4><p>这里通过最近邻插值算法来求柱面图上的点到原图的对应位置，并用此位置的像素值作为此点的像素值。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">Mat <span class="title">cylinder</span><span class="params">(Mat&amp; img, <span class="keyword">double</span> f)</span> </span>&#123;</span><br><span class="line">    Mat output;</span><br><span class="line">    <span class="keyword">int</span> cols = (<span class="keyword">int</span>)<span class="number">2</span> * f * <span class="built_in">atan</span>(<span class="number">0.5</span>*img.cols / f);</span><br><span class="line">    <span class="keyword">int</span> rows = (<span class="keyword">int</span>)img.rows;</span><br><span class="line">    output.<span class="built_in">create</span>(rows, cols, CV_8UC3);</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; rows; i++) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j &lt; cols; j++) &#123;</span><br><span class="line">            <span class="keyword">int</span> x = (<span class="keyword">int</span>)(f * <span class="built_in">tan</span>((<span class="keyword">float</span>)(j - cols * <span class="number">0.5</span>) / f) + img.cols*<span class="number">0.5</span>);</span><br><span class="line">            <span class="keyword">int</span> y = (<span class="keyword">int</span>)((i - <span class="number">0.5</span>*rows)*<span class="built_in">sqrt</span>(<span class="built_in">pow</span>(x - img.cols*<span class="number">0.5</span>, <span class="number">2</span>) + f*f) / f + <span class="number">0.5</span>*img.rows);</span><br><span class="line">            <span class="keyword">if</span> (<span class="number">0</span> &lt;= x &amp;&amp; x &lt; img.cols &amp;&amp; <span class="number">0</span> &lt;= y &amp;&amp; y &lt; img.rows) &#123;</span><br><span class="line">                output.at&lt;Vec3b&gt;(i, j) = img.at&lt;Vec3b&gt;(y, x);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">else</span> &#123;</span><br><span class="line">                output.at&lt;Vec3b&gt;(i, j) = <span class="built_in">Vec3b</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> output;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="特征点提取"><a href="#特征点提取" class="headerlink" title="特征点提取"></a>特征点提取</h4><p>这里的SIFT特征点是用OpenCV 3 的写法。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">Ptr&lt;Feature2D&gt; f2d = xfeatures2d::SIFT::<span class="built_in">create</span>();</span><br><span class="line">vector&lt;KeyPoint&gt; kps_0, kps_1;</span><br><span class="line">f2d-&gt;<span class="built_in">detect</span>(img_1, kps_0);</span><br><span class="line">f2d-&gt;<span class="built_in">detect</span>(img_2, kps_1);</span><br><span class="line"></span><br><span class="line">Mat descriptors_0, descriptors_1;</span><br><span class="line">f2d-&gt;<span class="built_in">compute</span>(img_1, kps_0, descriptors_0);</span><br><span class="line">f2d-&gt;<span class="built_in">compute</span>(img_2, kps_1, descriptors_1);</span><br></pre></td></tr></table></figure><h4 id="特征点的匹配和筛选"><a href="#特征点的匹配和筛选" class="headerlink" title="特征点的匹配和筛选"></a>特征点的匹配和筛选</h4><p>其中对于distance过大的点进行了筛选处理，保留比较好的点。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">FlannBasedMatcher matcher;</span><br><span class="line"><span class="comment">//BFMatcher matcher;</span></span><br><span class="line">vector&lt;DMatch&gt; matches;</span><br><span class="line">matcher.<span class="built_in">match</span>(descriptors_0, descriptors_1, matches);</span><br><span class="line"><span class="built_in">sort</span>(matches.<span class="built_in">begin</span>(), matches.<span class="built_in">end</span>());</span><br><span class="line"><span class="keyword">float</span> min_v = numeric_limits&lt;<span class="keyword">float</span>&gt;::<span class="built_in">max</span>();</span><br><span class="line"><span class="keyword">float</span> max_v = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; matches.<span class="built_in">size</span>(); ++i) &#123;</span><br><span class="line">    min_v = <span class="built_in">min</span>(min_v, matches[i].distance);</span><br><span class="line">    max_v = <span class="built_in">max</span>(max_v, matches[i].distance);</span><br><span class="line">&#125;</span><br><span class="line">vector&lt;Point2f&gt; ps_0, ps_1;</span><br><span class="line"><span class="comment">//assert(matches.size() &gt; 500);</span></span><br><span class="line">cout &lt;&lt; <span class="string">&quot;min_v &quot;</span> &lt;&lt; min_v &lt;&lt; endl;</span><br><span class="line">cout &lt;&lt; <span class="string">&quot;max_v &quot;</span> &lt;&lt; max_v &lt;&lt; endl;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i&lt;matches.<span class="built_in">size</span>(); ++i) &#123;</span><br><span class="line">    DMatch m = matches[i];</span><br><span class="line">    <span class="keyword">if</span> (m.distance &gt; max_v / <span class="number">2</span> )<span class="keyword">continue</span>;</span><br><span class="line">    ps_0.<span class="built_in">push_back</span>(kps_0[m.queryIdx].pt);</span><br><span class="line">    ps_1.<span class="built_in">push_back</span>(kps_1[m.trainIdx].pt);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="计算homography并计算图像扩大行列"><a href="#计算homography并计算图像扩大行列" class="headerlink" title="计算homography并计算图像扩大行列"></a>计算homography并计算图像扩大行列</h4><p>利用匹配点来计算出Homography。<br>并且利用边界点计算出拼接后的图像的大小。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">Mat rev_H = <span class="built_in">findHomography</span>(ps_1, ps_0, RANSAC);</span><br><span class="line">Mat H = <span class="built_in">findHomography</span>(ps_0, ps_1, RANSAC);</span><br><span class="line"></span><br><span class="line">cout &lt;&lt; <span class="string">&quot;begin stitcher....  &quot;</span> &lt;&lt; i &lt;&lt; endl;</span><br><span class="line"><span class="function">vector&lt;Point2f&gt; <span class="title">corners_1</span><span class="params">(<span class="number">4</span>)</span></span>;</span><br><span class="line"><span class="function">vector&lt;Point2f&gt; <span class="title">corners_2</span><span class="params">(<span class="number">4</span>)</span></span>;</span><br><span class="line">corners_1[<span class="number">0</span>] = <span class="built_in">Point2f</span>(<span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line">corners_1[<span class="number">1</span>] = <span class="built_in">Point2f</span>((<span class="keyword">float</span>)img_1.cols, <span class="number">0</span>);</span><br><span class="line">corners_1[<span class="number">2</span>] = <span class="built_in">Point2f</span>((<span class="keyword">float</span>)img_1.cols, (<span class="keyword">float</span>)img_1.rows);</span><br><span class="line">corners_1[<span class="number">3</span>] = <span class="built_in">Point2f</span>(<span class="number">0</span>, (<span class="keyword">float</span>)img_1.rows);</span><br><span class="line"></span><br><span class="line"><span class="built_in">perspectiveTransform</span>(corners_1, corners_2, H);</span><br><span class="line"><span class="keyword">int</span> down_rows = (<span class="keyword">int</span>)<span class="built_in">min</span>(corners_2[<span class="number">0</span>].y, corners_2[<span class="number">1</span>].y);</span><br><span class="line">down_rows = <span class="built_in">min</span>(<span class="number">0</span>, down_rows) * <span class="number">-1</span>;</span><br><span class="line"><span class="keyword">int</span> right_cols = (<span class="keyword">int</span>)<span class="built_in">min</span>(corners_2[<span class="number">0</span>].x, corners_2[<span class="number">3</span>].x);</span><br><span class="line">right_cols = <span class="built_in">min</span>(<span class="number">0</span>, right_cols) * <span class="number">-1</span>;</span><br></pre></td></tr></table></figure><h4 id="计算变换后的坐标并进行变换，拼接"><a href="#计算变换后的坐标并进行变换，拼接" class="headerlink" title="计算变换后的坐标并进行变换，拼接"></a>计算变换后的坐标并进行变换，拼接</h4><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">Mat stitch_img = Mat::<span class="built_in">zeros</span>(img_2.rows+down_rows, img_2.cols+right_cols, CV_8UC3);</span><br><span class="line">img_2.<span class="built_in">copyTo</span>(<span class="built_in">Mat</span>(stitch_img, <span class="built_in">Rect</span>(right_cols, down_rows, img_2.cols, img_2.rows)));</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; stitch_img.rows; ++i) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j &lt; stitch_img.cols; ++j) &#123;</span><br><span class="line">        <span class="keyword">if</span> (stitch_img.at&lt;Vec3b&gt;(i, j) != <span class="built_in">Vec3b</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>)) &#123;</span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">int</span> x0 = j - right_cols;</span><br><span class="line">        <span class="keyword">int</span> y0 = i - down_rows;</span><br><span class="line">        vector&lt;Point2f&gt; pix, dst;</span><br><span class="line">        pix.<span class="built_in">emplace_back</span>(x0, y0);</span><br><span class="line">        <span class="built_in">perspectiveTransform</span>(pix, dst, rev_H);</span><br><span class="line">        Point2f pos = dst[<span class="number">0</span>];</span><br><span class="line">        <span class="comment">//cout &lt;&lt; pos &lt;&lt; endl;</span></span><br><span class="line">        <span class="keyword">int</span> x = (<span class="keyword">int</span>)<span class="built_in">floor</span>(pos.x);</span><br><span class="line">        <span class="keyword">int</span> y = (<span class="keyword">int</span>)<span class="built_in">floor</span>(pos.y);</span><br><span class="line">        <span class="keyword">if</span> (<span class="number">0</span> &lt; y &amp;&amp; y &lt; img_1.rows &amp;&amp; <span class="number">0</span> &lt; x &amp;&amp; x &lt; img_1.cols &amp;&amp; img_1.at&lt;Vec3b&gt;(y,x) != <span class="built_in">Vec3b</span>(<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>) ) &#123;</span><br><span class="line">            Vec3b c = img_1.at&lt;Vec3b&gt;(y, x);</span><br><span class="line">            <span class="comment">//if (stitch_img.at&lt;Vec3b&gt;(i,j) != Vec3b(0, 0, 0)) &#123; c += (stitch_img.at&lt;Vec3b&gt;(i,j)-c)/2; &#125;</span></span><br><span class="line">            stitch_img.at&lt;Vec3b&gt;(i, j) = c;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">last_result = stitch_img;</span><br></pre></td></tr></table></figure><h2 id="实验结果"><a href="#实验结果" class="headerlink" title="实验结果"></a>实验结果</h2><p>对于两组图像，拼接得到的结果如下所示</p><p><img src="/assets/611478ffgy1fse7spcfnaj20tn0ae0w9.jpg"></p><p><img src="/assets/611478ffgy1fse7szhxabj21t90me14j.jpg"></p><h2 id="附"><a href="#附" class="headerlink" title="附"></a>附</h2><p>完整代码和数据：</p><p><a href="https://github.com/sleeepyy/ComputationalPhotography/tree/master/hw5/panorama">panorama</a></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;全景图拼接是利用同一场景的多张图像通过重叠部分寻找匹配关系，从而生成整个场景图像的技术。 全景图的拼接方法有很多，如按场景和运动的种类可以分为单视点全景拼接和多视点全景拼接。对于平面场景和只通过相机旋转拍摄的场景来说，可以使用求每两幅图像之间的一个Homography变换来映射到一张图像的方法，还可以使用恢复相机的旋转的方式得到最终的全景图。当相机固定只有水平方向旋转时，也可以使用柱面或球面坐标映射的方式求得全景图。&lt;/p&gt;</summary>
    
    
    
    <category term="学习笔记" scheme="https://archived.yanghan.life/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="学习笔记" scheme="https://archived.yanghan.life/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="计算摄影学" scheme="https://archived.yanghan.life/tags/%E8%AE%A1%E7%AE%97%E6%91%84%E5%BD%B1%E5%AD%A6/"/>
    
  </entry>
  
  <entry>
    <title>基本滤波器及图像傅里叶变换</title>
    <link href="https://archived.yanghan.life/2018/03/31/%E5%9F%BA%E6%9C%AC%E6%BB%A4%E6%B3%A2%E5%99%A8%E5%8F%8A%E5%9B%BE%E5%83%8F%E5%82%85%E9%87%8C%E5%8F%B6%E5%8F%98%E6%8D%A2/"/>
    <id>https://archived.yanghan.life/2018/03/31/%E5%9F%BA%E6%9C%AC%E6%BB%A4%E6%B3%A2%E5%99%A8%E5%8F%8A%E5%9B%BE%E5%83%8F%E5%82%85%E9%87%8C%E5%8F%B6%E5%8F%98%E6%8D%A2/</id>
    <published>2018-03-31T08:10:41.000Z</published>
    <updated>2022-09-06T10:32:51.734Z</updated>
    
    <content type="html"><![CDATA[<!-- # 图像滤波和傅里叶变换 --><p>去年写过手撸BMP文件头，直接操作图像文件的版本，这次因为课程作业，刚好就用OpenCV重新实现一遍。</p><span id="more"></span><h2 id="实验内容"><a href="#实验内容" class="headerlink" title="实验内容"></a>实验内容</h2><ul><li>实现盒状均值滤波</li><li>实现高斯滤波</li><li>实现中值滤波</li><li>实现简单的双边滤波</li><li>利用傅里叶变换完成图像的频域变换</li></ul><h2 id="理论及实验细节及效果"><a href="#理论及实验细节及效果" class="headerlink" title="理论及实验细节及效果"></a>理论及实验细节及效果</h2><h3 id="均值滤波"><a href="#均值滤波" class="headerlink" title="均值滤波"></a>均值滤波</h3><h4 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h4><p>均值滤波是一种线性滤波，它的卷积核在3x3的kernel中应该是：<br>$$<br>\begin{bmatrix}{1/9 ,1/9, 1/9 \1/9 ,1/9, 1/9 \1/9 ,1/9, 1/9 }\end{bmatrix}<br>$$</p><p>总之就是每个像素点滤波后的值是周围像素值的平均，即卷积核的值为$1/(w \times h)$.</p><h4 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h4><p>先是命令行参数的解析，后面每一个程序的基本结构都如下：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">char</span>** argv)</span> </span>&#123;</span><br><span class="line"><span class="keyword">if</span> (argc != <span class="number">5</span>) &#123;</span><br><span class="line">cout &lt;&lt; <span class="string">&quot;Input Illegal!&quot;</span> &lt;&lt; endl;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">char</span>* input = argv[<span class="number">1</span>];</span><br><span class="line"><span class="keyword">char</span>* output = argv[<span class="number">2</span>];</span><br><span class="line"><span class="keyword">int</span> w = <span class="built_in">atoi</span>(argv[<span class="number">3</span>]);</span><br><span class="line"><span class="keyword">int</span> h = <span class="built_in">atoi</span>(argv[<span class="number">4</span>]);</span><br><span class="line">Mat mat = <span class="built_in">imread</span>(input);</span><br><span class="line">Mat dst;</span><br><span class="line"><span class="built_in">myBoxFilter</span>(mat, dst, <span class="number">3</span>, <span class="number">3</span>);</span><br><span class="line"><span class="built_in">imwrite</span>(output, dst);</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>BoxFilter</code>实现起来很简单，只需要把卷积核生成好，然后调用filter2D进行计算就可以了。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;opencv2/opencv.hpp&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;vector&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> cv;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">myBoxFilter</span><span class="params">(Mat&amp; src, Mat&amp; dst, <span class="keyword">int</span> w, <span class="keyword">int</span> h)</span> </span>&#123;</span><br><span class="line"><span class="function">Mat <span class="title">mask</span><span class="params">(Size(h, w), CV_32FC1, Scalar(<span class="number">1.0</span> / w / h))</span></span>;</span><br><span class="line"><span class="built_in">filter2D</span>(src, dst, src.<span class="built_in">depth</span>(), mask);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="结果"><a href="#结果" class="headerlink" title="结果"></a>结果</h4><p>原图：</p><p><img src="/assets/611478ffgy1fpw2iogo59j2074074dif.jpg"></p><p>用3x3的均值滤波后的结果：</p><p><img src="/assets/611478ffgy1fpw2jvac0qj2074074t91.jpg"></p><h3 id="高斯滤波"><a href="#高斯滤波" class="headerlink" title="高斯滤波"></a>高斯滤波</h3><h4 id="原理-1"><a href="#原理-1" class="headerlink" title="原理"></a>原理</h4><p>通常我们认为图像像素之间的相关性随着距离增加应该不断减弱。 均值滤波不能体现这一性质。在对图像进行均值滤波时，如果图像中有一些很显著的亮点，滤波后它的周围会形成光斑。这正是因为均值滤波无视了距离，对很远处的像素依旧采用同样的权重导致的。</p><p>因此，采用高斯卷积核，可以用距离来控制周围像素对中心的影响。</p><p>高斯卷积核理论上使无穷大的，但是距离远的点对于中心的影响比较小，同时为了方便计算，只取高斯核的中心部分进行卷积运算。</p><p>二维的高斯分布公式为：<br>$$<br>G(x,y) = \frac{1}{2\pi\sigma^2}e^{-\frac{x^2+y^2}{2\sigma^2}}<br>$$<br>可以由此公式构建高斯卷积核。</p><h4 id="实现-1"><a href="#实现-1" class="headerlink" title="实现"></a>实现</h4><p>先实现一个计算高斯分布的函数，此处x2相当于$x^2+y^2$</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">double</span> <span class="title">G</span><span class="params">(<span class="keyword">double</span> x2, <span class="keyword">double</span>&amp; sigma)</span> </span>&#123;</span><br><span class="line"><span class="keyword">return</span> (<span class="number">1.0</span> / <span class="number">2</span> / PI / sigma) * <span class="built_in">exp</span>(<span class="number">-1.0</span>*(x2) / (<span class="number">2.0</span>*sigma*sigma));</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>然后计算出高斯核中每个元素的值，并进行归一化。</p><p>此处默认取高斯核大小为5。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">myGaussianFilter</span><span class="params">(Mat&amp; src, Mat&amp; dst, <span class="keyword">double</span> sigma)</span> </span>&#123;</span><br><span class="line"><span class="keyword">int</span> size = <span class="number">5</span>;</span><br><span class="line"><span class="keyword">int</span> mid = size / <span class="number">2</span>;</span><br><span class="line"><span class="function">Mat <span class="title">mask</span><span class="params">(Size(size, size), CV_32FC1)</span></span>;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; size; ++i) &#123;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j &lt; size; ++j) &#123;</span><br><span class="line">mask.at&lt;<span class="keyword">float</span>&gt;(i, j) = (<span class="keyword">float</span>)<span class="built_in">G</span>(<span class="built_in">pow</span>(i - mid, <span class="number">2</span>) + <span class="built_in">pow</span>(j - mid, <span class="number">2</span>), sigma);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">double</span> s = <span class="built_in">sum</span>(mask)[<span class="number">0</span>];</span><br><span class="line">mask = mask / s;</span><br><span class="line"><span class="built_in">filter2D</span>(src, dst, src.<span class="built_in">depth</span>(), mask);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="结果-1"><a href="#结果-1" class="headerlink" title="结果"></a>结果</h4><p>原图：</p><p><img src="/assets/611478ffgy1fpw2iogo59j2074074dif.jpg"></p><p>取sigma=10时的结果：</p><p><img src="/assets/611478ffgy1fpw2knqhd6j2074074wep.jpg"></p><h3 id="中值滤波"><a href="#中值滤波" class="headerlink" title="中值滤波"></a>中值滤波</h3><h4 id="原理-2"><a href="#原理-2" class="headerlink" title="原理"></a>原理</h4><p>中值滤波是一种基于统计的非线性滤波。它取周围像素的种植作为当前的像素的值，可以有效地抵抗椒盐噪声的影响。</p><h4 id="实现-2"><a href="#实现-2" class="headerlink" title="实现"></a>实现</h4><p>因为属于非线性滤波，所以无法使用统一的线性卷积核来运算。</p><p>对于每一个点，都要取周围的点来排序取中值。</p><p>这里，我对于边缘的点采取的处理是仅取卷积核和图像重叠的部分参与运算。</p><p>对于每个点的操作是如下代码：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">medianProcess</span><span class="params">(Mat&amp; src, <span class="keyword">int</span> y, <span class="keyword">int</span> x, <span class="keyword">int</span> w, <span class="keyword">int</span> h, <span class="keyword">int</span> ch)</span> </span>&#123;</span><br><span class="line">vector&lt;<span class="keyword">int</span>&gt; pixels;</span><br><span class="line"><span class="keyword">int</span> mid_w = w / <span class="number">2</span>;</span><br><span class="line"><span class="keyword">int</span> mid_h = h / <span class="number">2</span>;</span><br><span class="line">Size size = src.<span class="built_in">size</span>();</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = y - mid_h; i &lt;= y + mid_h; ++i) &#123;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> j = x - mid_w; j &lt;= x + mid_w; ++j) &#123;</span><br><span class="line"><span class="keyword">if</span> (i &gt;= <span class="number">0</span> &amp;&amp; i &lt; size.height &amp;&amp; j &gt;= <span class="number">0</span> &amp;&amp; j &lt; size.width) &#123;</span><br><span class="line">pixels.<span class="built_in">push_back</span>(src.at&lt;Vec3b&gt;(i, j)[ch]);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span> &#123;</span><br><span class="line"><span class="keyword">continue</span>;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">sort</span>(pixels.<span class="built_in">begin</span>(), pixels.<span class="built_in">end</span>());</span><br><span class="line"><span class="keyword">return</span> pixels.<span class="built_in">at</span>(pixels.<span class="built_in">size</span>() / <span class="number">2</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后，单独运算每个点的像素值就可以了。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">myMedianFilter</span><span class="params">(Mat&amp; src, Mat&amp; dst, <span class="keyword">int</span> w, <span class="keyword">int</span> h)</span> </span>&#123;</span><br><span class="line">Size size = src.<span class="built_in">size</span>();</span><br><span class="line">dst.<span class="built_in">create</span>(size, src.<span class="built_in">type</span>());</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; size.height; ++i) &#123;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j &lt; size.width; ++j) &#123;</span><br><span class="line">dst.at&lt;Vec3b&gt;(i, j)[<span class="number">0</span>] = <span class="built_in">medianProcess</span>(src, i, j, w, h, <span class="number">0</span>);</span><br><span class="line">dst.at&lt;Vec3b&gt;(i, j)[<span class="number">1</span>] = <span class="built_in">medianProcess</span>(src, i, j, w, h, <span class="number">1</span>);</span><br><span class="line">dst.at&lt;Vec3b&gt;(i, j)[<span class="number">2</span>] = <span class="built_in">medianProcess</span>(src, i, j, w, h, <span class="number">2</span>);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="效果"><a href="#效果" class="headerlink" title="效果"></a>效果</h4><p>原图</p><p><img src="/assets/611478ffgy1fpw2iogo59j2074074dif.jpg"></p><p>取w=3,h=3的中值滤波结果：</p><p><img src="/assets/611478ffgy1fpw2ky63qbj2074074gm1.jpg"></p><h3 id="双边滤波"><a href="#双边滤波" class="headerlink" title="双边滤波"></a>双边滤波</h3><h4 id="原理-3"><a href="#原理-3" class="headerlink" title="原理"></a>原理</h4><p>高斯滤波可以平滑图像，但是会模糊边界。为了可以保边，可以使用双边滤波。</p><p>在高斯滤波中，像素之间的欧式距离影响了其对于中心像素的影响。</p><p>在双边滤波中，不仅仅考虑欧式距离，还考虑像素值之间的距离的影响。对于像素值相差较远的点，认为此处是边界，所以其对于中心像素的影响较小。</p><p>将两个影响因素相乘，就是该像素对于中心像素的影响。最后进行归一化即可。</p><h4 id="实现-3"><a href="#实现-3" class="headerlink" title="实现"></a>实现</h4><p>对于每个点的操作如下：</p><p>对于每个点的周围mask范围，计算欧式距离，带入G函数，计算像素距离，带入G函数，作为该点的mask，进行卷积运算。</p><p>运算完后做一下归一化，保证影响的factor加起来等于1，使图片不会过亮或者过暗。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">double</span> <span class="title">bilateralProcess</span><span class="params">(Mat&amp; src, <span class="keyword">int</span> y, <span class="keyword">int</span> x, <span class="keyword">int</span> kernel_size, <span class="keyword">int</span> ch, <span class="keyword">double</span> sigma_s, <span class="keyword">double</span> sigma_r)</span> </span>&#123;</span><br><span class="line">Mat mask = <span class="built_in">Mat</span>(<span class="built_in">Size</span>(kernel_size, kernel_size), CV_32FC1);</span><br><span class="line">Size size = src.<span class="built_in">size</span>();</span><br><span class="line"><span class="keyword">double</span> result = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">double</span> factor = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">double</span> total_factor = <span class="number">0</span>;</span><br><span class="line">Vec3b color = src.at&lt;Vec3b&gt;(y, x);</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = y - kernel_size / <span class="number">2</span>; i &lt;= y + kernel_size / <span class="number">2</span>; ++i) &#123;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> j = x - kernel_size / <span class="number">2</span>; j &lt;= x + kernel_size / <span class="number">2</span>; ++j) &#123;</span><br><span class="line"><span class="keyword">if</span> (i &gt;= <span class="number">0</span> &amp;&amp; i &lt; src.rows &amp;&amp; j &gt;= <span class="number">0</span> &amp;&amp; j &lt; src.cols) &#123;</span><br><span class="line">factor = <span class="built_in">G</span>(<span class="built_in">pow</span>(i - y, <span class="number">2</span>) + <span class="built_in">pow</span>(j - x, <span class="number">2</span>), sigma_s) * <span class="built_in">G</span>(<span class="built_in">pow</span>(src.at&lt;Vec3b&gt;(i, j)[ch] - color[ch], <span class="number">2</span>), sigma_r);</span><br><span class="line">result += factor * src.at&lt;Vec3b&gt;(i, j)[ch];</span><br><span class="line">total_factor += factor;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span> &#123;</span><br><span class="line"><span class="keyword">continue</span>;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> result / total_factor;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>每个点的运算函数写好后，只要对于每个点进行运算即可：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">myBilateralFilter</span><span class="params">(Mat&amp; src, Mat&amp; dst, <span class="keyword">int</span> kernel_size, <span class="keyword">double</span> sigma_s, <span class="keyword">double</span> sigma_r)</span> </span>&#123;</span><br><span class="line">dst.<span class="built_in">create</span>(src.<span class="built_in">size</span>(), src.<span class="built_in">type</span>());</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; src.rows; i++)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j &lt; src.cols; j++)</span><br><span class="line">&#123;</span><br><span class="line">dst.at&lt;Vec3b&gt;(i, j)[<span class="number">0</span>] = (<span class="keyword">char</span>)<span class="built_in">bilateralProcess</span>(src, i, j, kernel_size, <span class="number">0</span>, sigma_s, sigma_r);</span><br><span class="line">dst.at&lt;Vec3b&gt;(i, j)[<span class="number">1</span>] = (<span class="keyword">char</span>)<span class="built_in">bilateralProcess</span>(src, i, j, kernel_size, <span class="number">1</span>, sigma_s, sigma_r);</span><br><span class="line">dst.at&lt;Vec3b&gt;(i, j)[<span class="number">2</span>] = (<span class="keyword">char</span>)<span class="built_in">bilateralProcess</span>(src, i, j, kernel_size, <span class="number">2</span>, sigma_s, sigma_r);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="结果-2"><a href="#结果-2" class="headerlink" title="结果"></a>结果</h4><p>原图：</p><p><img src="/assets/611478ffgy1fpw2iogo59j2074074dif.jpg"></p><p>取sigma_s = 10, sigma_r = 10的结果：</p><p><img src="/assets/611478ffgy1fpw2jeviw5j2074074jrv.jpg"></p><h3 id="傅里叶变换完成图像的频域变换"><a href="#傅里叶变换完成图像的频域变换" class="headerlink" title="傅里叶变换完成图像的频域变换"></a>傅里叶变换完成图像的频域变换</h3><h4 id="原理-4"><a href="#原理-4" class="headerlink" title="原理"></a>原理</h4><p>图像的存储是在空间域上的，可以通过傅里叶将其转换到频域并进行可视化。</p><h4 id="实现-4"><a href="#实现-4" class="headerlink" title="实现"></a>实现</h4><p>先把图像扩展到DFT效率最高的尺寸上，</p><p>然后用一个两通道的图像矩阵作为dft的结果。</p><p>然后调整dft结果的位置.</p><p>最后取对数，normalize之后可视化。</p><p>比较需要注意的是读入和输出的时候图像的type的转换。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">myDFT</span><span class="params">(Mat&amp; src, Mat&amp; dst)</span> </span>&#123;</span><br><span class="line"><span class="keyword">int</span> m = <span class="built_in">getOptimalDFTSize</span>(src.rows);</span><br><span class="line"><span class="keyword">int</span> n = <span class="built_in">getOptimalDFTSize</span>(src.cols);</span><br><span class="line">Mat padded;</span><br><span class="line"><span class="built_in">copyMakeBorder</span>(src, padded, <span class="number">0</span>, m - src.rows, <span class="number">0</span>, n - src.rows, BORDER_CONSTANT, Scalar::<span class="built_in">all</span>(<span class="number">0</span>));</span><br><span class="line"></span><br><span class="line">Mat planes[] = &#123; Mat_&lt;<span class="keyword">float</span>&gt;(padded), Mat::<span class="built_in">zeros</span>(padded.<span class="built_in">size</span>(),CV_32F) &#125;;</span><br><span class="line">Mat complexMat;</span><br><span class="line"><span class="built_in">merge</span>(planes, <span class="number">2</span>, complexMat);</span><br><span class="line"></span><br><span class="line"><span class="built_in">dft</span>(complexMat, complexMat, DFT_COMPLEX_OUTPUT);</span><br><span class="line"></span><br><span class="line"><span class="built_in">fftshift</span>(complexMat, complexMat);</span><br><span class="line"></span><br><span class="line"><span class="built_in">split</span>(complexMat, planes);</span><br><span class="line"><span class="built_in">magnitude</span>(planes[<span class="number">0</span>], planes[<span class="number">1</span>], planes[<span class="number">0</span>]);</span><br><span class="line">Mat mag = planes[<span class="number">0</span>];</span><br><span class="line">mag += Scalar::<span class="built_in">all</span>(<span class="number">1</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">log</span>(mag, mag);</span><br><span class="line"></span><br><span class="line"><span class="built_in">normalize</span>(mag, mag, <span class="number">0</span>, <span class="number">1</span>, CV_MINMAX);</span><br><span class="line">mag.<span class="built_in">convertTo</span>(dst, CV_8U, <span class="number">255</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>调整dft结果位置的代码给出：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">fftshift</span><span class="params">(<span class="keyword">const</span> Mat &amp;src, Mat &amp;dst)</span> </span>&#123;</span><br><span class="line">dst.<span class="built_in">create</span>(src.<span class="built_in">size</span>(), src.<span class="built_in">type</span>());</span><br><span class="line"><span class="keyword">int</span> rows = src.rows, cols = src.cols;</span><br><span class="line">Rect roiTopBand, roiBottomBand, roiLeftBand, roiRightBand;</span><br><span class="line"><span class="keyword">if</span> (rows % <span class="number">2</span> == <span class="number">0</span>) &#123;</span><br><span class="line">roiTopBand = <span class="built_in">Rect</span>(<span class="number">0</span>, <span class="number">0</span>, cols, rows / <span class="number">2</span>);</span><br><span class="line">roiBottomBand = <span class="built_in">Rect</span>(<span class="number">0</span>, rows / <span class="number">2</span>, cols, rows / <span class="number">2</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span> &#123;</span><br><span class="line">roiTopBand = <span class="built_in">Rect</span>(<span class="number">0</span>, <span class="number">0</span>, cols, rows / <span class="number">2</span> + <span class="number">1</span>);</span><br><span class="line">roiBottomBand = <span class="built_in">Rect</span>(<span class="number">0</span>, rows / <span class="number">2</span> + <span class="number">1</span>, cols, rows / <span class="number">2</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> (cols % <span class="number">2</span> == <span class="number">0</span>) &#123;</span><br><span class="line">roiLeftBand = <span class="built_in">Rect</span>(<span class="number">0</span>, <span class="number">0</span>, cols / <span class="number">2</span>, rows);</span><br><span class="line">roiRightBand = <span class="built_in">Rect</span>(cols / <span class="number">2</span>, <span class="number">0</span>, cols / <span class="number">2</span>, rows);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span> &#123;</span><br><span class="line">roiLeftBand = <span class="built_in">Rect</span>(<span class="number">0</span>, <span class="number">0</span>, cols / <span class="number">2</span> + <span class="number">1</span>, rows);</span><br><span class="line">roiRightBand = <span class="built_in">Rect</span>(cols / <span class="number">2</span> + <span class="number">1</span>, <span class="number">0</span>, cols / <span class="number">2</span>, rows);</span><br><span class="line">&#125;</span><br><span class="line">Mat srcTopBand = <span class="built_in">src</span>(roiTopBand);</span><br><span class="line">Mat dstTopBand = <span class="built_in">dst</span>(roiTopBand);</span><br><span class="line">Mat srcBottomBand = <span class="built_in">src</span>(roiBottomBand);</span><br><span class="line">Mat dstBottomBand = <span class="built_in">dst</span>(roiBottomBand);</span><br><span class="line">Mat srcLeftBand = <span class="built_in">src</span>(roiLeftBand);</span><br><span class="line">Mat dstLeftBand = <span class="built_in">dst</span>(roiLeftBand);</span><br><span class="line">Mat srcRightBand = <span class="built_in">src</span>(roiRightBand);</span><br><span class="line">Mat dstRightBand = <span class="built_in">dst</span>(roiRightBand);</span><br><span class="line"><span class="built_in">flip</span>(srcTopBand, dstTopBand, <span class="number">0</span>);</span><br><span class="line"><span class="built_in">flip</span>(srcBottomBand, dstBottomBand, <span class="number">0</span>);</span><br><span class="line"><span class="built_in">flip</span>(dst, dst, <span class="number">0</span>);</span><br><span class="line"><span class="built_in">flip</span>(srcLeftBand, dstLeftBand, <span class="number">1</span>);</span><br><span class="line"><span class="built_in">flip</span>(srcRightBand, dstRightBand, <span class="number">1</span>);</span><br><span class="line"><span class="built_in">flip</span>(dst, dst, <span class="number">1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="效果-1"><a href="#效果-1" class="headerlink" title="效果"></a>效果</h4><p>原图：</p><p><img src="/assets/611478ffgy1fpw2iogo59j2074074dif.jpg"></p><p>结果：</p><p><img src="/assets/611478ffgy1fpw2kahpf5j20740743z6.jpg"></p>]]></content>
    
    
    <summary type="html">&lt;!-- # 图像滤波和傅里叶变换 --&gt;

&lt;p&gt;去年写过手撸BMP文件头，直接操作图像文件的版本，这次因为课程作业，刚好就用OpenCV重新实现一遍。&lt;/p&gt;</summary>
    
    
    
    <category term="学习笔记" scheme="https://archived.yanghan.life/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="学习笔记" scheme="https://archived.yanghan.life/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="计算摄影学" scheme="https://archived.yanghan.life/tags/%E8%AE%A1%E7%AE%97%E6%91%84%E5%BD%B1%E5%AD%A6/"/>
    
  </entry>
  
  <entry>
    <title>Principle of Programming Language 复习笔记</title>
    <link href="https://archived.yanghan.life/2018/01/22/Principle-of-Programming-Language-%E5%A4%8D%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <id>https://archived.yanghan.life/2018/01/22/Principle-of-Programming-Language-%E5%A4%8D%E4%B9%A0%E7%AC%94%E8%AE%B0/</id>
    <published>2018-01-22T12:11:36.000Z</published>
    <updated>2022-09-06T10:21:31.940Z</updated>
    
    <content type="html"><![CDATA[<!-- Principle of Programming Language 知识点 --><span id="more"></span><p>PPL知识点图：</p><p><img src="/assets/611478ffgy1fnpn63lqpnj21f171ie81.jpg" alt="principle of programming language"></p><h2 id="Intro"><a href="#Intro" class="headerlink" title="Intro"></a>Intro</h2><p>Imperative</p><p>Functional</p><p>Logic</p><h2 id="History"><a href="#History" class="headerlink" title="History"></a>History</h2><ul><li><p>Lexical analysis: converts characters in the source program into lexical unit</p></li><li><p>Syntax analysis: transforms lexical units into parse trees which represent syntactic structure of program</p></li><li><p>Semantics analysis: generate intermediate code</p></li><li><p>Code generation: machine code is generated</p></li><li><p>Link and load</p></li></ul><p>Most important criteria for evaluating programming languages include:<br>   Readability, writability, reliability, cost<br>Major influences on language design have been application domains, machine architecture and software development methodologies<br>The major methods of implementing programming languages are: compilation, pure interpretation, and hybrid implementation</p><h2 id="Syntax"><a href="#Syntax" class="headerlink" title="Syntax"></a>Syntax</h2><p>Syntax: 语法 the form or structure of the expressions, statements, and program units</p><p>Semantics: 语义 the meaning of the expressions, statements, and program units</p><p>​ What programs do, their behavior and meaning</p><h2 id="Subprogram"><a href="#Subprogram" class="headerlink" title="Subprogram"></a>Subprogram</h2><p>In-Out Mode</p><h2 id="JVM"><a href="#JVM" class="headerlink" title="JVM"></a>JVM</h2><p>JAVA 的 JVM 的内存可分为 3 个区：堆 (heap)、栈 (stack) 和 方法区 (method)<br>堆区:</p><ol><li><p>存储的全部是对象，每个对象都包含一个与之对应的 class 的信息。(class 的目的是得到操作指令)</p></li><li><p>jvm 只有一个堆区 (heap) 被所有线程共享，堆中不存放基本类型和对象引用，只存放对象本身</p></li></ol><p>栈区:</p><ol><li><p>每个线程包含一个栈区，栈中只保存基础数据类型的对象和自定义对象的引用 (不是对象)，对象都存放在堆区中</p></li><li><p>每个栈中的数据 (原始类型和对象引用) 都是私有的，其他栈不能访问。</p></li><li><p>栈分为 3 个部分：基本类型变量区、执行环境上下文、操作指令区 (存放操作指令)。</p></li></ol><p>方法区:</p><ol><li>又叫静态区，跟堆一样，被所有的线程共享。方法区包含所有的 class 和 static 变量。</li><li>方法区中包含的都是在整个程序中永远唯一的元素，如 class，static 变量。</li></ol><h2 id="Parallelism"><a href="#Parallelism" class="headerlink" title="Parallelism"></a>Parallelism</h2><ul><li>并发：一个程序的多个任务同时执行</li><li>并行：一个任务分解为多个子任务同时执行，协作完成一个问题</li><li>分布式：并行的计算在不同的计算机上进行</li></ul><p>加速比：在p个核上的程序的加速比S=T串行/T并行</p><p>Amdahl’s Law</p><ul><li><p>speedup &lt;= work/span</p></li><li><p>q = fraction of sequential work</p></li><li><p>speedup &lt;= 1/q</p></li></ul><p>结构并行</p><ul><li>Folk/Join 框架</li></ul><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (任务足够小)&#123;</span><br><span class="line">  直接执行该任务;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span>&#123;</span><br><span class="line">  将任务一分为二;</span><br><span class="line">  执行这两个任务并等待结果;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>函数并行</p><ul><li>Future</li><li>Memorization</li></ul><p>循环并行</p><ul><li>Forall 框架</li><li>栅栏问题</li></ul><h2 id="Recursion"><a href="#Recursion" class="headerlink" title="Recursion"></a>Recursion</h2><p>线性递归和迭代  </p><ul><li>牛顿逼近平方根</li></ul><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">sqrt</span>(<span class="params">x</span>):</span></span><br><span class="line">  threshold = <span class="number">0.0001</span></span><br><span class="line">  <span class="keyword">return</span> sqrt_iter(<span class="number">1.0</span>, x, threshold)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">sqrt_iter</span>(<span class="params">guess, x, threshold</span>):</span></span><br><span class="line">  <span class="keyword">if</span> <span class="built_in">abs</span>(guess*guess -x) &lt; threshold:</span><br><span class="line">    <span class="keyword">return</span> guess</span><br><span class="line">  <span class="keyword">else</span>:</span><br><span class="line">    <span class="keyword">return</span> sqrt_iter((guess + x/guess)/<span class="number">2</span>, x, threshold)</span><br></pre></td></tr></table></figure><ul><li>阶乘</li></ul>]]></content>
    
    
    <summary type="html">&lt;!-- Principle of Programming Language 知识点 --&gt;</summary>
    
    
    
    <category term="学习笔记" scheme="https://archived.yanghan.life/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="学习笔记" scheme="https://archived.yanghan.life/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="PL" scheme="https://archived.yanghan.life/tags/PL/"/>
    
    <category term="复习笔记" scheme="https://archived.yanghan.life/tags/%E5%A4%8D%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
  </entry>
  
  <entry>
    <title>操作系统 复习笔记</title>
    <link href="https://archived.yanghan.life/2018/01/21/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-%E5%A4%8D%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <id>https://archived.yanghan.life/2018/01/21/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-%E5%A4%8D%E4%B9%A0%E7%AC%94%E8%AE%B0/</id>
    <published>2018-01-21T07:29:54.000Z</published>
    <updated>2022-09-06T10:12:13.483Z</updated>
    
    <content type="html"><![CDATA[<!-- # 操作系统 复习笔记 --><span id="more"></span><h2 id="Intro"><a href="#Intro" class="headerlink" title="Intro"></a>Intro</h2><p>系统提供的接口有二类：<br>命令级接口，它提供键盘或鼠标等命令。<br>程序级接口，它提供一组系统调用System calls ，即操作系统服务，供用户程序和其它程序调用。</p><p>OS is a resource allocator</p><ul><li>Manages all resources</li><li>Decides between conflicting requests for efficient and fair resource use</li></ul><p>OS is a control program</p><ul><li>Controls execution of programs to prevent errors and improper use of the computer</li></ul><p>bootstrap program is loaded at power-up or reboot</p><ul><li>Typically stored in ROM or EEPROM, generally known as firmware</li><li>Initializates all aspects of system</li><li>Loads operating system kernel and starts execution</li></ul><h2 id="Operating-System-Structures"><a href="#Operating-System-Structures" class="headerlink" title="Operating System Structures"></a>Operating System Structures</h2><p>System Calls ：Programming interface to the services provided by the OS<br>System calls are the programming interface between processes and the OS kernel.</p><p>Why use API’s rather than system calls?( exam of my system programming)</p><ol><li><p>System calls differ from platform to platform. By using a stable API, it is easier to migrate your software to different platforms.</p></li><li><p>The operating system may provide newer versions of a system call with enhanced features. The API implementation will typically also be upgraded to provide this support, so if you call the API, you’ll get it. If you make the system call directly, you won’t. (For example, code that called the Linux pthreads API for mutexes got the benefit of futexes without adding a single line of code. Had you called the system directly, that would not have happened.)</p></li><li><p>The API usually provides more useful functionality than the system call directly. If you make the system call directly, you’ll typically have to replicate the pre-call and post-call code that’s already implemented by the API. (For example the ‘fork’ API includes tons of code beyond just making the ‘fork’ system call. So does ‘select’.)</p></li><li><p>The API can support multiple versions of the operating system and detect which version it needs to use at run time. If you call the system directly, you either need to replicate this code or you can only support limited versions.</p></li></ol><p>WINDOWS 启动</p><ol><li>ROM中POST（Power On Self-Test）代码</li><li>BIOS/EFI（Extended Firmware Interfacte）</li><li>MBR(Main Boot Record)</li><li>引导扇区(Boot sector)</li><li>NTLDR/WinLoad</li><li>NTOSKRNL/HAL/BOOTVID/KDCOM</li><li>SMSS.EXE</li><li>WinLogon.EXE</li></ol><h2 id="Process"><a href="#Process" class="headerlink" title="Process"></a>Process</h2><p>进程是什么？</p><ul><li><p>一个具有一定独立功能的程序在一个数据集合上的一次动态执行过程。</p></li><li><p>正在执行中的程序 a program in execution</p></li></ul><p>A process includes:</p><ul><li>Program counter (PC)</li><li>Registers</li><li>Data section (global data)</li><li>Stack (temporary data)</li><li>Heap (dynamically allocated memory</li></ul><p>As a process executes, it changes state</p><ul><li>New（新）: The process is being created.</li><li>Running（运行、执行）: Instructions are being executed.</li><li>Ready（就绪）: The process is waiting to be assigned to a processor (CPU).</li><li>Waiting（等待、blocked阻塞）: The process is waiting for some event to occur.</li><li>Terminated（终止）: The process has finished execution.</li></ul><p><img src="/assets/611478ffgy1fno9i60uqpj20s40c6wj7.jpg" alt="state transition"></p><p><img src="/assets/611478ffgy1fno9igry7ij20v00m6q6j.jpg" alt="what makes up a process"></p><ul><li>Process state</li><li>Program counter</li><li>CPU registers</li><li>CPU scheduling information</li><li>Memory-management information</li><li>Accounting information</li><li>File management</li><li>I/O status information</li></ul><p>3.2.2 Scheduling Queues</p><ul><li>Job queue – set of all processes in the system.</li><li>Ready queue – set of all processes residing in main memory, ready and waiting to execute.</li><li>Device queues – set of processes waiting for an I/O device.</li><li>Process migration between the various queues.</li></ul><p>3.2.3 Context Switch（上下文切换）</p><ul><li>When CPU switches to another process, the system must save the state of the old process and load the saved state for the new process via a context switch</li><li>Context of a process represented in the PCB</li><li>Context-switch time is overhead; the system does no useful work while switching</li><li>Time dependent on hardware support</li></ul><p>3.3.1 Process Creation进程创建</p><ul><li><p>Parent process create children processes, which, in turn create other processes, forming a tree of processes.</p></li><li><p>Generally, process identified and managed via a process identifier (pid)  </p></li></ul><p>Resource sharing：</p><ul><li><p>Parent and children share all resources.</p></li><li><p>Children share subset of parent’s resources.</p></li><li><p>Parent and child share no resources.</p></li><li><p>fork system call creates new process</p><ul><li>int pid = fork();</li><li>从系统调用 fork 中返回时，两个进程除了返回值 pid 不同外，具有完全一样的用户级上下文。在子进程中，pid 的值为0;父进程中， pid 的值为子进程的进程号。</li></ul></li><li><p>exec system call used after a fork to replace the process’ memory space with a new program</p></li></ul><p>Producer-Consumer Problem</p><ul><li>unbounded-buffer places no practical limit on the size of the buffer.</li><li>bounded-buffer assumes that there is a fixed buffer size.</li></ul><h2 id="Threads"><a href="#Threads" class="headerlink" title="Threads"></a>Threads</h2><p>The concept of a process as embodying two characteristics :</p><ul><li>Unit of Resource ownership （资源拥有单位）- process is allocated a virtual address space to hold the process image</li><li>Unit of Dispatching （调度单位）- process is an execution path through one or more programs<ul><li>execution may be interleaved with other processes</li></ul></li></ul><p>A thread (or lightweight process) is a basic unit of CPU utilization; it consists of:</p><ul><li>a thread ID</li><li>program counter</li><li>register set</li><li>stack space</li></ul><p>Has an execution state (running, ready, etc.)；Saves thread context when not running；Has an execution stack；Has some per-thread static； storage for local variables；Has access to the memory and resources of its process，all threads of a process share this。</p><p><img src="/assets/611478ffgy1fno9ium201j20s00hcq6r.jpg" alt="multi thread"></p><p>A thread shares with threads belonging to the same process its:</p><ul><li>code section</li><li>data section</li><li>operating-system resources</li></ul><p>(Process Have a virtual address space which holds the process image Protected access to processors, other processes, files, and I/O resources)</p><p>User Threads（用户级线程）</p><p>Thread management done by user-level threads library</p><ul><li>用户线程的维护由应用进程完成；</li><li>内核不了解用户线程的存在；</li><li>用户线程切换不需要内核特权；</li><li>用户线程调度算法可针对应用优化；</li><li>一个线程发起系统调用而阻塞，则整个进程在等待。</li></ul><p>Three primary thread libraries:</p><ul><li><p>POSIX Pthreads</p></li><li><p>Win32 threads</p></li><li><p>Java threads</p></li></ul><p>Kernel Threads （内核级线程）<br>Supported by the Kernel</p><ul><li>内核维护进程和线程的上下文信息；</li><li>线程切换由内核完成；</li><li>时间片分配给线程，所以多线程的进程获得更多CPU时间；</li><li>一个线程发起系统调用而阻塞，不会影响其他线程的运行。</li></ul><p>Examples</p><ul><li>Windows XP/2000 及以后</li><li>Solaris</li><li>Linux</li><li>POSIX Pthreads</li><li>Mac OS X</li></ul><h2 id="CPU-scheduling"><a href="#CPU-scheduling" class="headerlink" title="CPU scheduling"></a>CPU scheduling</h2><ul><li>Turnaround time周转时间 =完成时间-提交时间</li><li>Average Turnaround time平均周转时间=Σ周转时间/进程数</li><li>Response time响应时间：从进程提出请求到首次被响应（而不是输出结果）的时间段（在分时系统环境下）</li><li>Waiting time等待时间 – 进程在就绪队列中等待的时间总和</li><li>Throughput(吞吐量) – # of processes that complete their execution per time unit</li></ul><p>first-come, first served (FCFS)<br>shortest job first (SJF)</p><ul><li>provably optimal, but difficult to know CPU burst</li></ul><p>Highest Response Ratio Next ( HRRN,最高响应比优先)</p><ul><li><p>响应比R = (等待时间 + 要求执行时间) / 要求执行时间</p></li><li><p>是FCFS和SJF的折衷</p></li></ul><p>general priority scheduling</p><ul><li>starvation, and aging</li></ul><p>round-robin (RR)</p><ul><li>for time-sharing, interactive system</li><li>problem: how to select the time quantum?</li></ul><p>Multilevel queue</p><ul><li>different algorithms for different classes of processes</li></ul><p>Multilevel feedback queue</p><ul><li>allow process to move from one (ready) queue to another</li></ul><h2 id="Process-Synchronization"><a href="#Process-Synchronization" class="headerlink" title="Process Synchronization"></a>Process Synchronization</h2><p>Critical-Section Problem</p><ul><li><p>Each process has a code segment, called critical section（临界区）, in which the shared data is accessed.</p></li><li><p>Problem – ensure that when one process is executing in its critical section, no other process is allowed to execute in its critical section.</p></li></ul><p>// TODO</p><h2 id="Deadlock"><a href="#Deadlock" class="headerlink" title="Deadlock"></a>Deadlock</h2><p>4个必要条件</p><ul><li><p>Mutual exclusion（互斥）: only one process at a time can use a resource.</p></li><li><p>Hold and wait（占有并等待、请求和保持） : a process holding at least one resource is waiting to acquire additional resources held by other processes.请求和保持(Hold and wait)条件：进程已经保持了至少一个资源，但又提出了新的资源要求，而该资源又已被其它进程占有，此时请求进程阻塞，但又对已经获得的其它资源保持不放</p></li><li><p>No preemption（不可抢占、不剥夺） : a resource can be released only voluntarily by the process holding it, after that process has completed its task.</p></li><li><p>Circular wait（循环等待）: there exists a set {P0, P1, …, Pn} of waiting processes such that P0 is waiting for a resource that is held by P1, P1 is waiting for a resource that is held by P2, …, Pn–1 is waiting for a resource that is held by Pn, and Pn is waiting for a resource that is held by P0.</p></li></ul><p>Resource-Allocation Graph</p><p>请求边 分配边</p><p>Safety State</p><p>Avoidance algorithms<br>Single instance of a resource type</p><ul><li>Use a resource-allocation graph</li></ul><p>Multiple instances of a resource type</p><ul><li>Use the banker’s algorithm</li></ul><p>Resource-Allocation Graph Algorithm</p><ul><li>算法：假设进程Pi申请资源Rj。只有在需求边Pi &gt; Rj 变成分配边 Rj &gt; Pi 而不会导致资源分配图形成环时，才允许申请。</li><li>用算法循环检测，如果没有环存在，那么资源分配会使系统处于安全状态。如果存在环，资源分配会使系统不安全。进程Pi必须等待。</li></ul><p>Detection:</p><p>wait-for graph</p><p>Banker  Algorithm</p><h2 id="Main-Memory"><a href="#Main-Memory" class="headerlink" title="Main Memory"></a>Main Memory</h2><p>Contiguous Allocation</p><ul><li>fixed partitioning</li><li>dynamic partition</li></ul><p>Paging</p><p>Page table is kept in main memory</p><ul><li>Page-table base register (PTBR) points to the page table， x86: cr3</li><li>Page-table length register (PRLR) indicates size of the page table</li><li>In this scheme every data/instruction access requires two memory accesses. One for the page table and one for the data/instruction.</li><li>The two memory access problem can be solved by the use of a special fast-lookup hardware cache called associative memory or translation look-aside buffers (TLBs) (联想寄存器、快表）</li><li>Some TLBs store address-space identifiers (ASIDs) in each TLB entry – uniquely identifies each process to provide address-space protection for that process</li></ul><p>Effective Access Time (EAT)<br>EAT = (t+e) a + ( t + t + e) (1 – a)</p><h2 id="Virtual-Memory"><a href="#Virtual-Memory" class="headerlink" title="Virtual Memory"></a>Virtual Memory</h2><p>局部性原理(principle of locality)：指程序在执行过程中的一个较短时期，所执行的指令地址和指令的操作数地址，分别局限于一定区域。表现为：</p><ul><li>时间局部性：一条指令的一次执行和下次执行，一个数据的一次访问和下次访问都集中在一个较短时期内；</li><li>空间局部性：当前指令和邻近的几条指令，当前访问的数据和邻近的数据都集中在一个较小区域内。</li></ul><p>虚拟存储器是具有请求调入功能和置换功能，能仅把进程的一部分装入内存便可运行进程的存储管理系统，它能从逻辑上对内存容量进行扩充的一种虚拟的存储器系统</p><p>The effective memory-access time is<br>(1 – p) x physical-memory-access + p x ( page-fault-overhead + swap-page-out + swap-page-in + restart-overhead )</p><p>Page Replacement Algorithms</p><ul><li>First-In-First-Out Algorithm (FIFO，先进先出算法)</li><li>Optimal Algorithm （OPT 最佳页面置换算法）</li><li>Least Recently Used (LRU) Algorithm (最近最久使用算法)</li><li>LRU Approximation Algorithms （近似LRU算法） ：</li><li>Additional-Reference-Bits Algorithm</li><li>Second-Chance（clock） Algorithm</li><li>Enhanced Second-Chance Algorithm</li><li>Counting-Base Page Replacement：</li><li>Least Frequently Used Algorithm (LFU最不经常使用算法）</li><li>Most Frequently Used Algorithm (MFU引用最多算法)</li><li>Page Buffering Algorithm（页面缓冲算法）</li></ul><p>Buddy</p><p>Slab</p><h2 id="File-System"><a href="#File-System" class="headerlink" title="File System"></a>File System</h2><p>FAT32磁盘的结构<br>主引导记录MBR是主引导区的第一个扇区，它由二部分组成:</p><ul><li>第一部分主引导代码，占据扇区的前446个字节，磁盘标识符（FD 4E F2 14）位于这段代码的未尾。</li><li>第二部分是分区表，分区表中每个条目有16字节长，分区表最多有4个条目，第一个分区条目从扇区的偏移量位置是0x01BE。</li></ul><p>扩展引导记录与主引导记录类同，如该扩展分区未装操作系则第一部分主引导代码为0，标签字也标记一个扩展分区引导区和分区引导区的结束。<br>PC计算机系统启动时，首先执行的是BIOS引导程序，完成自检，并加载主引导记录和分区表，然后执行主引导记录，由它引导激活分区引导记录，再执行分区引导记录，加载操作系统，最后执行操作系统，配置系统。</p><h2 id="IO-System"><a href="#IO-System" class="headerlink" title="IO System"></a>IO System</h2><ol><li>Polling</li><li>interruption</li><li>Direct memory access</li></ol><p>Buffering - store data in memory while transferring between devices，用来保存在两设备之间或在设备和应用程序之间所传输数据的内存区域。</p><p>缓冲作用：</p><ul><li>解决设备速度不匹配</li><li>解决设备传输块的大小不匹配</li><li>为了维持拷贝语义“copy semantics”要求</li></ul><p>Caching （高速缓存）- fast memory holding copy of data</p><ul><li>缓冲与高速缓存的差别是缓冲只是保留数据仅有的一个现存拷贝，而高速缓存只是提供了一个驻留在其他地方的数据的一个高速拷贝。</li><li>高速缓存和缓冲是两个不同的功能，但有时一块内存区域也可以同时用于两个目的。</li><li>当内核接收到I/O请求时，内核首先检查高速缓存以确定相应文件的内容是否在内存中。如果是，物理磁盘I/O就可以避免或延迟。</li></ul><p>SPOOLing（Simultaneous Peripheral Operation On Line）称为假脱机技术。用来保存设备输出的缓冲，这些设备如打印机不能接收交叉的数据流。<br>操作系统通过截取对打印机的输出来解决这一问题。应用程序的输出先是假脱机到一个独立的磁盘文件上。当应用程序完成打印时，假脱机系统将相应的待送打印机的假脱机文件进行排队<br>Printing：打印机虽然是独享设备，通过SPOOLing技术，可以将它改造为一台可供多个用户共享的设备。</p><p>RAID 0：如果你有n块磁盘，原来只能同时写一块磁盘，写满了再下一块，做了RAID 0之后，n块可以同时写，速度提升很快，但由于没有备份，可靠性很差。n最少为2。<br>RAID 1：正因为RAID 0太不可靠，所以衍生出了RAID 1。如果你有n块磁盘，把其中n/2块磁盘作为镜像磁盘，在往其中一块磁盘写入数据时，也同时往另一块写数据。坏了其中一块时，镜像磁盘自动顶上，可靠性最佳，但空间利用率太低。n最少为2。<br>RAID 3：.RAID 3是若你有n块盘，其中1块盘作为校验盘，剩余n-1块盘相当于作RAID 0同时读写，当其中一块盘坏掉时，可以通过校验码还原出坏掉盘的原始数据。这个校验方式比较特别，奇偶检验，1 XOR 0 XOR 1=0，0 XOR 1 XOR 0=1，最后的数据时校验数据，当中间缺了一个数据时，可以通过其他盘的数据和校验数据推算出来。但是这有个问题，由于n-1块盘做了RAID 0，每一次读写都要牵动所有盘来为它服务，而且万一校验盘坏掉就完蛋了。最多允许坏一块盘。n最少为3.<br>RAID 5：在RAID 3的基础上有所区别，同样是相当于是1块盘的大小作为校验盘，n-1块盘的大小作为数据盘，但校验码分布在各个磁盘中，不是单独的一块磁盘，也就是分布式校验盘，这样做好处多多。最多坏一块盘。n最少为3.</p><p>RAID 6：在RAID 5的基础上，又增加了一种校验码，和解方程似的，一种校验码一个方程，最多有两个未知数，也就是最多坏两块盘。</p><hr><p>学长的去年题目回忆</p><p>70题选择题，每题1分。约15分题目涉及到实验内容。</p><p>三道简答题：</p><ol><li>一个文件系统采用index allocation, 有16个direct index, single/double/triple indirect index各一个，Block size=1024B, block number fits into 4bytes, 计算最大支持的文件大小.</li><li>buddy memory allocation.</li><li>hashed page table, inverted page table.</li></ol>]]></content>
    
    
    <summary type="html">&lt;!-- # 操作系统 复习笔记 --&gt;</summary>
    
    
    
    <category term="学习笔记" scheme="https://archived.yanghan.life/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="学习笔记" scheme="https://archived.yanghan.life/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="复习笔记" scheme="https://archived.yanghan.life/tags/%E5%A4%8D%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="OS" scheme="https://archived.yanghan.life/tags/OS/"/>
    
  </entry>
  
  <entry>
    <title>计算机视觉 复习笔记</title>
    <link href="https://archived.yanghan.life/2018/01/16/%E8%AE%A1%E7%AE%97%E6%9C%BA%E8%A7%86%E8%A7%89-%E5%A4%8D%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <id>https://archived.yanghan.life/2018/01/16/%E8%AE%A1%E7%AE%97%E6%9C%BA%E8%A7%86%E8%A7%89-%E5%A4%8D%E4%B9%A0%E7%AC%94%E8%AE%B0/</id>
    <published>2018-01-16T09:26:44.000Z</published>
    <updated>2022-09-06T10:30:47.893Z</updated>
    
    <content type="html"><![CDATA[<!-- # 计算机视觉 复习笔记 --><span id="more"></span><h2 id="Intro"><a href="#Intro" class="headerlink" title="Intro"></a>Intro</h2><h3 id="计算机视觉"><a href="#计算机视觉" class="headerlink" title="计算机视觉"></a>计算机视觉</h3><p>研究用计算机来模拟生物外显或宏观视觉功能的科学和技术．计算机视觉系统的主要目标是用图像创建或恢复现实世界模型，然后认知现实世界．</p><h3 id="计算机视觉中心任务就是对图象进行理解"><a href="#计算机视觉中心任务就是对图象进行理解" class="headerlink" title="计算机视觉中心任务就是对图象进行理解"></a>计算机视觉中心任务就是对图象进行理解</h3><ul><li>对单幅图象的理解</li><li>对多幅图象的理解</li><li>对视频图象的理解</li></ul><h3 id="理解什么？"><a href="#理解什么？" class="headerlink" title="理解什么？"></a>理解什么？</h3><p>形状、位置、运动、类别</p><h3 id="核心问题"><a href="#核心问题" class="headerlink" title="核心问题"></a>核心问题</h3><p><img src="/assets/611478ffgy1fnikwh3mr1j20ug0i5ad9.jpg" alt="1"></p><h3 id="计算机视觉五大研究内容"><a href="#计算机视觉五大研究内容" class="headerlink" title="计算机视觉五大研究内容"></a>计算机视觉五大研究内容</h3><p>1）输入设备 (input device) 包括成像设备和数字化设备．成象设备是指通过光学摄像机或红外、激光、超声、X射线对周围场景或物体进行探测成象，得到关于场景或物体的二维或三维数字化图像．</p><p>2）低层视觉 (low level/early) 主要是对输入的原始图像进行处理．这一过程借用了大量的图像处理技术和算法，如图像滤波、图像增强、边缘检测、纹理检测、运动检测，以便从图像中抽取诸如角点、边缘、线条、边界、⾊彩、纹理、运动等关于场景的基本特征．</p><p>3）中层视觉（middle level） 主要任务是恢复场景的深度、表面法线方向、轮廓等有关场景的2.5维信息，实现的途径有立体视觉（ stereovision ）、测距成像（rangefinder）、从X恢复形状（Shape from X, X = 明暗、 纹理、运动） 系统标定、系统成像模型等研究内容一般也是在这个层次上进行的．分割、拟合等</p><p>4）高层视觉（high level） 主要任务是在以物体为中心的坐标系中，在原始输入图像、图像基本特征、2.5维图的基础上，恢复物体的完整三维图，建立物体三维描述，识别物体并确定物体的位置和方向</p><p>5）体系结构（system architecture） 在高度抽象的层次上，根据系统模型而不是根据实现设计的具体例子来研究系统的结构．为了说明这一点，可以考虑建筑设计中某一时期的建筑风格（如清朝时期）和根据这一风格设计出来的具体建筑之间的区别．体系结构研究涉及一系列相关的课题：并行结构、分层结构、信息流结构、拓扑结构以及从设计到实现的途径等等．</p><h3 id="Marr-视觉计算理论"><a href="#Marr-视觉计算理论" class="headerlink" title="Marr 视觉计算理论"></a>Marr 视觉计算理论</h3><p>Marr视觉计算理论立足于计算机科学，系统地概括了心理生理学、神经生理学等方面取得的所有重要成果，是视觉研究中迄今为止最为完善的视觉理论．<br>Marr建立的视觉计算理论，使计算机视觉研究有了一个比较明确的体系，并大大推动了计算机视觉研究的发展．人们普遍认为，计算机视觉这门学科的成与Marr的视觉理论有着密切的关系．</p><h4 id="信息处理分析的三个层次"><a href="#信息处理分析的三个层次" class="headerlink" title="信息处理分析的三个层次"></a>信息处理分析的三个层次</h4><table><thead><tr><th>计算层</th><th>表示和算法层</th><th>实现层</th></tr></thead><tbody><tr><td>计算的目的是什么？为什么这一计算是合适的？执行计算的策略是什么？</td><td>如何实现这个计算？输入、输出的表示是什么？表示与表示之间的变换是什么？</td><td>在物理上如何实现这些表示和算法？</td></tr></tbody></table><h4 id="视觉表示框架的三个阶段"><a href="#视觉表示框架的三个阶段" class="headerlink" title="视觉表示框架的三个阶段"></a>视觉表示框架的三个阶段</h4><p>第一阶段(Primal Sketch)：将输入的原始图像进行处理，抽取图像中诸如角点、边缘、纹理、线条、边界等基本特征，这些特征的集合称为基元图；</p><p>第二阶段(2.5D Sketch)：指在以观测者为中心的坐标系中，由输入图像和基元图恢复场景可见部分的深度、法线方向、轮廓等，这些信息包含了深度信息，但不是真正的物体三维表示，因此，称为二维半图；</p><p>第三阶段(3D Model)：在以物体为中心的坐标系中，由输入图像、基元图、二维半图来恢复、表示和识别三维物体。</p><h3 id="Gestalt-理论"><a href="#Gestalt-理论" class="headerlink" title="Gestalt 理论"></a>Gestalt 理论</h3><p>Gestalt理论反映了方类视觉本质的某些方面，但它对感知组织的基本原理只是一种公理性的描述(descriptive)，⽽不是一种机理性的解释(explanatory)。</p><ul><li>Law of Proximity<ul><li>Elements that are closer together will be perceived as a coherent object</li></ul></li><li>Law of Similarity<ul><li>Elements that look similar will be perceived as part of the same form</li></ul></li><li>Law of Good Continuation<ul><li>Humans tend to continue contours whenever the elements of the pattern establish an implied  direction</li></ul></li><li>Law of Closure<ul><li>Humans tend to enclose a space by completing a contour and ignoring gaps in the figure.</li></ul></li><li>Law of Prägnanz  (good form)<ul><li>A stimulus will be organized into as good a figure as possible. Here, good means symmetrical, simple, and regular.</li></ul></li><li>Law of Figure/Ground<ul><li>A stimulus will be perceived as separate from it’s ground.</li></ul></li></ul><h2 id="二值图像"><a href="#二值图像" class="headerlink" title="二值图像"></a>二值图像</h2><h3 id="二值化"><a href="#二值化" class="headerlink" title="二值化"></a>二值化</h3><p>Otsu流程：</p><ul><li>先计算影像的直方图</li><li>把直方图强度大于阈值的像素分成一组，把小于阈值的像素分成另一组。</li><li>分别计算这两个组的变异数，并把两个组内变异数相加。</li><li>将0〜255当前阈值来计算组内变异数和，总和值最小的就是结果阈值。</li></ul><h3 id="几何特性"><a href="#几何特性" class="headerlink" title="几何特性"></a>几何特性</h3><h4 id="面积（零阶矩）"><a href="#面积（零阶矩）" class="headerlink" title="面积（零阶矩）"></a>面积（零阶矩）</h4><p>$$<br>A = \sum_{i=0}^{n-1}\sum_{j=0}^{m-1}B[i,j]<br>$$</p><h4 id="区域中心（一阶矩）"><a href="#区域中心（一阶矩）" class="headerlink" title="区域中心（一阶矩）"></a>区域中心（一阶矩）</h4><p>$$<br>\overline x = \frac{ \sum_{i=0}^{n-1}\sum_{j=0}^{m-1}j  B[i,j]}{A} \<br>\overline y = \frac{ \sum_{i=0}^{n-1}\sum_{j=0}^{m-1}i  B[i,j]}{A}<br>$$</p><h4 id="方向"><a href="#方向" class="headerlink" title="方向"></a>方向</h4><p>– 某些形状(如圆)是没有方向的;<br>– 假定物体是长形的,长轴方向为物体的方向;</p><p>求方向：最小化问题</p><p>最小二乘法  $r_{ij}^2$为点到直线的距离<br>$$<br>\chi^2 =   \sum_{i=0}^{n-1}\sum_{j=0}^{m-1}r_{ij}^2B[i,j]<br>$$</p><h4 id="伸长率"><a href="#伸长率" class="headerlink" title="伸长率"></a>伸长率</h4><p>$$<br>E = \frac{\chi_{max}}{\chi_{min}}<br>$$</p><h4 id="密集度"><a href="#密集度" class="headerlink" title="密集度"></a>密集度</h4><p>$\rho$是周长<br>$$<br>C = \frac{A}{\rho^2}<br>$$<br>圆 &gt; 正方形 &gt; 长方形</p><h4 id="形态比"><a href="#形态比" class="headerlink" title="形态比"></a>形态比</h4><p>区域的最小外接矩形的长与宽之比</p><h4 id="欧拉数"><a href="#欧拉数" class="headerlink" title="欧拉数"></a>欧拉数</h4><p>亏格数 (genus)</p><p>连通分量数减去洞数</p><p>$$ E = C -H $$</p><p>平移、旋转、放缩 不变 </p><h4 id="距离度量"><a href="#距离度量" class="headerlink" title="距离度量"></a>距离度量</h4><ul><li><p>metric:</p><ul><li><p>d(p,q) &gt;= 0, 当且仅当 p=q时， d(p,q)=0</p></li><li><p>d(p,q) = d(q,p)</p></li><li><p>d(p,r) &lt;= d(p,q) + d(q,r)</p></li></ul></li></ul><ul><li>常用距离<ul><li>欧几里德距离(Euclidean)</li><li>街区距离(block)</li><li>棋盘距离(chess) max</li><li>Minkowski 距离(p-norm distance)   $L_p$</li></ul></li></ul><h3 id="投影计算"><a href="#投影计算" class="headerlink" title="投影计算"></a>投影计算</h3><p>水平投影：计算每一列像素为1的个数。</p><p>垂直投影：计算每一行像素为1的个数。</p><p>对角线投影：从左下到右上，计算每一个对角线像素为1的个数。</p><h3 id="连通区域标记"><a href="#连通区域标记" class="headerlink" title="连通区域标记"></a>连通区域标记</h3><p>四联通邻点  / 路径</p><p>八联通邻点  / 路径</p><p>连通是等价关系，自反性， 对称性，传递性</p><p>连通分量： 连通像素的集合</p><h4 id="递归算法"><a href="#递归算法" class="headerlink" title="递归算法"></a>递归算法</h4><p>(1)扫描图像，找到没有标记的一个前景点（即像素值为1），分配标记L<br>(2)递归分配标记L给该点的邻点<br>(3)如果不存在没标记的点，则停⽌<br>(4)返回第(1)步</p><h4 id="序贯算法"><a href="#序贯算法" class="headerlink" title="序贯算法"></a>序贯算法</h4><p>序贯算法（for 4连通）</p><p>(1)从左到右、从上到下扫描图像<br>(2)如果像素点值为1，则（分4种情况）</p><ul><li>如果上面点和左面点有且仅有一个标记，则复制这一标记</li><li>如果两点有相同标记，复制这一标记</li><li>如果两点有不同标记，则复制上点的标记且将两个标记输⼊等价表中作为等价标记</li><li>否则给这一个象素点分配一新的标记并将这一标记输⼊等价表</li></ul><p>(3)如果需要考虑更多点，则返回(2)<br>(4)在等价表的每一等价集中找到最低的标记<br>(5)扫描图像，用等价表中的最低标记取代每一标记</p><h3 id="边界跟踪算法"><a href="#边界跟踪算法" class="headerlink" title="边界跟踪算法"></a>边界跟踪算法</h3><p>(1)从左到右，从上到下扫描图像，求区域S的起始点<br>(2)用c表示当前边界上被跟踪的象素点，置c=s(k)，记c的左邻点为b<br>(3)按逆时针方向记从b开始的c的8个8邻点分别为<br>(4)从b开始，沿逆时针方向找到第一个 ni in S<br>(5)置 c = s(k) = ni, b = ni-1<br>(6)重复步骤(3),(4),(5)，直到s(k)=s(0)</p><h3 id="形态学算子"><a href="#形态学算子" class="headerlink" title="形态学算子"></a>形态学算子</h3><ul><li>膨胀 Dilate</li><li>腐蚀 Erode</li><li>开操作 Open</li><li>闭操作 Close</li></ul><h2 id="Edge-Detection"><a href="#Edge-Detection" class="headerlink" title="Edge Detection"></a>Edge Detection</h2><p>基本思想：</p><p>函数导数反映图像灰度变化的显著程度。<br>一阶导数的局部极大值，或二阶导数的过零点。</p><h3 id="模板-amp-卷积"><a href="#模板-amp-卷积" class="headerlink" title="模板 &amp; 卷积"></a>模板 &amp; 卷积</h3><ul><li>模板(Template/Kernel): A matrix represents an operator. A convolution template centers on each pixel in an image and generates new output pixels.</li><li>卷积(Convolution): by using the template, the new pixel value is computed by multiplying each pixel value in the neighborhood with the corresponding weight in the convolution mask and summing  these products.</li></ul><h3 id="基于一阶导数的边缘检测"><a href="#基于一阶导数的边缘检测" class="headerlink" title="基于一阶导数的边缘检测"></a>基于一阶导数的边缘检测</h3><h4 id="梯度"><a href="#梯度" class="headerlink" title="梯度"></a>梯度</h4><p>$$<br>G(x, y) = \begin{bmatrix}<br>G_x,\<br>G_y<br>\end{bmatrix}<br> = \begin{bmatrix}<br> \frac{\delta f}{\delta x}\<br> \frac{\delta f}{\delta y}<br> \end{bmatrix}<br> \<br> \arrowvert G(x,y) \arrowvert = \sqrt{G_x^2 + G_y^2}\<br> \arrowvert G(x,y) \arrowvert = \arrowvert{G_x} \arrowvert+ \arrowvert{G_y} \arrowvert \<br> \arrowvert G(x,y) \arrowvert \approx max(\arrowvert{G_x} \arrowvert,  \arrowvert{G_y} \arrowvert )<br>$$</p><p>梯度方向<br>$$<br>\alpha(x, y) = arctan(G_y / G_x)<br>$$<br>梯度方向为函数最大变化率方向。</p><p>用差分近似偏导数<br>$$<br>G_x = f \lbrack x+1, y \rbrack - f \lbrack x,y  \rbrack<br>\<br>G_y = f \lbrack x,y \rbrack - f \lbrack x, y+1\rbrack<br>\<br>G_x = \begin{bmatrix}<br>-1 &amp; 1<br>\end{bmatrix}<br>\<br>G_y = \begin{bmatrix}<br>1 \<br> -1<br>\end{bmatrix}<br>\<br>$$</p><h4 id="Roberts交叉算子"><a href="#Roberts交叉算子" class="headerlink" title="Roberts交叉算子"></a>Roberts交叉算子</h4><p> <img src="/assets/611478ffgy1fnic92j6wej20cy02pwec.jpg" alt="Roberts交叉算子"></p><h4 id="Sobel算子"><a href="#Sobel算子" class="headerlink" title="Sobel算子"></a>Sobel算子</h4><p> <img src="/assets/611478ffgy1fnhcg5b564j20gm04rjrl.jpg" alt="Sobel算子"></p><h4 id="Prewitt算子"><a href="#Prewitt算子" class="headerlink" title="Prewitt算子"></a>Prewitt算子</h4><p><img src="/assets/611478ffgy1fnhcghmaf7j20gm04t74g.jpg" alt="Prewitt算子"></p><h3 id="基于二阶导数的边缘检测"><a href="#基于二阶导数的边缘检测" class="headerlink" title="基于二阶导数的边缘检测"></a>基于二阶导数的边缘检测</h3><p>图像灰度二阶导数的过零点对应边缘点。</p><h4 id="Laplacian-算子"><a href="#Laplacian-算子" class="headerlink" title="Laplacian 算子"></a>Laplacian 算子</h4><p>二阶导数的二维等价形式<br>$$<br>\nabla ^2 f = \frac{\delta ^2 f}{\delta x^2} + \frac{\delta^2 f}{\delta y^2}\<br>\nabla^2 \approx  \begin{bmatrix}<br>0 &amp; 1 &amp; 0\ \<br>1 &amp; -4 &amp; 1 \<br>0 &amp; 1 &amp; 0<br>\end{bmatrix}\<br>\nabla^2 \approx  \begin{bmatrix}<br>1 &amp; 4 &amp; 1\ \<br>4 &amp; -20 &amp; 4 \<br>1 &amp; 4 &amp; 1<br>\end{bmatrix}\<br>$$</p><h4 id="LoG-边缘检测"><a href="#LoG-边缘检测" class="headerlink" title="LoG 边缘检测"></a>LoG 边缘检测</h4><p>Laplacian of Guassian</p><ul><li>平滑滤波器是高斯滤波器</li><li>采用拉普拉斯算子计算二阶导数</li><li>边缘检测判据是二阶导数零交叉点  并  对应一阶导数的较大峰值</li></ul><ul><li>使用线性内插方法在子像素分辨率水平上估计边缘的位置</li></ul><p>两种等效计算方法</p><ol><li>图像与高斯函数卷积，再求卷积的拉普拉斯微分</li><li>求高斯函数的拉普拉斯微分，再与图像卷积</li></ol><p>墨西哥草帽算子</p><p><img src="/assets/611478ffgy1fnhdc1ts66j207006wjre.jpg" alt="LoG"></p><h3 id="Canny-边缘检测器"><a href="#Canny-边缘检测器" class="headerlink" title="Canny 边缘检测器"></a>Canny 边缘检测器</h3><p>算法步骤：</p><ol><li>用高斯滤波器平滑图像</li><li>用一阶偏导有限差分计算梯度幅值和方向</li><li>对梯度幅值进行非极大值抑制（NMS）</li><li>用双阈值算法检测和连接边缘</li></ol><p>Why 高斯滤波器？<br>平滑去噪和边缘检测是一对⽭盾，应用高斯函数的一阶导数，在二者之间获得最佳的平衡</p><h4 id="非最大值抑制"><a href="#非最大值抑制" class="headerlink" title="非最大值抑制"></a>非最大值抑制</h4><p>去掉幅值局部变化非极大的点．</p><ul><li>将梯度角离散为圆周的四个扇区之一，以便用3×3的窗作抑制运算 //TODO</li><li>方向角离散化：<br>ζ [i, j] = Sector(θ[i, j])</li><li>抑制，得到新幅值图：<br>N[i, j]= NMS(M[i, j],ζ [i, j])</li></ul><p>How to抑制？ 若M[i,j]不比沿梯度线方向上的两个相邻点幅值大，则N[i,j]=0</p><h4 id="双阈值化"><a href="#双阈值化" class="headerlink" title="双阈值化"></a>双阈值化</h4><p>双阈值化并边缘链接<br>(a) 取高低两个阈值(T2, T1)作用于新幅值图N[i,j]，得到两个边缘图：高阈值和低阈值边缘图。<br>高阈值图：N[i,j] &gt; T2；<br>低阈值图：N[i,j] &gt; T1<br>(b) 连接高阈值边缘图，出现断点时，在低阈值边缘图中的8邻点域搜寻边缘点。</p><ul><li>阈值太低: 假边缘;</li><li>阈值太高: 部分轮廊丢失.</li><li>选用两个阈值: 更有效的阈值方案．</li></ul><h2 id="Local-Feature"><a href="#Local-Feature" class="headerlink" title="Local Feature"></a>Local Feature</h2><h3 id="Harris-Corner-Detector"><a href="#Harris-Corner-Detector" class="headerlink" title="Harris Corner Detector"></a>Harris Corner Detector</h3><p>$$<br>E(u,v) = \sum_{x,y}w(x,y)[I(x+u, y+v) - I(x, y)]^2 \<br>E(u,v) = \sum_{x,y}w(x,y)[uI_x + vI_y]^2 \<br>E \cong \begin{bmatrix} u &amp; v \end{bmatrix} M \begin{bmatrix}u \ v\end{bmatrix} \<br>M = \sum_{x,y}w(x,y)\begin{bmatrix} I_x^2 &amp; I_xI_y \ I_xI_y &amp; I_y^2 \end{bmatrix}   \<br>det M = \lambda_1 \lambda_2 \<br>trace M = \lambda_1 + \lambda_2 \<br>R = det M -k (trace  M)^2<br>$$<br>若 R&gt;0（大于某一阈值），则为角点；R&lt;0，则为边；R 绝对值很小，则为平面区域。</p><p>选取 R 得到的符合条件点的局部最大值作为结果。</p><h3 id="Scale-Invariant-Detection-Summary"><a href="#Scale-Invariant-Detection-Summary" class="headerlink" title="Scale Invariant Detection: Summary"></a>Scale Invariant Detection: Summary</h3><ul><li>Given: two images of the same scene with a large scale difference between them</li><li>Goal: find the same interest points independently in each image</li><li>Solution: search for maxima of suitable functions in scale and in space (over the image)</li></ul><p>Methods:</p><ol><li>Harris-Laplacian [Mikolajczyk, Schmid]: maximize Laplacian over scale, Harris’ measure of corner response over the image</li><li>SIFT [Lowe]: maximize Difference of Gaussians over scale and space</li></ol><h3 id="Laplacian-Harris-Corner"><a href="#Laplacian-Harris-Corner" class="headerlink" title="Laplacian  Harris Corner"></a>Laplacian  Harris Corner</h3><p><strong>设计一个尺度不变函数，不同尺度下的图片找到的区域是相同的</strong></p><ol><li>在多尺度检测关键点 </li><li>然后找上下不同尺度的局部最大值 </li><li>消除低于阈值的点</li></ol><p>Laplacian</p><p>Difference of Gaussians</p><p>$$<br>L = \sigma^2(G_{xx}(x, y, \sigma) +G_{yy}(x, y, \sigma)) \<br>DoG = G(x,y,k\sigma) - G(x,y,\sigma)<br>$$</p><h3 id="SIFT-Descriptor"><a href="#SIFT-Descriptor" class="headerlink" title="SIFT Descriptor"></a>SIFT Descriptor</h3><h4 id="基本步骤"><a href="#基本步骤" class="headerlink" title="基本步骤"></a>基本步骤</h4><ol><li>构建尺度空间，建立图像金字塔。</li><li>寻找极值点（相邻的 26 个点中最大 / 最小值）</li><li>去除不好的特征点：使用近似的 harris corner，检测关键点的位置和尺度，并且去除边缘响应点。</li><li>用 16x16 的窗口放在特征点附近</li><li> 将 16x16 分成 16 个 4x4 的窗口 </li><li>计算窗口中每个像素的边的方向（梯度角减去 90°） </li><li>丢掉方向能量小的边（使用阈值）用直方图描述结果 </li><li>将每个小窗口中的所有的方向离散成 8 个方向，一共 16x8=128 个 </li></ol><p><strong>为什么只使用梯度信息</strong><br>因为梯度信息可以表示边缘信息，并且在光照变化时有抵抗能力<br><strong>如何实现旋转不变的</strong><br>因为我们找的是对应位置的参考方向而非绝对方向<br><strong>尺度不变的原理</strong><br>因为在使用高斯模糊的不同尺度（如图像金字塔）处重新采样图像，仅当在两个尺度之间观察到最大值时才将梯度存储为描述符</p><h2 id="Curves"><a href="#Curves" class="headerlink" title="Curves"></a>Curves</h2><h3 id="Hough-变换"><a href="#Hough-变换" class="headerlink" title="Hough 变换"></a>Hough 变换</h3><p>Hough 变换是基于投票原理的参数估计方法，是一种重要的形状检测技术<br><strong>基本思想：</strong>图像中的每一点对参数组合进行表决，赢得多数票的参数组合为胜者（结果） </p><p>用极坐标来表示直线，从 $(x,y)$ 转换到 $(\rho , \theta)$ 空间。</p><p><strong>步骤：</strong> </p><ol><li>适当地量化参数空间  （合适的精度即可）</li><li>假定参数空间每一个单元都是一个累加器，把累加器初始化为 0 </li><li>对图像空间的每一点，在其所满足的参数方程对应的累加器上加 1 </li><li>累加器阵列最大值对应模型的参数</li></ol><h3 id="Fourier-Transform-傅立叶变换"><a href="#Fourier-Transform-傅立叶变换" class="headerlink" title="Fourier Transform 傅立叶变换"></a>Fourier Transform 傅立叶变换</h3><p>​        变换：用正弦来表示，对于二维图像而言，由以下的基图像表示：</p><p><img src="/assets/611478ffgy1fnie5pusrsj20e80egn1v.jpg" alt="img"></p><p>低频与高频：亮度灰度剧烈变化的地方是高频（图像边缘和轮廓的度量），对应边缘；</p><p>变化不大的是低频（图像强度的综合度量），对应大片色块。</p><p>近处看到的是高频分量，远处观察到的是低频分量。</p><h4 id="怎么理解拉普拉斯金字塔的每一层是带通滤波？"><a href="#怎么理解拉普拉斯金字塔的每一层是带通滤波？" class="headerlink" title="怎么理解拉普拉斯金字塔的每一层是带通滤波？"></a>怎么理解拉普拉斯金字塔的每一层是带通滤波？</h4><p>拉普拉斯金字塔是将图像下采样后再上采样得到的差值图像。</p><p>相减 保留细节 高通</p><p>下采样 降噪 低通</p><h3 id="Image-Pyramids"><a href="#Image-Pyramids" class="headerlink" title="Image Pyramids"></a>Image Pyramids</h3><ul><li>“Gaussian” Pyramid</li><li>“Laplacian” Pyramid</li></ul><p>$$<br>L_i = G_i – expand(G_{i+1})<br>$$</p><h2 id="PCA"><a href="#PCA" class="headerlink" title="PCA"></a>PCA</h2><h4 id="主元分析-PCA"><a href="#主元分析-PCA" class="headerlink" title="主元分析 (PCA)"></a>主元分析 (PCA)</h4><p>用于数据集降维。</p><p>选择一个新的坐标系统进行线性降维，使得第一轴上是最大投影方向，第二轴上是第二大投影方向…… 以此类推。</p><h3 id="Eigenface"><a href="#Eigenface" class="headerlink" title="Eigenface"></a>Eigenface</h3><ol><li>预处理：根据人眼位置进行裁剪，进行灰度均衡化。</li><li>将二维人脸图像按一行行向量拼成一列，得到列图像；并把所有列图像拼起来，并求出平均人脸。</li><li>图像的协方差矩阵。</li><li>求协方差矩阵的特征值，以及归一化的特征向量，即为特征人脸。</li></ol><p>识别  </p><p>​将两张图像都投影到人脸空间，比较投影向量的欧氏距离。</p><p>重构</p><p>将图像投影到人脸空间，通过左乘特征人脸空间矩阵恢复。</p><h2 id="Camera-Model"><a href="#Camera-Model" class="headerlink" title="Camera Model"></a>Camera Model</h2><h3 id="Thin-Lenses"><a href="#Thin-Lenses" class="headerlink" title="Thin Lenses"></a>Thin Lenses</h3><p>$$<br>\frac{1}{d_o} +\frac{1}{d_i} = \frac{1}{f}<br>$$</p><p>DOF: Depth of Field</p><p>FOV: Field of View     focal length<br>$$<br>\varphi = tan^{-1}(\frac{d}{2f})<br>$$</p><h3 id="Pinhole-Camera-Model"><a href="#Pinhole-Camera-Model" class="headerlink" title="Pinhole Camera Model"></a>Pinhole Camera Model</h3><h4 id="基本投影"><a href="#基本投影" class="headerlink" title="基本投影"></a>基本投影</h4><p>$$<br>\frac{-x}{f} = \frac{X}{Z}<br>\<br>-x = f \frac{X}{Z}<br>\<br>(x, y,z) \rightarrow  (-d \frac{x}{z}, -d\frac{y}{z}, -d)<br>$$</p><h4 id="perspective-projection"><a href="#perspective-projection" class="headerlink" title="perspective projection"></a>perspective projection</h4><p>projection matrix<br>$$<br>\begin{bmatrix}<br>1 &amp; 0 &amp; 0 &amp; 0 \<br>0 &amp; 1 &amp; 0 &amp; 0 \<br>0 &amp; 0 &amp; 1 &amp; 0 \<br>0 &amp; 0 &amp; -1/d &amp; 0 \<br>\end{bmatrix}  \begin{bmatrix}<br>x \ y \ z \ 1<br>\end{bmatrix}  =  \begin{bmatrix}<br>x \ y \ z \ -z/d<br>\end{bmatrix}  \rightarrow \begin{bmatrix}<br>-d\frac{x}{z} \ -d\frac{y}{z} \ -d \ 1<br>\end{bmatrix}  \rightarrow (-d\frac{x}{z} ,-d\frac{y}{z})<br>$$</p><h4 id="Intrinsic-Parameters"><a href="#Intrinsic-Parameters" class="headerlink" title="Intrinsic Parameters"></a>Intrinsic Parameters</h4><p>$$<br>(f_x, f_y, c_x, c_y)<br>$$</p><p><img src="/assets/611478ffgy1fnjnuj0gh3j20h80ajjs1.jpg" alt="intrinsic parameters"></p><p>$$<br>\begin{bmatrix}<br>f_x &amp; 0 &amp; c_x \<br>0 &amp; f_y &amp; c_y \<br>0 &amp; 0 &amp; 1<br>\end{bmatrix}<br>$$</p><p>f: 焦距对应的像素</p><p>c: 图像中心与坐标原点的偏移</p><h2 id="Lens-Distortion"><a href="#Lens-Distortion" class="headerlink" title="Lens Distortion"></a>Lens Distortion</h2><h3 id="Radial-Distortion"><a href="#Radial-Distortion" class="headerlink" title="Radial Distortion"></a>Radial Distortion</h3><ul><li>Caused by imperfect lenses:  Geometry of Lens,  Aperture Position(几何性质， 光圈位置)</li></ul><ul><li>Deviations are most noticeable for rays that pass through the edge of the lens</li></ul><p>常见枕形畸变和桶型畸变：</p><p><img src="/assets/611478ffgy1fnjofwrroxj20ix0d40uq.jpg" alt="radial distortion"></p><h4 id="Correcting"><a href="#Correcting" class="headerlink" title="Correcting"></a>Correcting</h4><p>k1, k2, k3<br>$$<br>x_{corrected} = x(1+k_1r^2+k_2r^4+k_3r^6) \<br>y_{corrected} = y(1+k_1r^2+k_2r^4+k_3r^6) \<br>$$</p><h3 id="Tangential-Distortion"><a href="#Tangential-Distortion" class="headerlink" title="Tangential Distortion"></a>Tangential Distortion</h3><p>The decentering of the optical component (assembly process)</p><p>由于 CMOS 等感光元件摆放倾斜，没有平行于图像平面<br>越靠近中间，畸变越小 </p><h4 id="Correcting-1"><a href="#Correcting-1" class="headerlink" title="Correcting"></a>Correcting</h4><p>p1, p2<br>$$<br>x_{corrected} = x + [2p_1y+p_2(r^2+2x^2)] \<br>x_{corrected} = x + [p_1(r^2+2y^2)+2p_2x]<br>$$</p><h3 id="Distortion-Parameters"><a href="#Distortion-Parameters" class="headerlink" title="Distortion Parameters"></a>Distortion Parameters</h3><p>$$<br>(k_1, k_2, p_1, p_2, k_3)<br>$$</p><h3 id="Extrinsic-Parameters"><a href="#Extrinsic-Parameters" class="headerlink" title="Extrinsic Parameters"></a>Extrinsic Parameters</h3><p>$$<br>(\theta, \phi, \varphi,t_x, t_y, t_z)<br>$$</p><p><img src="/assets/611478ffgy1fnjpjemyz8j20gv0b8aaj.jpg" alt="Extrinsic Parameters"></p><h3 id="成像过程"><a href="#成像过程" class="headerlink" title="成像过程"></a>成像过程</h3><p><strong>内参，外参，畸变参数在成像各阶段中的角色（三维物体到真实图像的过程）</strong> </p><ol><li>第一步是从世界坐标系转为相机坐标系，这一步是从三维点到三维点的转换，包括 R，t 等参数（相机外参） </li><li>第二步是从相机坐标系转为成像平面坐标系（像素坐标系），这一步是三维点到二维点的转换，包括 K 等参数（相机内参） </li><li>最后再用到畸变参数 </li></ol><p><img src="/assets/611478ffgy1fnjpi7000ej20i20cj3ze.jpg"></p><h2 id="Motion-Estimation"><a href="#Motion-Estimation" class="headerlink" title="Motion Estimation"></a>Motion Estimation</h2><h3 id="Optical-Flow"><a href="#Optical-Flow" class="headerlink" title="Optical Flow"></a>Optical Flow</h3><h4 id="光流解决的是什么问题？"><a href="#光流解决的是什么问题？" class="headerlink" title="光流解决的是什么问题？"></a>光流解决的是什么问题？</h4><p>评估从 H 到 I 的像素运动，给出图像 H 中的一个像素，找到图像 I 中相同颜色的相近像素。解决的是像素对应问题。</p><h4 id="光流三个基本假设是什么？"><a href="#光流三个基本假设是什么？" class="headerlink" title="光流三个基本假设是什么？"></a>光流三个基本假设是什么？</h4><p>亮度恒定性 brightness constancy  $I(x+u, y+v, t+1) = I(x, y, t)$</p><p>空间相干性 spatial coherence</p><p>细微运动 small motion</p><h4 id="哪些位置的光流比较可靠，为什么"><a href="#哪些位置的光流比较可靠，为什么" class="headerlink" title="哪些位置的光流比较可靠，为什么"></a>哪些位置的光流比较可靠，为什么</h4><p>纹理复杂区域，梯度比较大且方向不同，求出来的特征值比较大 </p><h4 id="推导"><a href="#推导" class="headerlink" title="推导"></a>推导</h4><p>$$<br>0 = I(x+u, y+v) - H(x, y) \<br>\approx [I(x,y)-H(x,y)] + uI_x + vI_y \<br>\approx I_t + uI_x + vI_y \<br>\approx I_t + \nabla I  [u, v]<br>$$</p><h2 id="Camera-Calibration"><a href="#Camera-Calibration" class="headerlink" title="Camera Calibration"></a>Camera Calibration</h2><p>Given:</p><p>N correspondences b/w scene and images</p><p>Recover the camera parameters:</p><p>Distortion coefficients, intrinsic para., extrinsic para</p><h4 id="基于-Pattern-Reference-Object-的相机标定"><a href="#基于-Pattern-Reference-Object-的相机标定" class="headerlink" title="基于 Pattern/Reference Object 的相机标定"></a>基于 Pattern/Reference Object 的相机标定</h4><p>已知：给定标定物体的 N 个角点，K 个视角（棋盘格子两个点可以得出四个等式）<br>求解：所有的参数。N 个点 K 个视角可以列出 2NK 个等式，会带来 6K+4 个参数。需要 2NK&gt;6K+4.</p><h4 id="简述其基本过程"><a href="#简述其基本过程" class="headerlink" title="简述其基本过程"></a>简述其基本过程</h4><ol><li>获取标定物体网格的角点在坐标系的位置 </li><li>找到图片的角点 </li><li>根据图像空间坐标系到世界坐标系列出等式 </li><li>求解相机参数 </li></ol><h2 id="Two-View-Vision"><a href="#Two-View-Vision" class="headerlink" title="Two View Vision"></a>Two View Vision</h2><h3 id="Triangular-测量"><a href="#Triangular-测量" class="headerlink" title="Triangular 测量"></a>Triangular 测量</h3><p>$$<br>\frac{T-(x^;-x^r)}{Z-f} = \frac{T}{Z} \rightarrow Z = \frac{fT}{x^l-x^r}<br>$$</p><p><img src="/assets/611478ffgy1fnjvmu5u7mj20g40e5gn7.jpg" alt="disparity"></p><p>视差：$d=x_l-x_r$，表示左右两摄像头成像的距离 </p><p>Z 的结果误差主要在分母（视差）那里。视差小的时候，视差的误差会对 Z 产生很大的影响。</p><p>T 越小，误差越小 </p><p>T 越大，看到的范围越小（因为是取两眼图像的交叉部分） </p><h3 id="How-To-Do-Stereo"><a href="#How-To-Do-Stereo" class="headerlink" title="How To Do Stereo"></a>How To Do Stereo</h3><ol><li>undistortion 消除畸变影响</li><li>rectification 校正相机位置 row-aligned</li><li>correspondence  找到对应点 计算视差disparity</li><li>Reprojection 三角测量-&gt;depth map</li></ol><h2 id="三维数据获取"><a href="#三维数据获取" class="headerlink" title="三维数据获取"></a>三维数据获取</h2><h3 id="Structured-Lighting-结构光成像系统结构"><a href="#Structured-Lighting-结构光成像系统结构" class="headerlink" title="Structured Lighting 结构光成像系统结构"></a>Structured Lighting 结构光成像系统结构</h3><p>结构光投影仪 + CCD 相机 + 深度信息重建系统</p><p>projector (one or more), CCD camera (one or more), and depth recovery system</p><h3 id="结构光获取三维数据原理"><a href="#结构光获取三维数据原理" class="headerlink" title="结构光获取三维数据原理"></a>结构光获取三维数据原理</h3><p><img src="/assets/611478ffgy1fnjwfzvthmj20me0f7tb4.jpg" alt="structured light"></p><h4 id="encoding"><a href="#encoding" class="headerlink" title="encoding"></a>encoding</h4><p>位码</p><p>3：4567</p><p>2：2367</p><p>1:   1457 </p><h3 id="ICP-Iterative-Closet-Point"><a href="#ICP-Iterative-Closet-Point" class="headerlink" title="ICP - Iterative Closet Point"></a>ICP - Iterative Closet Point</h3><p><strong>ICP：</strong>迭代最近点方法（用于多个摄像机的配准问题，即把多个扫描结果拼接在一起形成对扫描对象的完整描述） </p><p><strong>基本步骤：</strong></p><p>给定两个三维点集 X 与 Y，将 Y 配准到 X： </p><ol><li>建立两个扫描结果之间的对应关系</li><li>通过迭代获得一个仿射变换函数能够描述1中对应点之间的变换关系</li><li>对 Y 应用上一步求得的仿射变换，更新 Y </li><li>两个结果中距离最近的点作为对应点，计算对应点的距离如果大于阈值，重复23，否则停止计算</li></ol><p>寻找F就变成了找到使Cost最小的点的搜索过程，这就是本算法称为ICP的原因。</p><h2 id="Image-Segmentation"><a href="#Image-Segmentation" class="headerlink" title="Image Segmentation"></a>Image Segmentation</h2><h3 id="K-means"><a href="#K-means" class="headerlink" title="K-means"></a>K-means</h3><p>第一步：任意选择 k 个 sift 特征点作为初始聚类质心。</p><p>第二步：对于每个 sift 特征点，计算它们与 k 个聚类质心的欧式距离，找到最小的那个聚类质心，将该特征点放入此聚类质心集合中。</p><p>第三步：对于每个聚类质心集合，用所有元素均值来更新质心。</p><p>第四步：比较更新前后聚类质心集合No points are re-assigned, 否则返回步骤 2，如果迭代次数太多聚类失败。</p><ul><li><p>Pros</p><ul><li>Simple and fast</li><li>easy to implement</li></ul></li><li><p>Cons</p><ul><li>Need to choose K</li><li>Sensitive to outliers</li></ul></li><li><p>Usage</p><ul><li>Rarely used for pixel segmentation</li></ul></li></ul><h3 id="Graph-Cut"><a href="#Graph-Cut" class="headerlink" title="Graph Cut"></a>Graph Cut</h3><p>Input: User provides rough indication of foreground region.<br>Goal: Automatically provide a pixel-level segmentation.</p><h2 id="识别-Visual-Recognition"><a href="#识别-Visual-Recognition" class="headerlink" title="识别 Visual Recognition"></a>识别 Visual Recognition</h2><h3 id="基本任务及挑战因素"><a href="#基本任务及挑战因素" class="headerlink" title="基本任务及挑战因素"></a>基本任务及挑战因素</h3><h4 id="基本任务大概可以分为哪几大类"><a href="#基本任务大概可以分为哪几大类" class="headerlink" title="基本任务大概可以分为哪几大类"></a>基本任务大概可以分为哪几大类</h4><ol><li><p>图片分类 </p></li><li><p>检测和定位物体/图片分割 </p></li><li><p>估计语义和几何属性</p></li><li><p>对人类活动和事件进行分类 </p></li></ol><h4 id="都有哪些挑战因素"><a href="#都有哪些挑战因素" class="headerlink" title="都有哪些挑战因素"></a>都有哪些挑战因素</h4><p>  视角变换<br>  光线变化<br>  尺度变化<br>  物体形变<br>  物体遮挡<br>  背景凌乱<br>  内部类别多样 </p><h3 id="基于词袋-BoW-的物体分类"><a href="#基于词袋-BoW-的物体分类" class="headerlink" title="基于词袋 (BoW) 的物体分类"></a>基于词袋 (BoW) 的物体分类</h3><h4 id="图像的BoW（bag-of-words）是指什么意思"><a href="#图像的BoW（bag-of-words）是指什么意思" class="headerlink" title="图像的BoW（bag-of-words）是指什么意思?"></a>图像的BoW（bag-of-words）是指什么意思?</h4><p>图像中的单词被定义为一个图像块的特征向量，图像的Bow模型即图像中所有图像块的特征向量得到的直方图</p><h4 id="基本步骤-1"><a href="#基本步骤-1" class="headerlink" title="基本步骤"></a>基本步骤</h4><ol><li><p>特征提取与表示</p></li><li><p>通过训练样本聚类来建立字典 (codewords dictionary)</p></li><li><p>用字典的直方图来表达一张图像</p></li><li><p>根据 bag of words 来分类未知图像</p></li></ol><h2 id="Stitching"><a href="#Stitching" class="headerlink" title="Stitching"></a>Stitching</h2><ul><li>Detect key points</li><li>Build the SIFT descriptors</li><li>Match SIFT descriptors</li><li>Fitting the transformation</li><li>RANSAC</li><li>Image Blending</li></ul><h3 id="RANSAC"><a href="#RANSAC" class="headerlink" title="RANSAC"></a>RANSAC</h3><p>RANdom SAmple Consensus</p><ul><li>Approach: we want to avoid the impact of outliers, so let’s look for “inliers”, and use only those.</li></ul><ul><li>Intuition: if an outlier is chosen to compute the current fit, then the resulting line won’t have much  support from rest of the points.</li></ul><p>RANSAC loop:</p><ol><li>Randomly select a seed group of points on which to base transformation estimate (e.g., a group of<br>matches)</li><li>Compute transformation from seed group</li><li>Find inliers to this transformation</li><li>If the number of inliers is sufficiently large, recompute least-squares estimate of transformation on<br>all of the inliers</li><li>Keep the transformation with the largest number of inliers</li></ol><p>n, k, t, d</p><p><strong>优点：</strong>是大范围模型匹配问题的一个普遍意义上的方法，且运用简单，计算快。<br><strong>缺点：</strong>只能计算outliers不多的情况（投票机制可以解决outliers高的情况） </p>]]></content>
    
    
    <summary type="html">&lt;!-- # 计算机视觉 复习笔记 --&gt;</summary>
    
    
    
    <category term="学习笔记" scheme="https://archived.yanghan.life/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="学习笔记" scheme="https://archived.yanghan.life/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="视觉" scheme="https://archived.yanghan.life/tags/%E8%A7%86%E8%A7%89/"/>
    
  </entry>
  
  <entry>
    <title>椭圆检测与拟合</title>
    <link href="https://archived.yanghan.life/2018/01/07/%E6%A4%AD%E5%9C%86%E6%A3%80%E6%B5%8B%E4%B8%8E%E6%8B%9F%E5%90%88/"/>
    <id>https://archived.yanghan.life/2018/01/07/%E6%A4%AD%E5%9C%86%E6%A3%80%E6%B5%8B%E4%B8%8E%E6%8B%9F%E5%90%88/</id>
    <published>2018-01-07T08:54:27.000Z</published>
    <updated>2022-09-06T10:21:03.948Z</updated>
    
    <content type="html"><![CDATA[<!-- # 椭圆检测与拟合 --><span id="more"></span><h2 id="实验目标"><a href="#实验目标" class="headerlink" title="实验目标"></a>实验目标</h2><ul><li>调⽤CvBox2D cvFitEllipse2( const CvArr* points )实现椭圆拟合</li></ul><h2 id="实验环境"><a href="#实验环境" class="headerlink" title="实验环境"></a>实验环境</h2><ul><li>Windows 10 1709</li><li>OpenCV 3.3</li></ul><h2 id="实验过程"><a href="#实验过程" class="headerlink" title="实验过程"></a>实验过程</h2><p>实现了一个<code>fitEllipse()</code>函数，函数原型如下：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">fitEllipse</span><span class="params">(<span class="keyword">char</span>* filename, <span class="keyword">int</span> threshold)</span></span>;</span><br></pre></td></tr></table></figure><p>传入图片路径，然后显示出图片椭圆拟合之后的效果。</p><p>支持命令行解析图片路径参数。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">FitEllipse.exe  test.png</span><br></pre></td></tr></table></figure><p>如果没有路径参数，默认时当前目录下的<code>test.png</code>。</p><p>首先把图片读进来，包括一份灰度图和一份原图。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Mat gray_img = <span class="built_in">imread</span>(filename, IMREAD_GRAYSCALE);</span><br><span class="line">Mat result = <span class="built_in">imread</span>(filename);</span><br></pre></td></tr></table></figure><p>把灰度图二值化：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Mat binary_img = gray_img &gt;= thresh;</span><br></pre></td></tr></table></figure><p>然后使用<code>findContours()</code>检测二值化图像的轮廓点。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">findContours</span>(binary_img, contours, RETR_LIST, CHAIN_APPROX_NONE);</span><br></pre></td></tr></table></figure><p>其中，参数3可以取值为：</p><ul><li>RETR_EXTERNEL: 只检测最外围轮廓，包含在外围轮廓内的内围轮廓被忽略</li><li>RETR_LIST: 检测所有的轮廓，包括内围、外围轮廓，但是检测到的轮廓不建立等级关系，彼此之间独立，没有等级关系，这就意味着这个检索模式下不存在父轮廓或内嵌轮廓</li><li>RETR_CCOMP:  检测所有的轮廓，但所有轮廓只建立两个等级关系，外围为顶层，若外围内的内围轮廓还包含了其他的轮廓信息，则内围内的所有轮廓均归属于顶层</li><li>RETR_TREE: 检测所有轮廓，所有轮廓建立一个等级树结构。外层轮廓包含内层轮廓，内层轮廓还可以继续包含内嵌轮廓。</li></ul><p>这里我们只选择<code>RETR_LIST</code>即可满足椭圆拟合的要求。</p><p>参数4可以取值为:</p><ul><li>CHAIN_APPROX_NONE 保存物体边界上所有连续的轮廓点到contours向量内</li><li>CHAIN_APPROX_SIMPLE 仅保存轮廓的拐点信息，把所有轮廓拐点处的点保存入contours向量内，拐点与拐点之间直线段上的信息点不予保留</li></ul><ul><li>CHAIN_APPROX_TC89_L1, CHAIN_APPROX_TC89_KCOS: 使用teh-Chin Chain 近似算法</li></ul><p>这里直接选择简单的<code>CHAIN_APPROX_NONE</code>。</p><p>然后对于检测出的轮廓点，用椭圆去拟合：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">for</span> <span class="title">each</span> <span class="params">(<span class="keyword">auto</span> contour in contours)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (contour.<span class="built_in">size</span>() &lt; <span class="number">6</span>) <span class="keyword">continue</span>;</span><br><span class="line">  RotatedRect box = <span class="built_in">fitEllipse</span>(contour);</span><br><span class="line">  <span class="built_in">ellipse</span>(result, box, <span class="built_in">Scalar</span>(<span class="number">0</span>, <span class="number">255</span>, <span class="number">255</span>), <span class="number">1</span>, LINE_AA);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>椭圆的拟合至少需要6个点，所以把少于6个点的检测结果直接丢弃，然后对于剩下的点用<code>cv2::fitEllipse()</code>来拟合，然后把椭圆绘制在原图上。</p><p>之后再保存结果就行了。</p><h2 id="实验结果"><a href="#实验结果" class="headerlink" title="实验结果"></a>实验结果</h2><p>原图：</p><p><img src="/assets/611478ffgy1fn85ch9615j206s06sjsz.jpg" alt="test.png"></p><p>结果：</p><p><img src="/assets/611478ffgy1fn85cvialgj206s06swhz.jpg" alt="result.png"></p><p>可以看到椭圆基本上都检测并拟合出来了。</p><h2 id="心得体会"><a href="#心得体会" class="headerlink" title="心得体会"></a>心得体会</h2><p>这次实验就是先检测出图像的轮廓点，然后用<code>fitEllipse()</code>函数来拟合椭圆，整体不是太难。然后注意到一点就是<code>imread()</code>读入图片的时候第二个参数可以选择读入的模式，可以用<code>IMREAD_GRAYSCALE</code>让其读入单通道的图片矩阵数据。</p><h2 id="附：源代码"><a href="#附：源代码" class="headerlink" title="附：源代码"></a>附：源代码</h2><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// main.cpp</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;opencv2/opencv.hpp&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> cv;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">fitEllipse</span><span class="params">(<span class="keyword">char</span>* filename, <span class="keyword">int</span> threshold)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">char</span>** argv)</span> </span>&#123;</span><br><span class="line"><span class="keyword">char</span>* filename;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (argc == <span class="number">2</span>) &#123;</span><br><span class="line">filename = argv[<span class="number">1</span>];</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span> &#123;</span><br><span class="line">filename = <span class="string">&quot;test.png&quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="built_in">fitEllipse</span>(filename, <span class="number">150</span>);</span><br><span class="line"><span class="built_in">cvWaitKey</span>(<span class="number">0</span>);</span><br><span class="line"><span class="built_in">destroyAllWindows</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">fitEllipse</span><span class="params">(<span class="keyword">char</span>* filename, <span class="keyword">int</span> thresh)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">Mat gray_img = <span class="built_in">imread</span>(filename, IMREAD_GRAYSCALE);</span><br><span class="line">Mat result = <span class="built_in">imread</span>(filename);</span><br><span class="line">vector&lt;vector&lt;Point&gt;&gt; contours;</span><br><span class="line">Mat binary_img = gray_img &gt;= thresh;</span><br><span class="line"><span class="built_in">findContours</span>(binary_img, contours, RETR_LIST, CHAIN_APPROX_NONE);</span><br><span class="line"><span class="function"><span class="keyword">for</span> <span class="title">each</span> <span class="params">(<span class="keyword">auto</span> contour in contours)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (contour.<span class="built_in">size</span>() &lt; <span class="number">6</span>) <span class="keyword">continue</span>;</span><br><span class="line">RotatedRect box = <span class="built_in">fitEllipse</span>(contour);</span><br><span class="line"><span class="built_in">ellipse</span>(result, box, <span class="built_in">Scalar</span>(<span class="number">0</span>, <span class="number">255</span>, <span class="number">255</span>), <span class="number">1</span>, LINE_AA);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">imwrite</span>(<span class="string">&quot;result.png&quot;</span>, result);</span><br><span class="line"><span class="built_in">imshow</span>(filename, gray_img);</span><br><span class="line"><span class="built_in">imshow</span>(<span class="string">&quot;result&quot;</span>, result);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;!-- # 椭圆检测与拟合 --&gt;</summary>
    
    
    
    <category term="学习笔记" scheme="https://archived.yanghan.life/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="学习笔记" scheme="https://archived.yanghan.life/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="OpenCV" scheme="https://archived.yanghan.life/tags/OpenCV/"/>
    
    <category term="视觉" scheme="https://archived.yanghan.life/tags/%E8%A7%86%E8%A7%89/"/>
    
  </entry>
  
  <entry>
    <title>Harris Corner Detector</title>
    <link href="https://archived.yanghan.life/2017/12/20/Harris-Corner-Detector/"/>
    <id>https://archived.yanghan.life/2017/12/20/Harris-Corner-Detector/</id>
    <published>2017-12-20T15:22:32.000Z</published>
    <updated>2022-09-06T10:00:10.366Z</updated>
    
    <content type="html"><![CDATA[<!-- # Harris Corner Detector 实验报告 --><!-- ## 实验目的 --><p>实现Harris Corner Detector，输出结果以及中间过程。</p><span id="more"></span><h2 id="实验过程"><a href="#实验过程" class="headerlink" title="实验过程"></a>实验过程</h2><p>实现了一个<code>HarrisCornerDetector</code>函数，函数原型如下：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">HarrisCornerDetector</span><span class="params">(Mat&amp; src, Mat&amp; R, <span class="keyword">int</span> aperture_size, <span class="keyword">double</span> k)</span></span>;</span><br></pre></td></tr></table></figure><p>整体实现过程按照<code>HarrisCornerDetector</code>的运算过程来。</p><p>先将彩色图片转为单通道的灰度图，便于计算。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cvtColor</span>(src, src_gray, cv::COLOR_BGR2GRAY);</span><br></pre></td></tr></table></figure><p>然后计算X和Y方向的导数，本质上是用一个算子做一下卷积，我这里使用了Sobel算子，会根据aperture_size生成对应的模板来计算近似的导数。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Sobel</span>(src_gray, Ix, CV_32FC1, <span class="number">1</span>, <span class="number">0</span>, aperture_size);</span><br><span class="line"><span class="built_in">Sobel</span>(src_gray, Iy, CV_32FC1, <span class="number">0</span>, <span class="number">1</span>, aperture_size);</span><br></pre></td></tr></table></figure><p>然后就对整张导数的图来计算<code>IxIx, IxIy, IyIy</code>:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; size.height; ++i) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j &lt; size.width; ++j) &#123;</span><br><span class="line">        IxIx.at&lt;<span class="keyword">float</span>&gt;(i,j) = Ix.at&lt;<span class="keyword">float</span>&gt;(i, j) * Ix.at&lt;<span class="keyword">float</span>&gt;(i, j);</span><br><span class="line">        IxIy.at&lt;<span class="keyword">float</span>&gt;(i,j) = Ix.at&lt;<span class="keyword">float</span>&gt;(i, j) * Iy.at&lt;<span class="keyword">float</span>&gt;(i, j);</span><br><span class="line">        IyIy.at&lt;<span class="keyword">float</span>&gt;(i,j) = Iy.at&lt;<span class="keyword">float</span>&gt;(i, j) * Iy.at&lt;<span class="keyword">float</span>&gt;(i, j);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>如此就得到了这个矩阵的值：</p><p>$$<br>\begin{vmatrix}<br>I_xI_x  &amp; I_xI_y \<br>I_xI_y  &amp; I_yI_y<br>\end{vmatrix}<br>$$</p><p>然后就是用W[x,y] 来求和</p><p>$$<br>\sum W(x,y)  \begin{vmatrix}<br>I_xI_x  &amp; I_xI_y \<br>I_xI_y  &amp; I_yI_y<br>\end{vmatrix}<br>$$<br>W(x,y)可以取高斯滤波函数。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">Size <span class="title">block</span><span class="params">(<span class="number">3</span>,<span class="number">3</span>)</span></span>;</span><br><span class="line"><span class="built_in">GaussianBlur</span>(IxIx, IxIx, block, <span class="number">0</span>);</span><br><span class="line"><span class="built_in">GaussianBlur</span>(IxIy, IxIy, block, <span class="number">0</span>);</span><br><span class="line"><span class="built_in">GaussiaBlur</span>(IyIy, IyIy, block, <span class="number">0</span>);</span><br></pre></td></tr></table></figure><p>然后就可以求得到的矩阵的特征值。</p><p>因为是2x2的矩阵，特征方程就是<code> λ^2-(a+d)λ+ad-bc=0</code>,  直接使用求根公式来求特征值，用韦达定理可以得到R的值。<br>$$<br>R = det(H) - k \times trace(H) ^2 \<br>$$</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; size.height; ++i) &#123;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j &lt; size.width; ++j) &#123;</span><br><span class="line"><span class="keyword">float</span> a = IxIx.at&lt;<span class="keyword">float</span>&gt;(i, j);</span><br><span class="line"><span class="keyword">float</span> b = IxIy.at&lt;<span class="keyword">float</span>&gt;(i, j);</span><br><span class="line"><span class="keyword">float</span> c = b;</span><br><span class="line"><span class="keyword">float</span> d = IyIy.at&lt;<span class="keyword">float</span>&gt;(i, j);</span><br><span class="line"><span class="comment">// 2-D mat a b c d</span></span><br><span class="line"><span class="comment">// λ^2-(a+d)λ+ad-bc=0</span></span><br><span class="line"><span class="comment">// λ1 + λ2 = a+d</span></span><br><span class="line"><span class="comment">// λ1 * λ2 = ad-bc</span></span><br><span class="line">R.at&lt;<span class="keyword">float</span>&gt;(i,j) = (a*d - b*c) - k*(a + d)*(a + d);</span><br><span class="line">largeEigen.at&lt;<span class="keyword">float</span>&gt;(i, j) = ((a + d) + <span class="built_in">sqrt</span>((a + d)*(a + d) - <span class="number">4</span> * (a*d - b*c))) / <span class="number">2</span>;</span><br><span class="line">smallEigen.at&lt;<span class="keyword">float</span>&gt;(i, j) = ((a + d) - <span class="built_in">sqrt</span>((a + d)*(a + d) - <span class="number">4</span> * (a*d - b*c))) / <span class="number">2</span>;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后就可以根据R矩阵的值来画出检测出是角的点。</p><p>画的时候为避免多个点聚集，使用了Non Maximum Suppression, 只取一定范围内R值最大的点作为角的特征点。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; size.height; ++i) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j &lt; size.width; ++j) &#123;</span><br><span class="line">        <span class="keyword">if</span> ((<span class="keyword">int</span>)R.at&lt;uchar&gt;(i, j) &gt; threshold) &#123;</span><br><span class="line">            <span class="comment">// Non Maximum Suppression</span></span><br><span class="line">            <span class="keyword">if</span> (R.at&lt;uchar&gt;(i, j) == <span class="built_in">maxValue</span>(R, NMS_size, i, j)) &#123;</span><br><span class="line">              <span class="built_in">circle</span>(src, <span class="built_in">Point</span>(j, i), <span class="number">5</span>, <span class="built_in">Scalar</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">255.0</span>), <span class="number">2</span>, <span class="number">8</span>, <span class="number">0</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>最后展示结果并存储结果</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">imshow</span>(<span class="string">&quot;LargeEigen&quot;</span>, largeEigen);</span><br><span class="line"><span class="built_in">imshow</span>(<span class="string">&quot;SmallEigen&quot;</span>, smallEigen);</span><br><span class="line"><span class="built_in">imshow</span>(<span class="string">&quot;R&quot;</span>, R);</span><br><span class="line"><span class="built_in">imshow</span>(<span class="string">&quot;result&quot;</span>, src);</span><br><span class="line"></span><br><span class="line"><span class="built_in">waitKey</span>(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">imwrite</span>(<span class="string">&quot;LargeEigen.png&quot;</span>, largeEigen);</span><br><span class="line"><span class="built_in">imwrite</span>(<span class="string">&quot;SmallEigen.png&quot;</span>, smallEigen);</span><br><span class="line"><span class="built_in">imwrite</span>(<span class="string">&quot;R.png&quot;</span>, R);</span><br><span class="line"><span class="built_in">imwrite</span>(<span class="string">&quot;result.png&quot;</span>, src);</span><br></pre></td></tr></table></figure><h2 id="实验结果"><a href="#实验结果" class="headerlink" title="实验结果"></a>实验结果</h2><p>略。</p><h2 id="心得体会"><a href="#心得体会" class="headerlink" title="心得体会"></a>心得体会</h2><p>实验中实现了 <code>Harris Corner Detector</code>，充分体会了这样的角点检测的运算过程。在实现的过程中，深刻理解了Harris的这种检测的原理，推导了计算的公式，加深了理解。实验的结果也符合预期。</p><p>在代码的编写过程中也进一步熟悉了OpenCV的使用，可以熟练地使用OpenCV进行图片的处理和卷积运算，提高了使用的熟练度。</p><h2 id="附：源代码"><a href="#附：源代码" class="headerlink" title="附：源代码"></a>附：源代码</h2><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;opencv2/opencv.hpp&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;string&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;cmath&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> cv;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">HarrisCornerDetector</span><span class="params">(Mat&amp; src, Mat&amp; R, <span class="keyword">int</span> aperture_size, <span class="keyword">double</span> k)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">const</span> <span class="keyword">char</span>** argv)</span> </span>&#123;</span><br><span class="line"><span class="keyword">char</span>* filename = <span class="keyword">new</span> <span class="keyword">char</span>[<span class="number">100</span>];</span><br><span class="line"><span class="comment">//system(&quot;dir&quot;);</span></span><br><span class="line"><span class="keyword">int</span> apertureSize = <span class="number">3</span>;</span><br><span class="line"><span class="keyword">double</span> k = <span class="number">0.04</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (argc != <span class="number">4</span>) &#123;</span><br><span class="line">cout &lt;&lt; <span class="string">&quot;Illegal Input.&quot;</span> &lt;&lt; endl;</span><br><span class="line">cout &lt;&lt; <span class="string">&quot;HarrisDetector.exe $path $k $aperture_size.&quot;</span> &lt;&lt; endl;</span><br><span class="line"><span class="comment">//cout &lt;&lt; &quot;using default settings.&quot; &lt;&lt; endl;</span></span><br><span class="line"><span class="comment">//filename = &quot;sample.png&quot;;</span></span><br><span class="line">filename = <span class="string">&quot;Sydney_Opera_House_Sails_edit02_adj.jpg&quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span> &#123;</span><br><span class="line"> <span class="comment">// parse commandline args</span></span><br><span class="line"><span class="built_in">sscanf_s</span>(argv[<span class="number">1</span>], <span class="string">&quot;%s&quot;</span>, filename, <span class="number">99</span>);</span><br><span class="line"><span class="built_in">sscanf_s</span>(argv[<span class="number">2</span>], <span class="string">&quot;%lf&quot;</span>, &amp;k);</span><br><span class="line"><span class="built_in">sscanf_s</span>(argv[<span class="number">3</span>], <span class="string">&quot;%d&quot;</span>, &amp;apertureSize);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">Mat src = <span class="built_in">imread</span>(filename);</span><br><span class="line"><span class="keyword">if</span> (!src.data) &#123;</span><br><span class="line">cout &lt;&lt; <span class="string">&quot;imread failed&quot;</span> &lt;&lt; endl;</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line">Mat dst;</span><br><span class="line"></span><br><span class="line">cout &lt;&lt; <span class="string">&quot;path: &quot;</span> &lt;&lt; filename &lt;&lt; endl;</span><br><span class="line">cout &lt;&lt; <span class="string">&quot;k: &quot;</span> &lt;&lt; k &lt;&lt; endl;</span><br><span class="line">cout &lt;&lt; <span class="string">&quot;aperture_size: &quot;</span> &lt;&lt; apertureSize &lt;&lt; endl;</span><br><span class="line"></span><br><span class="line"><span class="built_in">HarrisCornerDetector</span>(src, dst, apertureSize, k);</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">maxValue</span><span class="params">(Mat&amp; img, <span class="keyword">int</span> size, <span class="keyword">int</span> y, <span class="keyword">int</span> x)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">uchar maxval = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = y-size; i&lt;y + size; ++i)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">if</span> (i &lt; <span class="number">0</span> || i &gt;= img.rows)<span class="keyword">continue</span>;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> j = x-size; j&lt; x + size; ++j)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">if</span> (j&lt;<span class="number">0</span> || j &gt;= img.cols)<span class="keyword">continue</span>;</span><br><span class="line"><span class="keyword">if</span> (img.at&lt;uchar&gt;(i,j) &gt; maxval)</span><br><span class="line">&#123;</span><br><span class="line">maxval = img.at&lt;uchar&gt;(i, j);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//cout &lt;&lt; &quot;maxval&quot; &lt;&lt; (int)maxval &lt;&lt; endl;</span></span><br><span class="line"><span class="keyword">return</span> maxval;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">HarrisCornerDetector</span><span class="params">(Mat&amp; src, Mat&amp; R, <span class="keyword">int</span> aperture_size, <span class="keyword">double</span> k)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">Mat src_gray;</span><br><span class="line"><span class="comment">// convert to gray</span></span><br><span class="line"><span class="built_in">cvtColor</span>(src, src_gray, cv::COLOR_BGR2GRAY);</span><br><span class="line"><span class="comment">// normalize src</span></span><br><span class="line"><span class="built_in">normalize</span>(src_gray, src_gray, <span class="number">0</span>, <span class="number">255</span>, NORM_MINMAX);</span><br><span class="line"><span class="built_in">convertScaleAbs</span>(src_gray, src_gray);</span><br><span class="line"></span><br><span class="line">R.<span class="built_in">create</span>(src_gray.<span class="built_in">size</span>(), CV_32FC1);</span><br><span class="line">Mat Ix, Iy;</span><br><span class="line"></span><br><span class="line"><span class="comment">//sobel operation get Ix, Iy </span></span><br><span class="line"><span class="built_in">Sobel</span>(src_gray, Ix, CV_32FC1, <span class="number">1</span>, <span class="number">0</span>, aperture_size);</span><br><span class="line"><span class="built_in">Sobel</span>(src_gray, Iy, CV_32FC1, <span class="number">0</span>, <span class="number">1</span>, aperture_size);</span><br><span class="line"><span class="comment">//cout &lt;&lt; Ix.size() &lt;&lt; &quot; &quot; &lt;&lt; Ix.channels() &lt;&lt; &quot; &quot; &lt;&lt; Ix.depth() &lt;&lt; endl;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// prepare Mat to store info</span></span><br><span class="line"><span class="function">Mat <span class="title">IxIx</span><span class="params">(src_gray.size(), CV_32FC1)</span></span>;</span><br><span class="line"><span class="function">Mat <span class="title">IxIy</span><span class="params">(src_gray.size(), CV_32FC1)</span></span>;</span><br><span class="line"><span class="function">Mat <span class="title">IyIy</span><span class="params">(src_gray.size(), CV_32FC1)</span></span>;</span><br><span class="line"><span class="function">Mat <span class="title">largeEigen</span><span class="params">(src_gray.size(), CV_32FC1)</span></span>;</span><br><span class="line"><span class="function">Mat <span class="title">smallEigen</span><span class="params">(src_gray.size(), CV_32FC1)</span></span>;</span><br><span class="line"><span class="comment">//Mat heat(src_gray.size(), CV_8SC3);</span></span><br><span class="line"></span><br><span class="line">Size size = src_gray.<span class="built_in">size</span>();</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; size.height; ++i) &#123;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j &lt; size.width; ++j) &#123;</span><br><span class="line">IxIx.at&lt;<span class="keyword">float</span>&gt;(i,j) = Ix.at&lt;<span class="keyword">float</span>&gt;(i, j) * Ix.at&lt;<span class="keyword">float</span>&gt;(i, j);</span><br><span class="line">IxIy.at&lt;<span class="keyword">float</span>&gt;(i,j) = Ix.at&lt;<span class="keyword">float</span>&gt;(i, j) * Iy.at&lt;<span class="keyword">float</span>&gt;(i, j);</span><br><span class="line">IyIy.at&lt;<span class="keyword">float</span>&gt;(i,j) = Iy.at&lt;<span class="keyword">float</span>&gt;(i, j) * Iy.at&lt;<span class="keyword">float</span>&gt;(i, j);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// W[x,y] * I</span></span><br><span class="line"><span class="comment">// W[x,y] is Gaussian Filter</span></span><br><span class="line"><span class="function">Size <span class="title">block</span><span class="params">(<span class="number">3</span>,<span class="number">3</span>)</span></span>;</span><br><span class="line"><span class="built_in">GaussianBlur</span>(IxIx, IxIx, block, <span class="number">0</span>);</span><br><span class="line"><span class="built_in">GaussianBlur</span>(IxIy, IxIy, block, <span class="number">0</span>);</span><br><span class="line"><span class="built_in">GaussianBlur</span>(IyIy, IyIy, block, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; size.height; ++i) &#123;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j &lt; size.width; ++j) &#123;</span><br><span class="line"><span class="keyword">float</span> a = IxIx.at&lt;<span class="keyword">float</span>&gt;(i, j);</span><br><span class="line"><span class="keyword">float</span> b = IxIy.at&lt;<span class="keyword">float</span>&gt;(i, j);</span><br><span class="line"><span class="keyword">float</span> c = b;</span><br><span class="line"><span class="keyword">float</span> d = IyIy.at&lt;<span class="keyword">float</span>&gt;(i, j);</span><br><span class="line"><span class="comment">// 2-D mat a b c d</span></span><br><span class="line"><span class="comment">// λ^2-(a+d)λ+ad-bc=0</span></span><br><span class="line"><span class="comment">// λ1 + λ2 = a+d</span></span><br><span class="line"><span class="comment">// λ1 * λ2 = ad-bc</span></span><br><span class="line">R.at&lt;<span class="keyword">float</span>&gt;(i,j) = (a*d - b*c) - k*(a + d)*(a + d);</span><br><span class="line">largeEigen.at&lt;<span class="keyword">float</span>&gt;(i, j) = ((a + d) + <span class="built_in">sqrt</span>((a + d)*(a + d) - <span class="number">4</span> * (a*d - b*c))) / <span class="number">2</span>;</span><br><span class="line">smallEigen.at&lt;<span class="keyword">float</span>&gt;(i, j) = ((a + d) - <span class="built_in">sqrt</span>((a + d)*(a + d) - <span class="number">4</span> * (a*d - b*c))) / <span class="number">2</span>;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">cout &lt;&lt; endl;</span><br><span class="line"><span class="built_in">normalize</span>(R, R, <span class="number">0</span>, <span class="number">255</span>, NORM_MINMAX, CV_32FC1, <span class="built_in">Mat</span>());</span><br><span class="line"><span class="built_in">convertScaleAbs</span>(R, R);</span><br><span class="line"><span class="keyword">int</span> threshold = <span class="number">50</span>;</span><br><span class="line"><span class="keyword">int</span> NMS_size = <span class="number">15</span>;</span><br><span class="line"><span class="comment">// Draw circles with NMS</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; size.height; ++i) &#123;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j &lt; size.width; ++j) &#123;</span><br><span class="line"><span class="keyword">if</span> ((<span class="keyword">int</span>)R.at&lt;uchar&gt;(i, j) &gt; threshold) &#123;</span><br><span class="line"><span class="comment">// Non Maximum Suppression</span></span><br><span class="line"><span class="keyword">if</span> (R.at&lt;uchar&gt;(i, j) == <span class="built_in">maxValue</span>(R, NMS_size, i, j)) &#123;</span><br><span class="line"><span class="built_in">circle</span>(src, <span class="built_in">Point</span>(j, i), <span class="number">5</span>, <span class="built_in">Scalar</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">255.0</span>), <span class="number">2</span>, <span class="number">8</span>, <span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Show the result</span></span><br><span class="line"><span class="built_in">imshow</span>(<span class="string">&quot;LargeEigen&quot;</span>, largeEigen);</span><br><span class="line"><span class="built_in">imshow</span>(<span class="string">&quot;SmallEigen&quot;</span>, smallEigen);</span><br><span class="line"><span class="comment">//imshow(&quot;heat&quot;, heat);</span></span><br><span class="line"><span class="built_in">imshow</span>(<span class="string">&quot;R&quot;</span>, R);</span><br><span class="line"><span class="built_in">imshow</span>(<span class="string">&quot;result&quot;</span>, src);</span><br><span class="line"></span><br><span class="line"><span class="built_in">waitKey</span>(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">imwrite</span>(<span class="string">&quot;LargeEigen.png&quot;</span>, largeEigen);</span><br><span class="line"><span class="built_in">imwrite</span>(<span class="string">&quot;SmallEigen.png&quot;</span>, smallEigen);</span><br><span class="line"><span class="built_in">imwrite</span>(<span class="string">&quot;R.png&quot;</span>, R);</span><br><span class="line"><span class="built_in">imwrite</span>(<span class="string">&quot;result.png&quot;</span>, src);</span><br><span class="line"><span class="built_in">destroyAllWindows</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;!-- # Harris Corner Detector 实验报告 --&gt;

&lt;!-- ## 实验目的 --&gt;

&lt;p&gt;实现Harris Corner Detector，输出结果以及中间过程。&lt;/p&gt;</summary>
    
    
    
    <category term="学习笔记" scheme="https://archived.yanghan.life/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="学习笔记" scheme="https://archived.yanghan.life/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="CV" scheme="https://archived.yanghan.life/tags/CV/"/>
    
  </entry>
  
  <entry>
    <title>Change CUDA / Cudnn version without root privileges</title>
    <link href="https://archived.yanghan.life/2017/12/10/Change-CUDA-Cudnn-version-without-root-privileges/"/>
    <id>https://archived.yanghan.life/2017/12/10/Change-CUDA-Cudnn-version-without-root-privileges/</id>
    <published>2017-12-10T12:31:13.000Z</published>
    <updated>2022-09-06T10:00:10.366Z</updated>
    
    <content type="html"><![CDATA[<!-- # Change CUDA / Cudnn version without root privileges --><p>On a machine that is used publicly, one can’t update its cuda or cudnn version arbitrarily. Here is a way you can use the version you need.</p><span id="more"></span><p>Without root privileges, you can do this to make your code run well:</p><p>Below is the example of cudnn:(Cuda can be done in the same way.)</p><p>Because the essence of cuda or cudnn is dynamic link library, so all you need do is to make your machine know where to link it properly.</p><p>You can download and extract the proper version of cuda or cudnn to your <code>~</code> directory (or anywhere you like).</p><p>Then append these to your <code>~/.bashrc</code> (or something like <code>.zshrc</code> etc.)</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">export PATH=/usr/local/cuda/bin:$PATH</span><br><span class="line">export CUDA_HOME=/usr/local/cuda</span><br><span class="line">export LD_LIBRARY_PATH=~/cuda/lib64:$LD_LIBRARY_PATH</span><br><span class="line">export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda/lib64</span><br></pre></td></tr></table></figure><p>Then <code>source .bashrc</code> will make it work perfectly.</p>]]></content>
    
    
    <summary type="html">&lt;!-- # Change CUDA / Cudnn version without root privileges --&gt;

&lt;p&gt;On a machine that is used publicly, one can’t update its cuda or cudnn version arbitrarily. Here is a way you can use the version you need.&lt;/p&gt;</summary>
    
    
    
    <category term="学习笔记" scheme="https://archived.yanghan.life/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="学习笔记" scheme="https://archived.yanghan.life/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="cudnn" scheme="https://archived.yanghan.life/tags/cudnn/"/>
    
    <category term="cuda" scheme="https://archived.yanghan.life/tags/cuda/"/>
    
  </entry>
  
  <entry>
    <title>Windows 重建引导</title>
    <link href="https://archived.yanghan.life/2017/11/30/Windows-%E9%87%8D%E5%BB%BA%E5%BC%95%E5%AF%BC/"/>
    <id>https://archived.yanghan.life/2017/11/30/Windows-%E9%87%8D%E5%BB%BA%E5%BC%95%E5%AF%BC/</id>
    <published>2017-11-29T16:38:35.000Z</published>
    <updated>2022-09-06T10:00:10.366Z</updated>
    
    <content type="html"><![CDATA[<!-- # Windows 重建引导 --><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">bcdboot C:\Windows</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;!-- # Windows 重建引导 --&gt;

&lt;figure class=&quot;highlight shell&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td cl</summary>
      
    
    
    
    <category term="学习笔记" scheme="https://archived.yanghan.life/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="学习笔记" scheme="https://archived.yanghan.life/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="Windows" scheme="https://archived.yanghan.life/tags/Windows/"/>
    
  </entry>
  
  <entry>
    <title>University Course Timetabling Problem的一些笔记</title>
    <link href="https://archived.yanghan.life/2017/08/28/University-Course-Timetabling-Problem%E7%9A%84%E4%B8%80%E4%BA%9B%E7%AC%94%E8%AE%B0/"/>
    <id>https://archived.yanghan.life/2017/08/28/University-Course-Timetabling-Problem%E7%9A%84%E4%B8%80%E4%BA%9B%E7%AC%94%E8%AE%B0/</id>
    <published>2017-08-28T02:01:51.000Z</published>
    <updated>2022-09-06T10:00:10.366Z</updated>
    
    <content type="html"><![CDATA[<!-- # University Course Timetabling Problem的一些笔记 --><span id="more"></span><h2 id="University-Course-Timetabling-Problem"><a href="#University-Course-Timetabling-Problem" class="headerlink" title="University Course Timetabling Problem"></a>University Course Timetabling Problem</h2><ul><li>H1: Only one event is assigned to each room at any timeslot.</li><li>H2: The room is big enough for hosting all attending students and satisfies all the features required by the event.</li><li>H3: No student attends more than one event at the same time.</li></ul><p>In addition, a candidate timetable receives a penalty cost for violating any of the following three soft constraints:</p><ul><li>S1: A student should not have a class in the last slot of a day.</li><li>S2: A student should not have more than two classes in a row.</li><li>S3: A student should not have a single class on a day.</li></ul><hr><h2 id="A-Hybrid-Algorithm-for-UCTP"><a href="#A-Hybrid-Algorithm-for-UCTP" class="headerlink" title="A Hybrid Algorithm for UCTP"></a>A Hybrid Algorithm for UCTP</h2><p>hybrid algorithm mainly based on construction heuristics and meta-heuristics</p><p>The algorithm deals separately with hard and soft constraints.</p><ul><li>The hard constraints: <ul><li>Local search and tabu search procedures</li></ul></li></ul><ul><li>The soft constraints: <ul><li>Variable neighborhood descent and simulated annealing</li></ul></li></ul><p>In particular, simulated annealing plays a significant role. </p><p>The algorithm was developed, configured and tuned through the race-based experimental methodology.</p><hr><h2 id="heuristic-algorithm"><a href="#heuristic-algorithm" class="headerlink" title="heuristic algorithm"></a>heuristic algorithm</h2><p>The use of <strong>experience</strong> and <strong>practical efforts</strong> to find answers to questions or to improve performance</p><p>维基百科词条heuristic，将其定义为<strong>基于经验</strong>的技巧（technique），用于解决问题、学习和探索。并对该词进行了更详尽的解释并罗列了多个相关领域：</p><blockquote><p> A heuristic method is used to <strong>rapidly</strong> come to a solution that is hoped to be close to the best possible answer, or ‘<strong>optimal solution’</strong>. A heuristic is a “<strong>rule of thumb</strong>“, an educated <strong>guess</strong>, an <strong>intuitive judgment</strong> or simply <strong>common sense</strong>.</p></blockquote><blockquote><p> A heuristic is a general way of solving a problem. Heuristics as a noun is another name for heuristic methods.</p></blockquote><p>Heuristic可以等同于：实际经验估计（rule of thumb）、有依据的猜测（educated guess, a guess beased on a certain amount of information, and therefore likely to be right）和常识（由经验得来的判断力）。</p><blockquote><p>驾驶汽车到达某人的家，写成算法是这样的：沿167 号高速公路往南行至Puyallup；从South Hill Mall 出口出来后往山上开 4.5 英里； 在一个杂物店旁边的红绿灯路口右转，接着在第一个路口左转；从左边褐色大房子的车道进去，就是North Cedar 路714 号。</p></blockquote><blockquote><p>用启发式方法来描述则可能是这样：找出上一次我们寄给你的信，照着信上面的寄出地址开车到这个镇；到了之后你问一下我们的房子在哪里。 这里每个人都认识我们——肯定有人会很愿意帮助你的；如果你找不到人，那就找个公共电话亭给我们打电话，我们会出来接你。</p></blockquote><p><strong>启发式策略</strong>（heuristic）是一类在求解某个具体问题时，在可以接受的时间和空间内能给出其可行解，但又不保证求得最优解（以及可行解与最优解的偏离）的策略的总称。许多启发式算法是相当特殊的，依赖于某个特定问题。启发式策略在一个寻求最优解的过程中能够根据个体或者全局的经验来改变其搜索路径，当寻求问题的最优解变得不可能或者很难完成时（e.g. NP-Complete 问题），启发式策略就是一个高效的获得可行解的办法。</p><p><strong>元启发式策略</strong>（metaheuristic）则不同，元启发式策略通常是一个通用的启发式策略，他们通常不借助于某种问题的特有条件，从而能够运用于更广泛的方面。元启发式策略通常会对搜索过程提出一些要求，然后按照这些要求实现的启发式算法便被称为元启发式算法。许多元启发式算法都从自然界的一些随机现象取得灵感（e.g. 模拟退火、遗传算法）。现在元启发式算法的重要研究方向在于防止搜索过早得陷入局部最优，已经有很多人做了相应的工作，例如禁忌搜索（tabu）和非改进转移（模拟退火）。</p><p>作者：王斌 链接：<a href="https://www.zhihu.com/question/36635796/answer/70528089">https://www.zhihu.com/question/36635796/answer/70528089</a></p><p>来源：知乎著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。</p><hr><h2 id="模拟退火"><a href="#模拟退火" class="headerlink" title="模拟退火"></a>模拟退火</h2><p><a href="http://www.cnblogs.com/heaad/archive/2010/12/20/1911614.html">http://www.cnblogs.com/heaad/archive/2010/12/20/1911614.html</a></p><h3 id="爬山算法"><a href="#爬山算法" class="headerlink" title="爬山算法"></a>爬山算法</h3><p>只能接受下一个解比当前解好的情况</p><h3 id="模拟退火-1"><a href="#模拟退火-1" class="headerlink" title="模拟退火"></a>模拟退火</h3><p>以一定概率接受比当前解差的解，此概率随时间经过逐渐变小</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">* J(y)：在状态y时的评价函数值</span></span><br><span class="line"><span class="comment">* Y(i)：表示当前状态</span></span><br><span class="line"><span class="comment">* Y(i+1)：表示新的状态</span></span><br><span class="line"><span class="comment">* r： 用于控制降温的快慢</span></span><br><span class="line"><span class="comment">* T： 系统的温度，系统初始应该要处于一个高温的状态</span></span><br><span class="line"><span class="comment">* T_min ：温度的下限，若温度T达到T_min，则停止搜索</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">while</span>( T &gt; T_min )</span><br><span class="line">&#123;</span><br><span class="line">　　dE = <span class="built_in">J</span>( <span class="built_in">Y</span>(i+<span class="number">1</span>) ) - <span class="built_in">J</span>( <span class="built_in">Y</span>(i) ) ; </span><br><span class="line"></span><br><span class="line">　　<span class="keyword">if</span> ( dE &gt;=<span class="number">0</span> ) <span class="comment">//表达移动后得到更优解，则总是接受移动</span></span><br><span class="line"><span class="built_in">Y</span>(i+<span class="number">1</span>) = <span class="built_in">Y</span>(i) ; <span class="comment">//接受从Y(i)到Y(i+1)的移动</span></span><br><span class="line">　　<span class="keyword">else</span></span><br><span class="line">　　&#123;</span><br><span class="line">      <span class="comment">// 函数exp( dE/T )的取值范围是(0,2/w/611) ，dE/T越大，则exp( dE/T )也</span></span><br><span class="line">      <span class="keyword">if</span> ( <span class="built_in">exp</span>( dE/T ) &gt; <span class="built_in">random</span>( <span class="number">0</span> , <span class="number">1</span> ) )</span><br><span class="line">      <span class="built_in">Y</span>(i+<span class="number">1</span>) = <span class="built_in">Y</span>(i) ; <span class="comment">//接受从Y(i)到Y(i+1)的移动</span></span><br><span class="line">　　&#125;</span><br><span class="line">　　T = r * T ; <span class="comment">//降温退火 ，0&lt;r&lt;1 。r越大，降温越慢；r越小，降温越快</span></span><br><span class="line">　　<span class="comment">/*</span></span><br><span class="line"><span class="comment">　　* 若r过大，则搜索到全局最优解的可能会较高，但搜索的过程也就较长。若r过小，则搜索的过程会很快，但最终可能会达到一个局部最优值</span></span><br><span class="line"><span class="comment">　　*/</span></span><br><span class="line">　　i ++ ;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="禁忌搜索"><a href="#禁忌搜索" class="headerlink" title="禁忌搜索"></a>禁忌搜索</h2><p><a href="http://www.wangxianfeng.name/2012/08/intelligent-optimization-algorithms-tabu-search/">http://www.wangxianfeng.name/2012/08/intelligent-optimization-algorithms-tabu-search/</a></p><h3 id="禁忌搜索算法的基本思想：一群兔子如何寻找世界最高峰"><a href="#禁忌搜索算法的基本思想：一群兔子如何寻找世界最高峰" class="headerlink" title="禁忌搜索算法的基本思想：一群兔子如何寻找世界最高峰"></a>禁忌搜索算法的基本思想：一群兔子如何寻找世界最高峰</h3><p>一群兔子要寻找世界最高山峰，兔子们找到了泰山，它们之中的一只就会留守在这里，其他的再去别的地方寻找。就这样，一大圈后，把找到的几个山峰一一比较，珠穆朗玛峰脱颖而出。</p><ul><li><p>当兔子们再寻找的时候，一般地会有意识地避开泰山，因为他们知道，这里已经找过，并且有一只兔子在那里看着了，这就是禁忌搜索中的<em>“禁忌表（Tabu List）”</em>；</p></li><li><p>那只留在泰山的兔子一般不会就安家在那里了，它会在一定时间后重新回到找最高峰的大军，因为这个时候已经有了许多新的消息，泰山毕竟也有一个不错的高度，需要重新考虑，这个归队时间，就是禁忌搜索中的<em>“禁忌长度（Tabu Length）</em>”</p></li><li><p>如果在搜索的过程中，留守泰山的兔子还没有归队，但是找到的地方全是华北平原等比较低的地方，兔子们就不得不再次考虑选中泰山，也就是说，当一个有兔子留守的地方优越性太突出，超过了“best so far”的状态，就可以不顾及有没有兔子留守，都把这个地方考虑进来，这就是禁忌搜索中的<em>“特赦准则（aspiration criterion）”</em>。</p></li></ul><h3 id="禁忌搜索算法的基本流程"><a href="#禁忌搜索算法的基本流程" class="headerlink" title="禁忌搜索算法的基本流程"></a>禁忌搜索算法的基本流程</h3><p>  给定一个初始解和一种邻域，然后在当前解的邻域中确定若干候选解；若最佳候选解对应的目标值优于“best so far”状态，则忽视其禁忌特性，用其替代当前解和“best so far”状态，并将相应的对象加入禁忌表，同时修改禁忌表中各对象的任期；若不存在上述候选解，则选择在候选解中选择非禁忌的最佳状态为新的当前解，而无视它与当前解的优劣，同时将相应的对象加入禁忌表，并修改禁忌表中各对象的任期；如此重复上述迭代搜索过程，直至满足停止准则。条理化些，则简单禁忌搜索的算法步骤可描述如下：</p><pre><code>1. 按照随机方法产生一个初始解作为当前解X，置空禁忌表。2. 判断算法是否满足终止准则？若是，则停止算法并输出优化结果；否则，继续以下步骤。3. 利用邻域函数在当前解X的邻域N(X)中选出满足禁忌要求的候选解集C-N(X);4. 在候选解集C-N(X)中选一个评价值最好的解作为当前解Y=C-N(X)-best。5. 对候选解判断藐视准则是否满足？若成立，则用满足藐视准则的最佳状态Y替代x成为新的当前解，即X=Y，并用与Y对应的禁忌对象替换最早进入禁忌表的禁忌对象，同时用Y替换“best so far”状态，然后转步骤7；否则，继续以下步骤。6. 判断候选解对应的各对象的禁忌属性，选择候选解集中非禁忌对象对应的最佳状态为新的当前解，同时用与之对应的禁忌对象替换最早进入禁忌表的禁忌对象元素。7. 转到步骤2。</code></pre><p>  可以明显地看到，邻域函数、禁忌对象、禁忌表和藐视准则，构成了禁忌搜索算法的关键。其中，邻域函数沿用局部邻域搜索的思想，用于实现邻域搜索；禁忌表和禁忌对象的设置，体现了算法避免迂回搜索的特点；藐视准则，则是对优良状态的奖励，它是对禁忌策略的一种放松。</p><h3 id="禁忌搜索的关键参数"><a href="#禁忌搜索的关键参数" class="headerlink" title="禁忌搜索的关键参数"></a>禁忌搜索的关键参数</h3><ul><li>禁忌表：是用来存放禁忌对象的一个容器，放入禁忌表中的禁忌对象在解禁之前不能被再次搜索。禁忌表模拟了人的记忆机制，主要目的是阻止搜索过程中出现循环和避免陷入局部最优，进而探索更多搜索空间；</li><li>禁忌长度：可以为常数，也可以根据问题的规模确定；</li><li>评价函数：可以为直接评价函数，通过目标函数的运算得到评价函数；也可以是间接评价函数，构造其他评价函数替代目标函数（应反映目标函数的特性）减少计算复杂性.</li><li>藐视准则：它保证搜索过程在全部候选解被禁或者是有优于当前最优解的候选解被禁时，能够释放特定的解，从而实现全局优化搜索。当一个禁忌移动在随后T次的迭代内再度出现时，如果它能把搜索带到一个从未搜索过的区域，则应该接受该移动即破禁，不受禁忌表的限制。</li><li>终止规则：保证算法具有优良的优化性能和时间性能，可以<ul><li>(1) 确定步数终止，无法保证解的效果，应记录当前最优解；</li><li>(2) 频率控制原则，当某一个解、目标值或元素序列的频率超过一个给定值时，终止计算；</li><li>(3) 目标控制原则，如果在一个给定步数内，当前最优值没有变化，可终止计算。</li></ul></li></ul><hr><h2 id="Neighborhood-Structure"><a href="#Neighborhood-Structure" class="headerlink" title="Neighborhood Structure"></a>Neighborhood Structure</h2><ol><li>$N_1(a)$   move a single event to a different room and time slot. (an event involved in at least one hard constraint violated)</li><li>$N_2(a)$   swap timeslots and rooms of two events.(at least one causes a hard constraint violated.)</li></ol><p>Do not introduce violations of new the hard constraints.</p><p>$N_1’  \subseteq N_1$ , $N_2’ \subseteq N_2$</p><ol><li>$N_3(a)$   swap all events assigned to two timeslots</li><li>$N_4(a)$   (Kempe chain ?…待续)</li></ol><p>side walk move: move that doesn’t change the evaluation function value.</p>]]></content>
    
    
    <summary type="html">&lt;!-- # University Course Timetabling Problem的一些笔记 --&gt;</summary>
    
    
    
    <category term="学习笔记" scheme="https://archived.yanghan.life/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="学习笔记" scheme="https://archived.yanghan.life/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="算法" scheme="https://archived.yanghan.life/tags/%E7%AE%97%E6%B3%95/"/>
    
    <category term="Timetabling" scheme="https://archived.yanghan.life/tags/Timetabling/"/>
    
  </entry>
  
  <entry>
    <title>Simulated Annealing解决TSP问题</title>
    <link href="https://archived.yanghan.life/2017/07/25/Simulated-Annealing%E8%A7%A3%E5%86%B3TSP%E9%97%AE%E9%A2%98/"/>
    <id>https://archived.yanghan.life/2017/07/25/Simulated-Annealing%E8%A7%A3%E5%86%B3TSP%E9%97%AE%E9%A2%98/</id>
    <published>2017-07-25T08:19:08.000Z</published>
    <updated>2022-09-06T10:20:33.928Z</updated>
    
    <content type="html"><![CDATA[<!-- # Simulated Annealing解决TSP问题 --><span id="more"></span><h2 id="模拟退火"><a href="#模拟退火" class="headerlink" title="模拟退火"></a>模拟退火</h2><p>参考: <a href="http://www.cnblogs.com/heaad/archive/2010/12/20/1911614.html">http://www.cnblogs.com/heaad/archive/2010/12/20/1911614.html</a></p><h3 id="爬山算法"><a href="#爬山算法" class="headerlink" title="爬山算法"></a>爬山算法</h3><p>只能接受下一个解比当前解好的情况</p><h3 id="模拟退火-1"><a href="#模拟退火-1" class="headerlink" title="模拟退火"></a>模拟退火</h3><p>以一定概率接受比当前解差的解，此概率随时间经过逐渐变小</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">* J(y)：在状态y时的评价函数值</span></span><br><span class="line"><span class="comment">* Y(i)：表示当前状态</span></span><br><span class="line"><span class="comment">* Y(i+1)：表示新的状态</span></span><br><span class="line"><span class="comment">* r： 用于控制降温的快慢</span></span><br><span class="line"><span class="comment">* T： 系统的温度，系统初始应该要处于一个高温的状态</span></span><br><span class="line"><span class="comment">* T_min ：温度的下限，若温度T达到T_min，则停止搜索</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">while</span>( T &gt; T_min )</span><br><span class="line">&#123;</span><br><span class="line">　　dE = <span class="built_in">J</span>( <span class="built_in">Y</span>(i+<span class="number">1</span>) ) - <span class="built_in">J</span>( <span class="built_in">Y</span>(i) ) ; </span><br><span class="line"></span><br><span class="line">　　<span class="keyword">if</span> ( dE &gt;=<span class="number">0</span> ) <span class="comment">//表达移动后得到更优解，则总是接受移动</span></span><br><span class="line"> <span class="built_in">Y</span>(i+<span class="number">1</span>) = <span class="built_in">Y</span>(i) ; <span class="comment">//接受从Y(i)到Y(i+1)的移动</span></span><br><span class="line">　　<span class="keyword">else</span></span><br><span class="line">　　&#123;</span><br><span class="line">      <span class="comment">// 函数exp( dE/T )的取值范围是(0,1) ，dE/T越大，则exp( dE/T )也</span></span><br><span class="line">      <span class="keyword">if</span> ( <span class="built_in">exp</span>( dE/T ) &gt; <span class="built_in">random</span>( <span class="number">0</span> , <span class="number">1</span> ) )</span><br><span class="line">      <span class="built_in">Y</span>(i+<span class="number">1</span>) = <span class="built_in">Y</span>(i) ; <span class="comment">//接受从Y(i)到Y(i+1)的移动</span></span><br><span class="line">　　&#125;</span><br><span class="line">　　T = r * T ; <span class="comment">//降温退火 ，0&lt;r&lt;1 。r越大，降温越慢；r越小，降温越快</span></span><br><span class="line">　　<span class="comment">/*</span></span><br><span class="line"><span class="comment">　　* 若r过大，则搜索到全局最优解的可能会较高，但搜索的过程也就较长。若r过小，则搜索的过程会很快，但最终可能会达到一个局部最优值</span></span><br><span class="line"><span class="comment">　　*/</span></span><br><span class="line">　　i ++ ;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>参考：<a href="https://zh.wikipedia.org/wiki/%E6%A8%A1%E6%8B%9F%E9%80%80%E7%81%AB">https://zh.wikipedia.org/wiki/%E6%A8%A1%E6%8B%9F%E9%80%80%E7%81%AB</a></p><h2 id="演算步骤"><a href="#演算步骤" class="headerlink" title="演算步骤"></a>演算步骤</h2><h3 id="初始化"><a href="#初始化" class="headerlink" title="初始化"></a>初始化</h3><p>生成一个可行的解作为当前解输入迭代过程，并定义一个足够大的数值作为初始温度。</p><h3 id="迭代过程"><a href="#迭代过程" class="headerlink" title="迭代过程"></a>迭代过程</h3><p>迭代过程是模拟退火算法的核心步骤，分为新解的产生和接受新解两部分：</p><ol><li>由一个产生函数从当前解产生一个位于解空间的新解；为便于后续的计算和接受，减少算法耗时，通常选择由当前新解经过简单地变换即可产生新解的方法，如对构成新解的全部或部分元素进行置换、互换等，注意到产生新解的变换方法决定了当前新解的邻域结构，因而对冷却进度表的选取有一定的影响。</li><li>计算与新解所对应的目标函数差。因为目标函数差仅由变换部分产生，所以目标函数差的计算最好按增量计算。事实表明，对大多数应用而言，这是计算目标函数差的最快方法。</li><li>判断新解是否被接受，判断的依据是一个接受准则，最常用的接受准则是Metropolis准则：若Δt′&lt;0则接受S′作为新的当前解S，否则以概率exp（-Δt′/T）接受S′作为新的当前解S。</li><li>当新解被确定接受时，用新解代替当前解，这只需将当前解中对应于产生新解时的变换部分予以实现，同时修正目标函数值即可。此时，当前解实现了一次迭代。可在此基础上开始下一轮试验。而当新解被判定为舍弃时，则在原当前解的基础上继续下一轮试验。</li></ol><p>模拟退火算法与初始值无关，算法求得的解与初始解状态S（是算法迭代的起点）无关；模拟退火算法具有渐近收敛性，已在理论上被证明是一种以概率1收敛于全局最优解的全局优化算法；模拟退火算法具有并行性。</p><h3 id="停止准则"><a href="#停止准则" class="headerlink" title="停止准则"></a>停止准则</h3><p>迭代过程的停止准则：温度T降至某最低值时，完成给定数量迭代中无法接受新解，停止迭代，接受当前寻找的最优解为最终解。</p><h3 id="退火方案"><a href="#退火方案" class="headerlink" title="退火方案"></a>退火方案</h3><p>在某个温度状态T下，当一定数量的迭代操作完成后，降低温度T，在新的温度状态下执行下一个批次的迭代操作。</p><h3 id="TSP问题示例代码"><a href="#TSP问题示例代码" class="headerlink" title="TSP问题示例代码"></a>TSP问题示例代码</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br></pre></td><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> math</span><br><span class="line"><span class="keyword">import</span> random</span><br><span class="line"><span class="keyword">from</span> copy <span class="keyword">import</span> deepcopy</span><br><span class="line"><span class="keyword">import</span> matplotlib.pyplot <span class="keyword">as</span> plt</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Tsp</span>(<span class="params"><span class="built_in">object</span></span>):</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__init__</span>(<span class="params">self, city_num, times, steps, init_temperature, simulated_k</span>):</span></span><br><span class="line">        self.city_num = city_num</span><br><span class="line">        self.distance = np.mat(np.zeros((city_num, city_num)), dtype=<span class="built_in">int</span>)</span><br><span class="line">        self.init_temperature = init_temperature</span><br><span class="line">        self.times = times</span><br><span class="line">        self.steps = steps</span><br><span class="line">        self.simulated_k = simulated_k</span><br><span class="line"></span><br><span class="line">        self.x = <span class="built_in">list</span>(<span class="built_in">range</span>(self.city_num))</span><br><span class="line">        self.y = <span class="built_in">list</span>(<span class="built_in">range</span>(self.city_num))</span><br><span class="line">        self.now_path = <span class="built_in">list</span>(<span class="built_in">range</span>(self.city_num))</span><br><span class="line">        self.new_path = <span class="built_in">list</span>(<span class="built_in">range</span>(self.city_num))</span><br><span class="line">        self.best_path = <span class="built_in">list</span>(<span class="built_in">range</span>(self.city_num))</span><br><span class="line">        self.now_value = <span class="number">0</span></span><br><span class="line">        self.new_value = <span class="number">0</span></span><br><span class="line">        self.best_value = -<span class="number">1</span></span><br><span class="line">        self.path = [<span class="built_in">int</span>(item)-<span class="number">1</span> <span class="keyword">for</span> item <span class="keyword">in</span> <span class="string">&quot;1 8 38 31 44 18 7 28 6 37 19 27 17 43 30 36 46 33 20 47 21 32 39 48 5  42 24 10 45 35 4 26 2 29 34 41 16 22 3 23 14 25 13 11 12 15 40 9&quot;</span>.split()]</span><br><span class="line"></span><br><span class="line">        self.best_time = -<span class="number">1</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">read</span>(<span class="params">self, path</span>):</span></span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;Reading data...&quot;</span>)</span><br><span class="line">        <span class="comment"># x = [0 for i in range(self.city_num)]</span></span><br><span class="line">        <span class="comment"># y = [0 for i in range(self.city_num)]</span></span><br><span class="line">        <span class="keyword">with</span> <span class="built_in">open</span>(path, <span class="string">&quot;r&quot;</span>) <span class="keyword">as</span> file:</span><br><span class="line">            lines = file.readlines()</span><br><span class="line">            self.x = [<span class="built_in">int</span>(line.split()[<span class="number">1</span>]) <span class="keyword">for</span> line <span class="keyword">in</span> lines]</span><br><span class="line">            self.y = [<span class="built_in">int</span>(line.split()[<span class="number">2</span>]) <span class="keyword">for</span> line <span class="keyword">in</span> lines]</span><br><span class="line">        x = self.x</span><br><span class="line">        y = self.y</span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0</span>, self.city_num):</span><br><span class="line">            <span class="keyword">for</span> j <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0</span>, self.city_num):</span><br><span class="line">                self.distance[i, j] = <span class="built_in">round</span>(math.sqrt((x[i]-x[j])*(x[i]-x[j]) + (y[i]-y[j])*(y[i]-y[j])))</span><br><span class="line">        <span class="built_in">print</span>(self.distance)</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;Read data done.&quot;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">init_path</span>(<span class="params">self</span>):</span></span><br><span class="line">        random.shuffle(self.now_path)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">value_of_path</span>(<span class="params">self, path</span>):</span></span><br><span class="line">        value = <span class="number">0</span></span><br><span class="line">        <span class="keyword">for</span> (index, i) <span class="keyword">in</span> <span class="built_in">enumerate</span>(path):</span><br><span class="line">            value += self.distance[path[index-<span class="number">1</span>], i]</span><br><span class="line">        <span class="keyword">return</span> value</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">get_neighbor</span>(<span class="params">self</span>):</span></span><br><span class="line">        self.new_path = deepcopy(self.now_path)</span><br><span class="line">        i_1, i_2 = random.randint(<span class="number">0</span>, self.city_num-<span class="number">1</span>), random.randint(<span class="number">0</span>, self.city_num-<span class="number">1</span>)</span><br><span class="line">        self.new_path[i_1], self.new_path[i_2] = self.new_path[i_2], self.new_path[i_1]</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">solve</span>(<span class="params">self</span>):</span></span><br><span class="line">        self.init_path()</span><br><span class="line">        self.now_value = self.value_of_path(self.now_path)</span><br><span class="line">        <span class="built_in">print</span>(self.now_value)</span><br><span class="line">        temperature = self.init_temperature</span><br><span class="line">        k = <span class="number">0</span></span><br><span class="line">        <span class="comment"># while k &lt; self.times:</span></span><br><span class="line">        <span class="keyword">for</span> k <span class="keyword">in</span> <span class="built_in">range</span>(self.times):</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&quot;k: &quot;</span>, k, <span class="string">&quot;  now best: &quot;</span>, self.best_value)</span><br><span class="line">            <span class="keyword">for</span> n <span class="keyword">in</span> <span class="built_in">range</span>(self.steps):</span><br><span class="line">                self.get_neighbor()</span><br><span class="line">                self.new_value = self.value_of_path(self.new_path)</span><br><span class="line">                <span class="keyword">if</span> self.new_value &lt; self.best_value <span class="keyword">or</span> self.best_value == -<span class="number">1</span>:</span><br><span class="line">                    self.best_value = self.new_value</span><br><span class="line">                    self.best_path = deepcopy(self.new_path)</span><br><span class="line">                    self.best_time = k</span><br><span class="line">                    <span class="built_in">print</span>(self.best_value)</span><br><span class="line">                <span class="comment"># random_value = random.random()</span></span><br><span class="line">                <span class="keyword">if</span> self.new_value &lt; self.now_value <span class="keyword">or</span> math.exp((self.now_value - self.new_value)/temperature)&gt;random.random():</span><br><span class="line">                    self.now_path = deepcopy(self.new_path)</span><br><span class="line">                    self.now_value = self.new_value</span><br><span class="line">            temperature *= self.simulated_k</span><br><span class="line"></span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;best: &quot;</span>, self.best_value)</span><br><span class="line">        <span class="comment"># self.draw_best()</span></span><br><span class="line">        <span class="comment"># self.draw()</span></span><br><span class="line">        <span class="comment"># self.show()</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">test</span>(<span class="params">self</span>):</span></span><br><span class="line">        <span class="comment"># best = 33551(ceil)  33522(round)</span></span><br><span class="line">        <span class="built_in">print</span>(self.value_of_path(self.path))</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">draw_path</span>(<span class="params">self, path</span>):</span></span><br><span class="line">        x = [self.x[i] <span class="keyword">for</span> i <span class="keyword">in</span> path]</span><br><span class="line">        y = [self.y[i] <span class="keyword">for</span> i <span class="keyword">in</span> path]</span><br><span class="line">        x.append(self.x[path[<span class="number">0</span>]])</span><br><span class="line">        y.append(self.y[path[<span class="number">0</span>]])</span><br><span class="line">        plt.plot(x, y, <span class="string">&quot;-o&quot;</span>)</span><br><span class="line">        <span class="comment"># plt.show()</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">draw_best</span>(<span class="params">self</span>):</span></span><br><span class="line">        self.draw_path(self.path)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">draw</span>(<span class="params">self</span>):</span></span><br><span class="line">        self.draw_path(self.best_path)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">show</span>(<span class="params">self</span>):</span></span><br><span class="line">        plt.show()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    tsp = Tsp(<span class="number">48</span>, <span class="number">1000</span>, <span class="number">1000</span>, <span class="number">10000</span>, <span class="number">0.992</span>)</span><br><span class="line">    tsp.read(<span class="string">&quot;att48.tsp&quot;</span>)</span><br><span class="line">    tsp.solve()</span><br><span class="line">    tsp.draw_best()</span><br><span class="line">    <span class="comment"># tsp.init_path()</span></span><br><span class="line">    <span class="comment"># tsp.draw_path(tsp.now_path)</span></span><br><span class="line">    <span class="comment"># tsp.test()</span></span><br><span class="line">    tsp.draw()</span><br><span class="line">    tsp.show()</span><br></pre></td></tr></table></figure><h3 id="运行结果"><a href="#运行结果" class="headerlink" title="运行结果"></a>运行结果</h3><p>已知结果取round的情况下最优解是33522。</p><p>我以代码中的参数迭代1000次的结果是34384。</p><p>可视化如下：</p><p><img src="/assets/611478ffly1fwoy8z0nedj21hc0seq60.jpg"></p><p>图中蓝色路线为最优解，橙色路线为SA跑出来的解。</p>]]></content>
    
    
    <summary type="html">&lt;!-- # Simulated Annealing解决TSP问题 --&gt;</summary>
    
    
    
    <category term="学习笔记" scheme="https://archived.yanghan.life/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="算法" scheme="https://archived.yanghan.life/tags/%E7%AE%97%E6%B3%95/"/>
    
    <category term="模拟退火" scheme="https://archived.yanghan.life/tags/%E6%A8%A1%E6%8B%9F%E9%80%80%E7%81%AB/"/>
    
  </entry>
  
  <entry>
    <title>用C++实现MVVM</title>
    <link href="https://archived.yanghan.life/2017/07/12/%E7%94%A8C-%E5%AE%9E%E7%8E%B0MVVM/"/>
    <id>https://archived.yanghan.life/2017/07/12/%E7%94%A8C-%E5%AE%9E%E7%8E%B0MVVM/</id>
    <published>2017-07-12T15:07:31.000Z</published>
    <updated>2022-09-06T10:00:10.366Z</updated>
    
    <content type="html"><![CDATA[<!-- # 用C++实现MVVM --><span id="more"></span><h2 id="序"><a href="#序" class="headerlink" title="序"></a>序</h2><p><code>MVVM</code>(<code>Model-View-ViewModel</code>)是现在比较流行的GUI程序的框架。</p><p>整体代码的sample在<a href="https://github.com/sleeepyy/GraphicsEditor">Graphics Editor</a>可以看到。</p><p>GUI库使用了<code>QT5.9</code>，功能代码主要使用了<code>OpenCV</code>库。</p><p>后面一些功能的编写不是我写的，所以代码风格可能有些不和谐，这里主要集中精力于整个框架的实现，忽略其各项功能的实现。</p><p>如果有任何理解不对的地方，欢迎批评指出。</p><h2 id="MVVM"><a href="#MVVM" class="headerlink" title="MVVM"></a>MVVM</h2><p>在<a href="http://www.ruanyifeng.com/blog/2015/02/mvcmvp_mvvm.html">阮一峰的”MVC，MVP 和 MVVM 的图示”</a>中， 介绍了三个架构之间的区别。</p><p>总结来说，就是在Model，View，ViewModel三个模块之间，View与ViewModel之间的数据通过双向绑定进行联系，View与Model之间不产生联系，ViewModel操作Model进行数据处理。</p><p>（这里实际写代码的时候好像跟阮老师所说的有一些区别：按照阮老师所说，应该是ViewModel在功能上相当于MVP模式中的Presenter，所有逻辑都部署在这里，实际上写的时候应该是大部分逻辑都部署在Model层进行数据操作，然后通知ViewModel和View进行更新，不知道是否是在我的理解中出现问题……）</p><h2 id="项目目录"><a href="#项目目录" class="headerlink" title="项目目录"></a>项目目录</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line">.</span><br><span class="line">├── app.cpp</span><br><span class="line">├── app.h</span><br><span class="line">├── command.cpp</span><br><span class="line">├── command.h</span><br><span class="line">├── Commands</span><br><span class="line">│   ├── alter_bright_command.cpp</span><br><span class="line">│   ├── alter_bright_command.h</span><br><span class="line">│   ├── crop_command.cpp</span><br><span class="line">│   ├── crop_command.h</span><br><span class="line">│   ├── detect_face_command.cpp</span><br><span class="line">│   ├── detect_face_command.h</span><br><span class="line">│   ├── filter_command.cpp</span><br><span class="line">│   ├── filter_command.h</span><br><span class="line">│   ├── open_file_command.cpp</span><br><span class="line">│   ├── open_file_command.h</span><br><span class="line">│   ├── reset_command.cpp</span><br><span class="line">│   ├── reset_command.h</span><br><span class="line">│   ├── rotate_command.cpp</span><br><span class="line">│   ├── rotate_command.h</span><br><span class="line">│   ├── save_bmp_command.cpp</span><br><span class="line">│   ├── save_bmp_command.h</span><br><span class="line">│   ├── save_file_command.cpp</span><br><span class="line">│   └── save_file_command.h</span><br><span class="line">├── common.cpp</span><br><span class="line">├── common.h</span><br><span class="line">├── GraphicsEditor.pro</span><br><span class="line">├── GraphicsEditor.pro.user</span><br><span class="line">├── LICENSE</span><br><span class="line">├── main.cpp</span><br><span class="line">├── model.cpp</span><br><span class="line">├── model.h</span><br><span class="line">├── MyView.cpp</span><br><span class="line">├── MyView.h</span><br><span class="line">├── notification.cpp</span><br><span class="line">├── notification.h</span><br><span class="line">├── parameters.cpp</span><br><span class="line">├── parameters.h</span><br><span class="line">├── README.md</span><br><span class="line">├── test.pro</span><br><span class="line">├── test.pro.user</span><br><span class="line">├── view.cpp</span><br><span class="line">├── view.h</span><br><span class="line">├── viewmodel.cpp</span><br><span class="line">├── viewmodel.h</span><br><span class="line">└── view.ui</span><br></pre></td></tr></table></figure><h2 id="项目架构介绍"><a href="#项目架构介绍" class="headerlink" title="项目架构介绍"></a>项目架构介绍</h2><p>各个类以及之间关系如下：</p><h3 id="App"><a href="#App" class="headerlink" title="App"></a>App</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">App</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    std::shared_ptr&lt;View&gt; view;</span><br><span class="line">    std::shared_ptr&lt;Model&gt; model;</span><br><span class="line">    std::shared_ptr&lt;ViewModel&gt; viewmodel;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">App</span>();</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">run</span><span class="params">()</span></span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>在构造函数中，对各项需要初始化和绑定的数据进行绑定：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">App::<span class="built_in">App</span>():<span class="built_in">view</span>(<span class="keyword">new</span> View),<span class="built_in">model</span>(<span class="keyword">new</span> Model), <span class="built_in">viewmodel</span>(<span class="keyword">new</span> ViewModel)</span><br><span class="line">&#123;</span><br><span class="line"></span><br><span class="line">    viewmodel-&gt;<span class="built_in">bind</span>(model);</span><br><span class="line"></span><br><span class="line">    view-&gt;<span class="built_in">set_img</span>(viewmodel-&gt;<span class="built_in">get</span>());</span><br><span class="line"></span><br><span class="line">    view-&gt;<span class="built_in">set_open_file_command</span>(viewmodel-&gt;<span class="built_in">get_open_file_command</span>());</span><br><span class="line">    view-&gt;<span class="built_in">set_alter_bright_command</span>(viewmodel-&gt;<span class="built_in">get_alter_bright_command</span>());</span><br><span class="line">    view-&gt;<span class="built_in">set_filter_rem_command</span>(viewmodel-&gt;<span class="built_in">get_filter_rem_command</span>());</span><br><span class="line">    view-&gt;<span class="built_in">set_reset_command</span>(viewmodel-&gt;<span class="built_in">get_reset_command</span>());</span><br><span class="line">    view-&gt;<span class="built_in">set_detect_face_command</span>(viewmodel-&gt;<span class="built_in">get_detect_face_command</span>());</span><br><span class="line">    view-&gt;<span class="built_in">set_save_file_command</span>(viewmodel-&gt;<span class="built_in">get_save_file_command</span>());</span><br><span class="line">    view-&gt;<span class="built_in">set_save_bmp_file_command</span>(viewmodel-&gt;<span class="built_in">get_save_bmp_file_command</span>());</span><br><span class="line">    view-&gt;<span class="built_in">set_rotate_command</span>(viewmodel-&gt;<span class="built_in">get_rotate_command</span>());</span><br><span class="line">    view-&gt;<span class="built_in">set_crop_command</span>(viewmodel-&gt;<span class="built_in">get_crop_command</span>());</span><br><span class="line"></span><br><span class="line">    viewmodel-&gt;<span class="built_in">set_update_view_notification</span>(view-&gt;<span class="built_in">get_update_view_notification</span>());</span><br><span class="line">    model-&gt;<span class="built_in">set_update_display_data_notification</span>(viewmodel-&gt;<span class="built_in">get_update_display_data_notification</span>());</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="View"><a href="#View" class="headerlink" title="View"></a>View</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">View</span> :</span> <span class="keyword">public</span> QMainWindow</span><br><span class="line">&#123;</span><br><span class="line">    Q_OBJECT</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">explicit</span> <span class="title">View</span><span class="params">(QWidget *parent = <span class="number">0</span>)</span></span>;</span><br><span class="line">    ~<span class="built_in">View</span>();</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">update</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">set_img</span><span class="params">(std::shared_ptr&lt;QImage&gt; image)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">set_open_file_command</span><span class="params">(std::shared_ptr&lt;Command&gt;)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">set_alter_bright_command</span><span class="params">(std::shared_ptr&lt;Command&gt;)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">set_filter_rem_command</span><span class="params">(std::shared_ptr&lt;Command&gt;)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">set_reset_command</span><span class="params">(std::shared_ptr&lt;Command&gt;)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">set_detect_face_command</span><span class="params">(std::shared_ptr&lt;Command&gt;)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">set_save_file_command</span><span class="params">(std::shared_ptr&lt;Command&gt;)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">set_save_bmp_file_command</span><span class="params">(std::shared_ptr&lt;Command&gt;)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">set_rotate_command</span><span class="params">(std::shared_ptr&lt;Command&gt;)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">set_crop_command</span><span class="params">(std::shared_ptr&lt;Command&gt;)</span></span>;</span><br><span class="line">    <span class="function">std::shared_ptr&lt;Notification&gt; <span class="title">get_update_view_notification</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> slots:</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">on_button_open_clicked</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">on_brightSlider_valueChanged</span><span class="params">(<span class="keyword">int</span> value)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">on_contrastSlider_valueChanged</span><span class="params">(<span class="keyword">int</span> value)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">on_filter_1_clicked</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">on_reset_clicked</span><span class="params">()</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">on_actionOpen_File_triggered</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">on_button_detect_face_clicked</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">on_actionSave_triggered</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">on_action_bmp_triggered</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">on_action_png_triggered</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">on_action_jpeg_triggered</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">on_rotateSlider_valueChanged</span><span class="params">(<span class="keyword">int</span> value)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    Ui::View *ui;</span><br><span class="line">    MyView* canvas;</span><br><span class="line">    std::shared_ptr&lt;QImage&gt; q_image;</span><br><span class="line">    std::shared_ptr&lt;Command&gt; open_file_command;</span><br><span class="line">    std::shared_ptr&lt;Command&gt; alter_bright_command;</span><br><span class="line">    std::shared_ptr&lt;Command&gt; filter_rem_command;</span><br><span class="line">    std::shared_ptr&lt;Command&gt; reset_command;</span><br><span class="line">    std::shared_ptr&lt;Command&gt; detect_face_command;</span><br><span class="line">    std::shared_ptr&lt;Command&gt; save_file_command;</span><br><span class="line">    std::shared_ptr&lt;Command&gt; save_bmp_file_command;</span><br><span class="line">    std::shared_ptr&lt;Command&gt; rotate_command;</span><br><span class="line">    std::shared_ptr&lt;Command&gt; crop_command;</span><br><span class="line"></span><br><span class="line">    std::shared_ptr&lt;Notification&gt; update_view_notification;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>本身提供一个用于更新的<code>notification</code>, 并提供<code>get()</code>方法交给<code>ViewModel</code>层进行绑定，如此可以实现<code>ViewModel</code>通知<code>View</code>进行更新。</p><p>同时，本身提供很多<code>Command</code>的成员变量，这些变量本省并不属于<code>View</code>层，本身属于<code>ViewModel</code>层，并在<code>ViewModel</code>层提供<code>get</code>方法给<code>View</code>层进行<code>set</code>绑定，这样就实现了<code>View</code>发送<code>command</code>给<code>ViewModel</code>层，<code>View</code>就可以在不知道Command具体派生类的情况下写代码。</p><h3 id="ViewModel"><a href="#ViewModel" class="headerlink" title="ViewModel"></a>ViewModel</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ViewModel</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    std::shared_ptr&lt;QImage&gt; q_image;</span><br><span class="line">    std::shared_ptr&lt;Model&gt; model;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    std::shared_ptr&lt;Command&gt; open_file_command;</span><br><span class="line">    std::shared_ptr&lt;Command&gt; alter_bright_command;</span><br><span class="line">    std::shared_ptr&lt;Command&gt; filter_rem_command;</span><br><span class="line">    std::shared_ptr&lt;Command&gt; reset_command;</span><br><span class="line">    std::shared_ptr&lt;Command&gt; detect_face_command;</span><br><span class="line">    std::shared_ptr&lt;Command&gt; save_file_command;</span><br><span class="line">    std::shared_ptr&lt;Command&gt; save_bmp_file_command;</span><br><span class="line">    std::shared_ptr&lt;Command&gt; rotate_command;</span><br><span class="line">    std::shared_ptr&lt;Command&gt; crop_command;</span><br><span class="line"></span><br><span class="line">    std::shared_ptr&lt;Notification&gt; update_display_data_notification;</span><br><span class="line"></span><br><span class="line">    std::shared_ptr&lt;Notification&gt; update_view_notification;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">ViewModel</span>();</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">bind</span><span class="params">(std::shared_ptr&lt;Model&gt; model)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">exec_open_file_command</span><span class="params">(std::string path)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">exec_alter_bright_command</span><span class="params">(<span class="keyword">int</span> nBright, <span class="keyword">int</span> nContrast)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">exec_filter_rem_command</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">exec_reset_command</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">exec_detect_face_command</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">exec_save_file_command</span><span class="params">(std::string path)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">exec_save_bmp_file_command</span><span class="params">(std::string path)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">exec_rotate_command</span><span class="params">(<span class="keyword">int</span> angle)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">exec_crop_command</span><span class="params">(<span class="keyword">double</span> x_s, <span class="keyword">double</span> y_s, <span class="keyword">double</span> x_e, <span class="keyword">double</span> y_e)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">set_update_view_notification</span><span class="params">(std::shared_ptr&lt;Notification&gt; notification)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="function">std::shared_ptr&lt;Command&gt; <span class="title">get_open_file_command</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function">std::shared_ptr&lt;Command&gt; <span class="title">get_alter_bright_command</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function">std::shared_ptr&lt;Command&gt; <span class="title">get_filter_rem_command</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function">std::shared_ptr&lt;Command&gt; <span class="title">get_reset_command</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function">std::shared_ptr&lt;Command&gt; <span class="title">get_detect_face_command</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function">std::shared_ptr&lt;Command&gt; <span class="title">get_save_file_command</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function">std::shared_ptr&lt;Command&gt; <span class="title">get_save_bmp_file_command</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function">std::shared_ptr&lt;Command&gt; <span class="title">get_rotate_command</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function">std::shared_ptr&lt;Command&gt; <span class="title">get_crop_command</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="function">std::shared_ptr&lt;Notification&gt; <span class="title">get_update_display_data_notification</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function">std::shared_ptr&lt;QImage&gt; <span class="title">get</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">notified</span><span class="params">()</span></span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>与<code>View</code>层之间的通信在之前已经讲过，在构造函数中初始化具体的命令，然后<code>get</code>交给<code>View</code>的<code>set</code>进行绑定。这其中有一个向基类指针的转换，我是这么写的：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">open_file_command = std::static_pointer_cast&lt;Command, OpenFileCommand&gt;(std::shared_ptr&lt;OpenFileCommand&gt; (<span class="keyword">new</span> <span class="built_in">OpenFileCommand</span>(std::shared_ptr&lt;ViewModel&gt;(<span class="keyword">this</span>))));</span><br></pre></td></tr></table></figure><p>然后与<code>Model</code>间的通信没有通过<code>Command</code>，而是直接获得一个<code>Model</code>的指针，调用它的功能函数即可。</p><h3 id="Model"><a href="#Model" class="headerlink" title="Model"></a>Model</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Model</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">     cv::Mat image;</span><br><span class="line">     std::shared_ptr&lt;Notification&gt; update_display_data_notification;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">Model</span>()&#123;&#125;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">set_update_display_data_notification</span><span class="params">(std::shared_ptr&lt;Notification&gt; notification)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">open_file</span><span class="params">(std::string path)</span></span>;</span><br><span class="line">    <span class="function">cv::Mat&amp; <span class="title">get</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function">cv::Mat&amp; <span class="title">getOrigin</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">notify</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">save_file</span><span class="params">(std::string path)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">save_bmp_file</span><span class="params">(std::string path)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">alterBrightAndContrast</span><span class="params">(<span class="keyword">int</span> nbright, <span class="keyword">int</span> nContrast)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">detect_face</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">filterReminiscence</span><span class="params">()</span></span>; <span class="comment">//Filter No.1</span></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">reset</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">rotate</span><span class="params">(<span class="keyword">double</span> angle)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">crop</span><span class="params">(<span class="keyword">int</span> x1, <span class="keyword">int</span> y1, <span class="keyword">int</span> x2, <span class="keyword">int</span> y2)</span></span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><p><code>Model</code>层本身又一个set一个notification的接口，这个notification用于通知<code>ViewModel</code>进行更新数据。</p><p>其他的就是针对数据的一些功能代码。</p><h3 id="Command"><a href="#Command" class="headerlink" title="Command"></a>Command</h3><p>本身可以写为纯虚类，我是写了一个成员变量是一个基类参数的指针，然后所有具体的command都是派生于此，提供<code>exec()</code>方法。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Command</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line">    std::shared_ptr&lt;Parameters&gt; params;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">Command</span>();</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">set_parameters</span><span class="params">(std::shared_ptr&lt;Parameters&gt; parameters)</span></span>&#123;</span><br><span class="line">        params = parameters;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">exec</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="Notification"><a href="#Notification" class="headerlink" title="Notification"></a>Notification</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Notification</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">Notification</span>();</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">exec</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">UpdateDisplayDataNotification</span>:</span> <span class="keyword">public</span> Notification&#123;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    std::shared_ptr&lt;ViewModel&gt; viewmodel;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">UpdateDisplayDataNotification</span>(std::shared_ptr&lt;ViewModel&gt; vm):<span class="built_in">viewmodel</span>(vm)&#123;&#125;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">exec</span><span class="params">()</span></span>&#123;</span><br><span class="line">        viewmodel-&gt;<span class="built_in">notified</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">UpdateViewNotification</span>:</span> <span class="keyword">public</span> Notification&#123;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    std::shared_ptr&lt;View&gt; view;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">UpdateViewNotification</span>(std::shared_ptr&lt;View&gt; v):<span class="built_in">view</span>(v)&#123;&#125;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">exec</span><span class="params">()</span></span>&#123;</span><br><span class="line">        view-&gt;<span class="built_in">update</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="Parameters"><a href="#Parameters" class="headerlink" title="Parameters"></a>Parameters</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Parameters</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">Parameters</span>();</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">PathParameters</span>:</span> <span class="keyword">public</span> Parameters&#123;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    std::string path;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">PathParameters</span>(std::string _path):<span class="built_in">path</span>(_path)&#123;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function">std::string <span class="title">get_path</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">return</span> path;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>以<code>PathParameters</code>为例表示了一般的新的参数的派生方法。</p><h3 id="common"><a href="#common" class="headerlink" title="common"></a>common</h3><p>实现了<code>cv::Mat</code>与<code>QImage</code>之间的转换代码。</p><h2 id="整体流程"><a href="#整体流程" class="headerlink" title="整体流程"></a>整体流程</h2><p>在<code>View</code>层进行操作之后，会触发对应槽函数，该槽函数会准备好参数<code>Parameter</code>交给对应的<code>Command</code>，然后执行<code>exec()</code>这个command，exec会解出参数交给<code>ViewModel</code>层，<code>ViewModel</code>调用<code>Model</code>里对应的方法，进行数据操作，<code>Model</code>操作完之后会通知<code>ViewModel</code>更新显示数据，<code>ViewModel</code>会通知<code>View</code>刷新显示。</p>]]></content>
    
    
    <summary type="html">&lt;!-- # 用C++实现MVVM --&gt;</summary>
    
    
    
    <category term="学习笔记" scheme="https://archived.yanghan.life/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="学习笔记" scheme="https://archived.yanghan.life/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="C++" scheme="https://archived.yanghan.life/tags/C/"/>
    
    <category term="MVVM" scheme="https://archived.yanghan.life/tags/MVVM/"/>
    
  </entry>
  
  <entry>
    <title>hexo公式显示</title>
    <link href="https://archived.yanghan.life/2017/07/01/hexo%E5%85%AC%E5%BC%8F%E6%98%BE%E7%A4%BA/"/>
    <id>https://archived.yanghan.life/2017/07/01/hexo%E5%85%AC%E5%BC%8F%E6%98%BE%E7%A4%BA/</id>
    <published>2017-07-01T03:25:10.000Z</published>
    <updated>2022-09-06T10:00:10.366Z</updated>
    
    <content type="html"><![CDATA[<!-- # 痛苦地在考试周配hexo环境 --><span id="more"></span><p>啊昨天满心欢喜得搭好了hexo，配啊调啊一些奇怪的东西，最后终于开起来正常了，以为可以愉快得写博客了= =</p><p>后来才发现了太过naive了啊 = =。</p><p>公式显示我尝试换了<code>pandoc</code>的渲染，装了<code>pandoc</code>和<code>hexo-renderer-pandoc</code>，卸载了原装的<code>hexo-renderer-marked</code>，但是本地<code>hexo s</code>虽然显示正常，但是<code>deploy</code>过后网站上的就只有将<code>$$</code>转义成<code>\[</code>和<code>\]</code>的东西 = =</p><p>然后就又用<code>hexo-math</code>，它告诉我它已经<code>deprecated</code>了 = =</p><p>但是能显示我就感激不尽了 = =</p><p>然后就是<code>markdown</code>和<code>mathJax</code>的冲突了 = =</p><p>改了一发<code>marked.js</code> = =</p><p>感觉这样可移植性就变得糟糕了= =</p><p>但是现在怎么说看起来也算还好了吧 = =</p><p>最后用的办法是：</p><p>修改hexo的渲染源码: <code>nodes_modules/marked/lib/marked.js</code>:</p><ul><li>去掉<code>\\</code>的额外转义</li><li>将em标签对应的符号中，去掉<code>_</code>,因为markdown中有<code>*</code>可以表示斜体，<code>—</code>就去掉了。</li></ul><p>具体思路参考了<a href="http://blog.csdn.net/emptyset110/article/details/50123231">使Marked.js与MathJax共存</a>,<br>打开<code>nodes_modules/marked/lib/marked.js</code>: 第一步: 找到下面的代码:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">escape</span>: <span class="regexp">/^\\([\\`*&#123;&#125;\[\]()# +\-.!_&gt;])/</span>,</span><br></pre></td></tr></table></figure><p>改为:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">escape</span>: <span class="regexp">/^\\([`*&#123;&#125;\[\]()# +\-.!_&gt;])/</span>,</span><br></pre></td></tr></table></figure><p>这样就会去掉<code>\\</code>的转义了。 第二步: 找到em的符号:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">em: <span class="regexp">/^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/</span>,</span><br></pre></td></tr></table></figure><p>改为:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">em:<span class="regexp">/^\*((?:\*\*|[\s\S])+?)\*(?!\*)/</span>,</span><br></pre></td></tr></table></figure><p>去掉<code>_</code>的斜体含义,这样就解决了。这种方式指标不治本，因为保证不了还可能有其它的字符会冲突，这样的话，还需要返回去接着修改。</p>]]></content>
    
    
    <summary type="html">&lt;!-- # 痛苦地在考试周配hexo环境 --&gt;</summary>
    
    
    
    <category term="随笔" scheme="https://archived.yanghan.life/categories/%E9%9A%8F%E7%AC%94/"/>
    
    
    <category term="学习笔记" scheme="https://archived.yanghan.life/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="hexo" scheme="https://archived.yanghan.life/tags/hexo/"/>
    
    <category term="博客" scheme="https://archived.yanghan.life/tags/%E5%8D%9A%E5%AE%A2/"/>
    
  </entry>
  
</feed>
