{"componentChunkName":"component---src-templates-post-tsx","path":"/first-attempt-on-handwritten-digits-recogition","result":{"data":{"markdownRemark":{"html":"<p>这是我模式识别课的一个实验。简而言之，就是识别手写在纸上的数字。</p>\n<p>这个工作仔细分析还是比较简单的，我的实现方式是把整个过程分为许多 stage，每个 stage 只做一件单独的事，这样不仅方便调试（你可以看代码卡在了哪一步，或者单独显示哪一步的输出）而且写出来的代码也比较漂亮（不至于出现过了一段时间看不懂的情况）。</p>\n<!-- more -->\n<p>我把整个流程分为了以下的几个 stage：</p>\n<ol>\n<li>loader - 加载图片</li>\n<li>thresholding - 去噪声和二值化</li>\n<li>scissor - 裁剪图片</li>\n<li>normalizer - 处理裁剪后的单个数字</li>\n<li>recognizer - 识别裁剪后的单个数字</li>\n<li>marker - 把结果标注在原图片上</li>\n</ol>\n<p>前三个步骤都可以直接调用 OpenCV 的函数完成。第四个稍稍费了些脑筋，为了提高准确率，我在裁剪出的每个数字周围添加了一圈补白，使之构成正方形图像（方便在下一个 stage 喂给神经网络）；然后用到了一个神奇的 <code class=\"language-text\">deskew</code> 函数来矫正倾斜；接着缩放成 28x28 的大小（神经网络是用 MNIST 训练的）；因为大的数字图片缩小后会出现笔画变得极细的问题，所以最后还需要做一个膨胀操作。</p>\n<p>第五步就是最不必说的东西了……我偷了懒没用 TensorFlow，而是用了比较傻瓜的 Keras，不过好处就是构建起来快……可以把更多的时间花费在优化识别率上。</p>\n<p>有一个值得一提的小插曲。我的最初版本在 9 的识别率上非常低，经常被误识别成 1 或 7。其原因是大家写 9 的时候会写的比较长，导致缩放成 28x28 后「9」上面的圈会变得极小无比，看起来就像是个 1 或者 7。解决这个问题的办法非常简单，就是在 MNIST 数据集上做了一个小手术：</p>\n<div class=\"gatsby-highlight\" data-language=\"python\"><pre class=\"language-python\"><code class=\"language-python\"><span class=\"token keyword\">def</span> <span class=\"token function\">transform_nine</span><span class=\"token punctuation\">(</span>x_train<span class=\"token punctuation\">,</span> y_train<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n    x_superset <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span>\n    y_superset <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span>\n    <span class=\"token keyword\">for</span> x<span class=\"token punctuation\">,</span> y <span class=\"token keyword\">in</span> <span class=\"token builtin\">zip</span><span class=\"token punctuation\">(</span>x_train<span class=\"token punctuation\">,</span> y_train<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n        <span class=\"token keyword\">if</span> y <span class=\"token operator\">==</span> <span class=\"token number\">9</span> <span class=\"token keyword\">and</span> random<span class=\"token punctuation\">.</span>random<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">&lt;</span> <span class=\"token number\">0.5</span><span class=\"token punctuation\">:</span>\n            upper <span class=\"token operator\">=</span> x<span class=\"token punctuation\">[</span><span class=\"token number\">0</span><span class=\"token punctuation\">:</span><span class=\"token number\">14</span><span class=\"token punctuation\">,</span><span class=\"token punctuation\">:</span><span class=\"token punctuation\">]</span>\n            lower <span class=\"token operator\">=</span> x<span class=\"token punctuation\">[</span><span class=\"token number\">14</span><span class=\"token punctuation\">:</span><span class=\"token punctuation\">,</span><span class=\"token punctuation\">:</span><span class=\"token punctuation\">]</span>\n            stretched <span class=\"token operator\">=</span> numpy<span class=\"token punctuation\">.</span>zeros<span class=\"token punctuation\">(</span>shape<span class=\"token operator\">=</span><span class=\"token punctuation\">(</span><span class=\"token number\">35</span><span class=\"token punctuation\">,</span> <span class=\"token number\">28</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n            stretched<span class=\"token punctuation\">[</span><span class=\"token number\">0</span><span class=\"token punctuation\">:</span><span class=\"token number\">14</span><span class=\"token punctuation\">,</span><span class=\"token punctuation\">:</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> upper\n            stretched<span class=\"token punctuation\">[</span><span class=\"token number\">14</span><span class=\"token punctuation\">:</span><span class=\"token punctuation\">,</span><span class=\"token punctuation\">:</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> cv2<span class=\"token punctuation\">.</span>resize<span class=\"token punctuation\">(</span>lower<span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token number\">28</span><span class=\"token punctuation\">,</span> <span class=\"token number\">21</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> interpolation<span class=\"token operator\">=</span>cv2<span class=\"token punctuation\">.</span>INTER_AREA<span class=\"token punctuation\">)</span>\n            stretched <span class=\"token operator\">=</span> cv2<span class=\"token punctuation\">.</span>erode<span class=\"token punctuation\">(</span>stretched<span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token number\">7</span><span class=\"token punctuation\">,</span> <span class=\"token number\">7</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n            stretched <span class=\"token operator\">=</span> cv2<span class=\"token punctuation\">.</span>resize<span class=\"token punctuation\">(</span>stretched<span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token number\">28</span><span class=\"token punctuation\">,</span> <span class=\"token number\">28</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> interpolation<span class=\"token operator\">=</span>cv2<span class=\"token punctuation\">.</span>INTER_AREA<span class=\"token punctuation\">)</span>\n            x_superset<span class=\"token punctuation\">.</span>append<span class=\"token punctuation\">(</span>stretched<span class=\"token punctuation\">)</span>\n            y_superset<span class=\"token punctuation\">.</span>append<span class=\"token punctuation\">(</span><span class=\"token number\">9</span><span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">else</span><span class=\"token punctuation\">:</span>\n            x_superset<span class=\"token punctuation\">.</span>append<span class=\"token punctuation\">(</span>x<span class=\"token punctuation\">)</span>\n            y_superset<span class=\"token punctuation\">.</span>append<span class=\"token punctuation\">(</span>y<span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">return</span> numpy<span class=\"token punctuation\">.</span>array<span class=\"token punctuation\">(</span>x_superset<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> numpy<span class=\"token punctuation\">.</span>array<span class=\"token punctuation\">(</span>y_superset<span class=\"token punctuation\">)</span></code></pre></div>\n<p>这段代码做的事情就是把数据集中一半的 9 的下半部分拉长了，使得它们变得像我们写的 9 缩放成 28x28 的样子。这个做法虽然简单暴力但是效果拔群。听老师讲这种手段还有一个高大上的名字叫 domain adaption 哈哈哈哈哈。最终的效果如下，这是我手写的一堆数字以及程序的识别结果。</p>\n<p><span\n      class=\"gatsby-resp-image-wrapper\"\n      style=\"position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; \"\n    >\n      <a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/f0e39ee7209ba0d038c00b71cd260433/72372/handwritten-digits-recognition-result.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-background-image\"\n    style=\"padding-bottom: 60.810810810810814%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC0klEQVQozy3Ce08SAQAA8PsmfYS+RX9UKAj4Ak7E487jDjxOPOAEPJI7DoEDz7cgJwamWJKv8G3NOWrmY8KymmtrNTUfm1vO1hjR2vrtB8Tz15VyqVy5K1fO/vw6Lt8clq72S5cH///Y/326e/f17c3J9mlx48te7kN+obAztzWXvHdfBmT2zyqV09LPQul85/Zj7vpw7vx99vxw4bK4enG0clFc/b63eLIze/xmZm9lMr+U2pyVVjNjW9nEAwUMzL8rVK62b47mL3aff16XCrlYYWnkaClWXE4W1tLHm5nixtR+LpXPSutToy/HxLmYOBPvW5kcrgdx4MXS8u3u1KdXscLiSH66dysVWpsIrkj8WjK0nha3pkc2p0dz6eFsoi8zGk2JPenB8HiEz8YiRrQdkJIT3zYT+Yz4OhXKxbnZAeaZ6EmG6PEgPS64n0YYKcwkomwiwsZ7eakvKA0KcTE4HYu2kU5AFMWD2ehygs8OPZkSOqWAvb+LCDlxvsPEO81+yhR0msNuIkz/G2UcAz3sUJAd6+XtDhfAcvyqxE6EnTEf2eexBB0YRyJeAqYtRspkcLY2u9tgFwG7rXBXm5GlCYFzi34mFvGzXi/Q7Q/NjwkJwSd6HVwHTpsMNiNoNTaZIRA1aNFmLW7Q4ZCuHQZdZshHYWGPrT/ADAucwPsAppuL9/rDfHfA28V0Om1mDGsGEW2DoaHWUKfS16r0aqW+XoVo1GSLlkJA2qRnO3CBsQ9EOMDh8vg8dKedcpAkaeuw4BYYMjZpNRplTX2Nok4hr1XI6xTVYL3KqKvDIY0N03tIhOu0RANuwEo5bFbS3EagKAajOIRgBhhtbNTVKpVKmUxZJVM9eqiWPdapFFCDikBAO4l4KNTnwnu8VgA2E3Ar1gKb9BACwiYdhOoQvFEPqTU6uaKmuqpKLq9WK2s0DWrIoLVgzRQBualWH435XdhfxxqOEjIJEs8AAAAASUVORK5CYII='); background-size: cover; display: block;\"\n  ></span>\n  <img\n        class=\"gatsby-resp-image-image\"\n        alt=\"result\"\n        title=\"result\"\n        src=\"/static/f0e39ee7209ba0d038c00b71cd260433/fcda8/handwritten-digits-recognition-result.png\"\n        srcset=\"/static/f0e39ee7209ba0d038c00b71cd260433/12f09/handwritten-digits-recognition-result.png 148w,\n/static/f0e39ee7209ba0d038c00b71cd260433/e4a3f/handwritten-digits-recognition-result.png 295w,\n/static/f0e39ee7209ba0d038c00b71cd260433/fcda8/handwritten-digits-recognition-result.png 590w,\n/static/f0e39ee7209ba0d038c00b71cd260433/efc66/handwritten-digits-recognition-result.png 885w,\n/static/f0e39ee7209ba0d038c00b71cd260433/72372/handwritten-digits-recognition-result.png 1149w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;\"\n        loading=\"lazy\"\n      />\n  </a>\n    </span></p>\n<p>代码在 GitHub 上。</p>","frontmatter":{"title":"First Attempt on Handwritten Digits Recogition","date":"November 26, 2017","tags":["machine-learning","notes"],"language":"zh-Hans"}},"site":{"siteMetadata":{"title":"Luyu Cheng"}}},"pageContext":{"id":"33c79087-4849-5fcd-a4e9-ad0311bf8601"}},"staticQueryHashes":["3649515864","63159454","684267432"]}