<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>J.J.&#39;s Blogs</title>
  
  <subtitle>技術筆記</subtitle>
  <link href="/atom.xml" rel="self"/>
  
  <link href="https://morosedog.gitlab.io/"/>
  <updated>2026-03-02T01:23:05.769Z</updated>
  <id>https://morosedog.gitlab.io/</id>
  
  <author>
    <name>J.J. Huang</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Python | OpenCV 與深度學習框架整合</title>
    <link href="https://morosedog.gitlab.io/python-opencv-20260302-python-opencv-framework/"/>
    <id>https://morosedog.gitlab.io/python-opencv-20260302-python-opencv-framework/</id>
    <published>2026-03-02T01:00:00.000Z</published>
    <updated>2026-03-02T01:23:05.769Z</updated>
    
    <content type="html"><![CDATA[<h2 id="📚-前言"><a href="#📚-前言" class="headerlink" title="📚 前言"></a>📚 前言</h2><p>在前一篇我們學會了 <strong>多物件偵測與追蹤 (YOLO + DeepSORT)</strong>，這是偏向「應用層」的做法，直接利用 OpenCV 的 DNN 模組載入模型並完成任務。<br>然而，若要進一步開發專案或研究，就需要更高的彈性與可擴充性。</p><p>整合 <strong>PyTorch</strong> 或 <strong>TensorFlow/Keras</strong> 的好處在於：</p><ul><li>可以載入各種預訓練模型（ResNet、MobileNet、YOLOv5 等）。</li><li>能使用自己訓練或微調過的模型，針對特定場景最佳化。</li><li>支援 GPU 加速，大幅提升即時推論效能。</li><li>允許修改網路結構、增加新層，甚至自行訓練，滿足研究與開發需求。</li><li>結合 OpenCV 的影像處理能力，打造更靈活的應用。</li></ul><p>這樣不僅能「跑起來」，還能「改得動」，真正把電腦視覺應用推向研究與實務的下一步。</p><h2 id="🎨-範例圖片-影片"><a href="#🎨-範例圖片-影片" class="headerlink" title="🎨 範例圖片/影片"></a>🎨 範例圖片/影片</h2><p>這裡我們使用 Pexels 提供的免費圖片素材：<a href="https://www.pexels.com/zh-tw/photo/29446313/" target="_blank" rel="external nofollow noopener noreferrer">Street Image</a>。<br>下載後將檔名改為 <code>street.jpg</code>，在程式碼範例中使用。這張圖片中有車輛與行人，非常適合用來測試物件分類與定位。</p><p>這裡我們使用 Pexels 提供的免費影片素材：<a href="https://www.pexels.com/zh-tw/video/6648153/" target="_blank" rel="external nofollow noopener noreferrer">Street Video</a>。<br>下載後將檔名改為 <code>street.mp4</code>，在程式碼範例中使用。影片中有車輛與行人，非常適合用來測試物件分類與定位。</p><h2 id="🔎-原理說明"><a href="#🔎-原理說明" class="headerlink" title="🔎 原理說明"></a>🔎 原理說明</h2><ul><li><strong>OpenCV</strong>：負責影像擷取、前處理與顯示。</li><li><strong>深度學習框架</strong>：負責模型載入、推論與微調。</li><li><strong>整合方式</strong>：框架輸出推論結果，OpenCV 負責繪製邊界框或文字。</li></ul><h2 id="📂-模型下載與使用說明"><a href="#📂-模型下載與使用說明" class="headerlink" title="📂 模型下載與使用說明"></a>📂 模型下載與使用說明</h2><h3 id="PyTorch"><a href="#PyTorch" class="headerlink" title="PyTorch"></a>PyTorch</h3><ul><li>使用 <code>torchvision.models</code> 載入預訓練模型 (ResNet、MobileNet 等)。</li><li>可進行微調或自訂網路結構。</li></ul><h3 id="TensorFlow-Keras"><a href="#TensorFlow-Keras" class="headerlink" title="TensorFlow/Keras"></a>TensorFlow/Keras</h3><ul><li>使用 <code>tf.keras.applications</code> 載入預訓練模型 (MobileNetV2、ResNet50 等)。</li><li>API 友善，適合快速應用與部署。</li></ul><h2 id="🧠-函式與參數說明"><a href="#🧠-函式與參數說明" class="headerlink" title="🧠 函式與參數說明"></a>🧠 函式與參數說明</h2><h3 id="PyTorch-—-torchvision-models"><a href="#PyTorch-—-torchvision-models" class="headerlink" title="PyTorch — torchvision.models"></a>PyTorch — <code>torchvision.models</code></h3><ul><li><strong><code>resnet18(weights=ResNet18_Weights.DEFAULT)</code></strong>：載入 ResNet18 預訓練模型，建議使用 <code>weights</code> 參數取代舊版的 <code>pretrained=True</code>，避免警告。</li><li><strong><code>model.eval()</code></strong>：切換到推論模式。</li><li><strong><code>weights.transforms()</code></strong>：取得官方推薦的前處理流程。</li><li><strong><code>torch.topk(probs, 5)</code></strong>：取出前 5 個類別與機率。</li></ul><h3 id="TensorFlow-Keras-—-tf-keras-applications"><a href="#TensorFlow-Keras-—-tf-keras-applications" class="headerlink" title="TensorFlow/Keras — tf.keras.applications"></a>TensorFlow/Keras — <code>tf.keras.applications</code></h3><ul><li><strong><code>MobileNetV2(weights=&quot;imagenet&quot;)</code></strong>：載入 MobileNetV2 預訓練模型。</li><li><strong><code>preprocess_input(x)</code></strong>：對輸入影像做正規化。</li><li><strong><code>model.predict(x)</code></strong>：進行推論，輸出 1000 類別的分數。</li><li><strong><code>decode_predictions(preds, top=5)</code></strong>：解碼分類結果，顯示前 5 個預測類別與機率。</li></ul><h2 id="💻-範例程式-—-PyTorch-整合-ResNet18"><a href="#💻-範例程式-—-PyTorch-整合-ResNet18" class="headerlink" title="💻 範例程式 — PyTorch 整合 (ResNet18)"></a>💻 範例程式 — PyTorch 整合 (ResNet18)</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> torch</span><br><span class="line"><span class="keyword">from</span> torchvision.models <span class="keyword">import</span> resnet18, ResNet18_Weights</span><br><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"><span class="keyword">from</span> PIL <span class="keyword">import</span> Image</span><br><span class="line"></span><br><span class="line">weights = ResNet18_Weights.DEFAULT</span><br><span class="line">model = resnet18(weights=weights)</span><br><span class="line">model.eval()</span><br><span class="line"></span><br><span class="line">preprocess = weights.transforms()</span><br><span class="line"></span><br><span class="line">img = cv2.imread(<span class="string">"street.jpg"</span>)</span><br><span class="line">img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)</span><br><span class="line">pil_img = Image.fromarray(img_rgb)</span><br><span class="line"></span><br><span class="line">input_tensor = preprocess(pil_img).unsqueeze(<span class="number">0</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">with</span> torch.no_grad():</span><br><span class="line">    outputs = model(input_tensor)</span><br><span class="line">    probs = torch.nn.functional.softmax(outputs[<span class="number">0</span>], dim=<span class="number">0</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 取前 5 個類別</span></span><br><span class="line">top5 = torch.topk(probs, <span class="number">5</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> idx, score <span class="keyword">in</span> zip(top5.indices, top5.values):</span><br><span class="line">    label = weights.meta[<span class="string">"categories"</span>][idx]</span><br><span class="line">    print(<span class="string">f"<span class="subst">&#123;label&#125;</span>: <span class="subst">&#123;score:<span class="number">.4</span>f&#125;</span>"</span>)</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/framework/01.png" alt><br><em>圖：使用 PyTorch ResNet18 對影像進行分類，Top-5 預測結果顯示模型的信心分布</em></p><h3 id="📊-ResNet18-Top-5-預測結果"><a href="#📊-ResNet18-Top-5-預測結果" class="headerlink" title="📊 ResNet18 Top-5 預測結果"></a>📊 ResNet18 Top-5 預測結果</h3><table><thead><tr><th>排名</th><th>類別 (Label)</th><th>機率 (Probability)</th></tr></thead><tbody><tr><td>1</td><td>police van</td><td>0.3636</td></tr><tr><td>2</td><td>ambulance</td><td>0.2735</td></tr><tr><td>3</td><td>garbage truck</td><td>0.1055</td></tr><tr><td>4</td><td>tow truck</td><td>0.0890</td></tr><tr><td>5</td><td>minivan</td><td>0.0556</td></tr></tbody></table><h2 id="💻-範例程式-—-TensorFlow-Keras-整合-MobileNetV2"><a href="#💻-範例程式-—-TensorFlow-Keras-整合-MobileNetV2" class="headerlink" title="💻 範例程式 — TensorFlow/Keras 整合 (MobileNetV2)"></a>💻 範例程式 — TensorFlow/Keras 整合 (MobileNetV2)</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> tensorflow <span class="keyword">as</span> tf</span><br><span class="line"><span class="keyword">from</span> tensorflow.keras.applications <span class="keyword">import</span> MobileNetV2</span><br><span class="line"><span class="keyword">from</span> tensorflow.keras.applications.mobilenet_v2 <span class="keyword">import</span> preprocess_input, decode_predictions</span><br><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line">model = MobileNetV2(weights=<span class="string">"imagenet"</span>)</span><br><span class="line"></span><br><span class="line">img = cv2.imread(<span class="string">"street.jpg"</span>)</span><br><span class="line">img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)</span><br><span class="line">img_resized = cv2.resize(img_rgb, (<span class="number">224</span>, <span class="number">224</span>))</span><br><span class="line"></span><br><span class="line">x = np.expand_dims(img_resized, axis=<span class="number">0</span>)</span><br><span class="line">x = preprocess_input(x)</span><br><span class="line"></span><br><span class="line">preds = model.predict(x)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 顯示前五個預測結果</span></span><br><span class="line">results = decode_predictions(preds, top=<span class="number">5</span>)[<span class="number">0</span>]</span><br><span class="line"><span class="keyword">for</span> (imagenet_id, label, prob) <span class="keyword">in</span> results:</span><br><span class="line">    print(<span class="string">f"<span class="subst">&#123;label&#125;</span>: <span class="subst">&#123;prob:<span class="number">.4</span>f&#125;</span>"</span>)</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/framework/02.png" alt><br><em>圖：使用 TensorFlow/Keras MobileNetV2 對影像進行分類，顯示前 5 個預測結果</em></p><h3 id="📊-MobileNetV2-Top-5-預測結果"><a href="#📊-MobileNetV2-Top-5-預測結果" class="headerlink" title="📊 MobileNetV2 Top-5 預測結果"></a>📊 MobileNetV2 Top-5 預測結果</h3><table><thead><tr><th>排名</th><th>類別 (Label)</th><th>機率 (Probability)</th></tr></thead><tbody><tr><td>1</td><td>minivan</td><td>0.2867</td></tr><tr><td>2</td><td>minibus</td><td>0.2489</td></tr><tr><td>3</td><td>police van</td><td>0.1733</td></tr><tr><td>4</td><td>cab</td><td>0.0288</td></tr><tr><td>5</td><td>limousine</td><td>0.0198</td></tr></tbody></table><h2 id="🛠️-與-OpenCV-結合的應用"><a href="#🛠️-與-OpenCV-結合的應用" class="headerlink" title="🛠️ 與 OpenCV 結合的應用"></a>🛠️ 與 OpenCV 結合的應用</h2><ul><li><strong>即時影像分類</strong>：用框架載入模型，OpenCV 負責影像擷取與顯示。</li><li><strong>物件偵測</strong>：用 PyTorch/TensorFlow 載入 YOLOv5/SSD 模型，OpenCV 負責繪製邊界框。</li><li><strong>追蹤整合</strong>：框架提供特徵抽取，OpenCV + DeepSORT 負責追蹤。</li></ul><h2 id="⚠️-注意事項"><a href="#⚠️-注意事項" class="headerlink" title="⚠️ 注意事項"></a>⚠️ 注意事項</h2><ul><li><strong>版本相容性</strong>：建議使用 Python 3.8–3.11，避免 3.12+ 的套件相容性問題。</li><li><strong>GPU 支援</strong>：若要使用 CUDA，需安裝對應版本的 PyTorch/TensorFlow。</li><li><strong>資料集準備</strong>：若要進行微調或訓練，需準備符合格式的影像資料集。</li></ul><h2 id="📊-應用場景"><a href="#📊-應用場景" class="headerlink" title="📊 應用場景"></a>📊 應用場景</h2><ul><li><strong>智慧交通</strong>：用框架載入 YOLOv5，結合 OpenCV 即時顯示。</li><li><strong>人流分析</strong>：用 ResNet/MobileNet 做分類，OpenCV 負責影像擷取。</li><li><strong>零售應用</strong>：用框架模型辨識商品，OpenCV 顯示結果。</li></ul><h2 id="🎯-結語"><a href="#🎯-結語" class="headerlink" title="🎯 結語"></a>🎯 結語</h2><p>本篇我們學會了如何將 <strong>OpenCV 與深度學習框架整合</strong>，不再只是「跑起來」，而是能夠載入、修改、甚至微調模型。<br>這讓我們的應用更靈活，能同時滿足研究與實務需求。</p><p>📖 如在學習過程中遇到疑問，或是想了解更多相關主題，建議回顧一下 <a href="/python-opencv-20260106-python-opencv-index"><strong>Python | OpenCV 系列導讀</strong></a>，掌握完整的章節目錄，方便快速找到你需要的內容。<br><br></p><blockquote><p>註：以上參考了<br><a href="https://docs.opencv.org/4.x/d9/df8/tutorial_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV 官方文件 — Tutorials</a><br><a href="https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV 官方文件 — Python Tutorials</a><br><a href="https://pytorch.org/get-started/locally/" target="_blank" rel="external nofollow noopener noreferrer">PyTorch 官方安裝指南</a><br><a href="https://www.tensorflow.org/guide/keras" target="_blank" rel="external nofollow noopener noreferrer">TensorFlow/Keras 官方文件</a><br><a href="https://www.pexels.com/zh-tw" target="_blank" rel="external nofollow noopener noreferrer">Pexels — 免費圖片與影片素材</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;📚-前言&quot;&gt;&lt;a href=&quot;#📚-前言&quot; class=&quot;headerlink&quot; title=&quot;📚 前言&quot;&gt;&lt;/a&gt;📚 前言&lt;/h2&gt;&lt;p&gt;在前一篇我們學會了 &lt;strong&gt;多物件偵測與追蹤 (YOLO + DeepSORT)&lt;/strong&gt;，這是偏向
      
    
    </summary>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/categories/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/"/>
    
      <category term="07.物件偵測與辨識篇" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/07-%E7%89%A9%E4%BB%B6%E5%81%B5%E6%B8%AC%E8%88%87%E8%BE%A8%E8%AD%98%E7%AF%87/"/>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/tags/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/tags/OpenCV/"/>
    
  </entry>
  
  <entry>
    <title>Python | OpenCV 多物件偵測與追蹤</title>
    <link href="https://morosedog.gitlab.io/python-opencv-20260301-python-opencv-multi-object/"/>
    <id>https://morosedog.gitlab.io/python-opencv-20260301-python-opencv-multi-object/</id>
    <published>2026-03-01T01:00:00.000Z</published>
    <updated>2026-03-02T01:23:05.769Z</updated>
    
    <content type="html"><![CDATA[<h2 id="📚-前言"><a href="#📚-前言" class="headerlink" title="📚 前言"></a>📚 前言</h2><p>在前一篇我們學會了 <strong>物件分類與定位</strong>。<br>這一篇要進一步介紹 <strong>多物件偵測與追蹤 (Multi-Object Detection &amp; Tracking)</strong>。<br>偵測是同時找到多個物件的位置，追蹤則是持續更新它們的移動並維持 ID。這是進入智慧監控與交通分析的重要技術。</p><h2 id="🎨-範例圖片-影片"><a href="#🎨-範例圖片-影片" class="headerlink" title="🎨 範例圖片/影片"></a>🎨 範例圖片/影片</h2><p>這裡我們使用 Pexels 提供的免費圖片素材：<a href="https://www.pexels.com/zh-tw/photo/29446313/" target="_blank" rel="external nofollow noopener noreferrer">Street Image</a>。<br>下載後將檔名改為 <code>street.jpg</code>，在程式碼範例中使用。這張圖片中有車輛與行人，非常適合用來測試物件分類與定位。</p><p>這裡我們使用 Pexels 提供的免費影片素材：<a href="https://www.pexels.com/zh-tw/video/6648153/" target="_blank" rel="external nofollow noopener noreferrer">Street Video</a>。<br>下載後將檔名改為 <code>street.mp4</code>，在程式碼範例中使用。影片中有車輛與行人，非常適合用來測試物件分類與定位。</p><h2 id="🔎-原理說明"><a href="#🔎-原理說明" class="headerlink" title="🔎 原理說明"></a>🔎 原理說明</h2><ul><li><strong>多物件偵測</strong>：同時輸出多個邊界框與類別。</li><li><strong>追蹤 (Tracking)</strong>：持續更新物件位置，並維持唯一 ID。</li><li><strong>常見方法</strong>：<ul><li>YOLO (You Only Look Once)：即時多物件偵測。</li><li>SSD (Single Shot Detector)：輕量化偵測模型。</li><li>DeepSORT：結合偵測結果，維持物件 ID。</li></ul></li></ul><h2 id="📂-模型下載與使用說明"><a href="#📂-模型下載與使用說明" class="headerlink" title="📂 模型下載與使用說明"></a>📂 模型下載與使用說明</h2><h3 id="YOLOv3-tiny-Darknet"><a href="#YOLOv3-tiny-Darknet" class="headerlink" title="YOLOv3-tiny (Darknet)"></a>YOLOv3-tiny (Darknet)</h3><p><img loading="lazy" src="/images/python/opencv/multi-object/01.png" alt></p><ul><li>檔案名稱：<ul><li><code>yolov3-tiny.cfg</code></li><li><code>yolov3-tiny.weights</code></li></ul></li><li>下載來源：<ul><li><a href="https://pjreddie.com/darknet/yolo/" target="_blank" rel="external nofollow noopener noreferrer">YOLO 官方網站 — pjreddie.com/darknet/yolo</a></li></ul></li><li>使用方式：<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">net = cv2.dnn.readNetFromDarknet(<span class="string">"yolov3-tiny.cfg"</span>, <span class="string">"yolov3-tiny.weights"</span>)</span><br></pre></td></tr></table></figure></li></ul><blockquote><p>💡 YOLOv3-tiny 是輕量化版本，適合即時應用。</p></blockquote><h2 id="🧠-函式與參數說明"><a href="#🧠-函式與參數說明" class="headerlink" title="🧠 函式與參數說明"></a>🧠 函式與參數說明</h2><h3 id="📌-cv2-dnn-readNetFromDarknet"><a href="#📌-cv2-dnn-readNetFromDarknet" class="headerlink" title="📌 cv2.dnn.readNetFromDarknet()"></a>📌 <code>cv2.dnn.readNetFromDarknet()</code></h3><p>載入 YOLO 模型</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">net = cv2.dnn.readNetFromDarknet(cfg, weights)</span><br></pre></td></tr></table></figure><ul><li><strong>cfg</strong>：模型結構檔案。</li><li><strong>weights</strong>：訓練好的權重檔案。</li></ul><h3 id="📌-cv2-dnn-blobFromImage"><a href="#📌-cv2-dnn-blobFromImage" class="headerlink" title="📌 cv2.dnn.blobFromImage()"></a>📌 <code>cv2.dnn.blobFromImage()</code></h3><p>將圖片轉換成 DNN 輸入格式</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">blob = cv2.dnn.blobFromImage(image, scalefactor, size, mean, swapRB, crop)</span><br></pre></td></tr></table></figure><ul><li><strong>image</strong>：輸入圖片。</li><li><strong>scalefactor</strong>：縮放比例，常用 <code>1/255.0</code>。</li><li><strong>size</strong>：輸入大小 <code>(width, height)</code>，例如 <code>(416, 416)</code>。</li><li><strong>mean</strong>：減去的平均值，常用 <code>(0, 0, 0)</code>。</li><li><strong>swapRB</strong>：是否交換 R 與 B 通道，常用 <code>True</code>。</li><li><strong>crop</strong>：是否裁切圖片，常用 <code>False</code>。</li></ul><h2 id="💻-範例程式-—-YOLO-多物件偵測-圖片"><a href="#💻-範例程式-—-YOLO-多物件偵測-圖片" class="headerlink" title="💻 範例程式 — YOLO 多物件偵測 (圖片)"></a>💻 範例程式 — YOLO 多物件偵測 (圖片)</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line">net = cv2.dnn.readNetFromDarknet(<span class="string">"models/yolov3-tiny.cfg"</span>, <span class="string">"models/yolov3-tiny.weights"</span>)</span><br><span class="line">layer_names = net.getLayerNames()</span><br><span class="line">output_layers = [layer_names[i - <span class="number">1</span>] <span class="keyword">for</span> i <span class="keyword">in</span> net.getUnconnectedOutLayers()]</span><br><span class="line"></span><br><span class="line">img = cv2.imread(<span class="string">"street.jpg"</span>)</span><br><span class="line">(h, w) = img.shape[:<span class="number">2</span>]</span><br><span class="line">blob = cv2.dnn.blobFromImage(img, <span class="number">1</span>/<span class="number">255.0</span>, (<span class="number">416</span>, <span class="number">416</span>), swapRB=<span class="literal">True</span>, crop=<span class="literal">False</span>)</span><br><span class="line"></span><br><span class="line">net.setInput(blob)</span><br><span class="line">outputs = net.forward(output_layers)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> output <span class="keyword">in</span> outputs:</span><br><span class="line">    <span class="keyword">for</span> detection <span class="keyword">in</span> output:</span><br><span class="line">        scores = detection[<span class="number">5</span>:]</span><br><span class="line">        class_id = int(scores.argmax())</span><br><span class="line">        confidence = scores[class_id]</span><br><span class="line">        <span class="keyword">if</span> confidence &gt; <span class="number">0.5</span>:</span><br><span class="line">            box = detection[<span class="number">0</span>:<span class="number">4</span>] * [w, h, w, h]</span><br><span class="line">            (centerX, centerY, width, height) = box.astype(<span class="string">"int"</span>)</span><br><span class="line">            x = int(centerX - width / <span class="number">2</span>)</span><br><span class="line">            y = int(centerY - height / <span class="number">2</span>)</span><br><span class="line">            cv2.rectangle(img, (x, y), (x + int(width), y + int(height)), (<span class="number">0</span>, <span class="number">255</span>, <span class="number">0</span>), <span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">cv2.imshow(<span class="string">"YOLO Object Detection"</span>, img)</span><br><span class="line">cv2.waitKey(<span class="number">0</span>)</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/multi-object/02.gif" alt><br><em>圖：圖片多物件偵測，框選多個物件</em></p><h2 id="💻-範例程式-—-YOLO-多物件偵測-影片"><a href="#💻-範例程式-—-YOLO-多物件偵測-影片" class="headerlink" title="💻 範例程式 — YOLO 多物件偵測 (影片)"></a>💻 範例程式 — YOLO 多物件偵測 (影片)</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line">net = cv2.dnn.readNetFromDarknet(<span class="string">"models/yolov3-tiny.cfg"</span>, <span class="string">"models/yolov3-tiny.weights"</span>)</span><br><span class="line">layer_names = net.getLayerNames()</span><br><span class="line">output_layers = [layer_names[i - <span class="number">1</span>] <span class="keyword">for</span> i <span class="keyword">in</span> net.getUnconnectedOutLayers()]</span><br><span class="line"></span><br><span class="line">cap = cv2.VideoCapture(<span class="string">"street.mp4"</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    ret, frame = cap.read()</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> ret:</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">    (h, w) = frame.shape[:<span class="number">2</span>]</span><br><span class="line">    blob = cv2.dnn.blobFromImage(frame, <span class="number">1</span>/<span class="number">255.0</span>, (<span class="number">416</span>, <span class="number">416</span>), swapRB=<span class="literal">True</span>, crop=<span class="literal">False</span>)</span><br><span class="line"></span><br><span class="line">    net.setInput(blob)</span><br><span class="line">    outputs = net.forward(output_layers)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> output <span class="keyword">in</span> outputs:</span><br><span class="line">        <span class="keyword">for</span> detection <span class="keyword">in</span> output:</span><br><span class="line">            scores = detection[<span class="number">5</span>:]</span><br><span class="line">            class_id = int(scores.argmax())</span><br><span class="line">            confidence = scores[class_id]</span><br><span class="line">            <span class="keyword">if</span> confidence &gt; <span class="number">0.5</span>:</span><br><span class="line">                box = detection[<span class="number">0</span>:<span class="number">4</span>] * [w, h, w, h]</span><br><span class="line">                (centerX, centerY, width, height) = box.astype(<span class="string">"int"</span>)</span><br><span class="line">                x = int(centerX - width / <span class="number">2</span>)</span><br><span class="line">                y = int(centerY - height / <span class="number">2</span>)</span><br><span class="line">                cv2.rectangle(frame, (x, y), (x + int(width), y + int(height)), (<span class="number">0</span>, <span class="number">255</span>, <span class="number">0</span>), <span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">    cv2.imshow(<span class="string">"YOLO Video Detection"</span>, frame)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> cv2.waitKey(<span class="number">30</span>) &amp; <span class="number">0xFF</span> == ord(<span class="string">'q'</span>):</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">cap.release()</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/multi-object/03.gif" alt><br><em>圖：影片多物件偵測，逐幀框選多個物件</em></p><h2 id="💻-範例程式-—-YOLO-DeepSORT-多物件追蹤"><a href="#💻-範例程式-—-YOLO-DeepSORT-多物件追蹤" class="headerlink" title="💻 範例程式 — YOLO + DeepSORT 多物件追蹤"></a>💻 範例程式 — YOLO + DeepSORT 多物件追蹤</h2><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 範例僅示意，需安裝 deep_sort 套件</span></span><br><span class="line"><span class="comment"># pip install deep-sort-realtime</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> deep_sort_realtime.deepsort_tracker <span class="keyword">import</span> DeepSort</span><br><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line">net = cv2.dnn.readNetFromDarknet(<span class="string">"models/yolov3-tiny.cfg"</span>, <span class="string">"models/yolov3-tiny.weights"</span>)</span><br><span class="line">layer_names = net.getLayerNames()</span><br><span class="line">output_layers = [layer_names[i - <span class="number">1</span>] <span class="keyword">for</span> i <span class="keyword">in</span> net.getUnconnectedOutLayers()]</span><br><span class="line"></span><br><span class="line">tracker = DeepSort(max_age=<span class="number">30</span>)</span><br><span class="line"></span><br><span class="line">cap = cv2.VideoCapture(<span class="string">"street.mp4"</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    ret, frame = cap.read()</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> ret:</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">    (h, w) = frame.shape[:<span class="number">2</span>]</span><br><span class="line">    blob = cv2.dnn.blobFromImage(frame, <span class="number">1</span>/<span class="number">255.0</span>, (<span class="number">416</span>, <span class="number">416</span>), swapRB=<span class="literal">True</span>, crop=<span class="literal">False</span>)</span><br><span class="line">    net.setInput(blob)</span><br><span class="line">    outputs = net.forward(output_layers)</span><br><span class="line"></span><br><span class="line">    detections = []</span><br><span class="line">    <span class="keyword">for</span> output <span class="keyword">in</span> outputs:</span><br><span class="line">        <span class="keyword">for</span> detection <span class="keyword">in</span> output:</span><br><span class="line">            scores = detection[<span class="number">5</span>:]</span><br><span class="line">            class_id = int(scores.argmax())</span><br><span class="line">            confidence = scores[class_id]</span><br><span class="line">            <span class="keyword">if</span> confidence &gt; <span class="number">0.5</span>:</span><br><span class="line">                box = detection[<span class="number">0</span>:<span class="number">4</span>] * [w, h, w, h]</span><br><span class="line">                (centerX, centerY, width, height) = box.astype(<span class="string">"int"</span>)</span><br><span class="line">                x = int(centerX - width / <span class="number">2</span>)</span><br><span class="line">                y = int(centerY - height / <span class="number">2</span>)</span><br><span class="line">                detections.append(([x, y, int(width), int(height)], confidence, class_id))</span><br><span class="line"></span><br><span class="line">    tracks = tracker.update_tracks(detections, frame=frame)</span><br><span class="line">    <span class="keyword">for</span> track <span class="keyword">in</span> tracks:</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> track.is_confirmed():</span><br><span class="line">            <span class="keyword">continue</span></span><br><span class="line">        x1, y1, x2, y2 = track.to_ltrb()</span><br><span class="line">        track_id = track.track_id</span><br><span class="line">        cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (<span class="number">0</span>, <span class="number">255</span>, <span class="number">255</span>), <span class="number">2</span>)</span><br><span class="line">        cv2.putText(frame, <span class="string">f"ID <span class="subst">&#123;track_id&#125;</span>"</span>, (int(x1), int(y1)<span class="number">-10</span>),</span><br><span class="line">                    cv2.FONT_HERSHEY_SIMPLEX, <span class="number">0.5</span>, (<span class="number">0</span>,<span class="number">255</span>,<span class="number">255</span>), <span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">    cv2.imshow(<span class="string">"YOLO + DeepSORT Tracking"</span>, frame)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> cv2.waitKey(<span class="number">30</span>) &amp; <span class="number">0xFF</span> == ord(<span class="string">'q'</span>):</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">cap.release()</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/multi-object/04.gif" alt><br><em>圖：影片多物件偵測與追蹤，持續追蹤多個物件並維持唯一 ID</em></p><h2 id="🛠️-使用-DeepSORT-常見問題與解決辦法"><a href="#🛠️-使用-DeepSORT-常見問題與解決辦法" class="headerlink" title="🛠️ 使用 DeepSORT 常見問題與解決辦法"></a>🛠️ 使用 DeepSORT 常見問題與解決辦法</h2><p>在整合 YOLO 與 DeepSORT 的過程中，常會遇到一些環境或套件相容性的問題：</p><ul><li><p><strong><code>ModuleNotFoundError: No module named &#39;pkg_resources&#39;</code></strong>  </p><ul><li>原因：Python 3.12+ 移除了 <code>pkg_resources</code>，導致 DeepSORT 初始化失敗。  </li><li>解決：改用 Python 3.10 或 3.11 建立虛擬環境，並安裝 <code>setuptools</code>。</li></ul></li><li><p><strong><code>ModuleNotFoundError: No module named &#39;torch&#39;</code></strong>  </p><ul><li>原因：PyTorch 尚未安裝。  </li><li>解決：在虛擬環境中安裝 PyTorch 與 TorchVision：  <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install torch torchvision</span><br></pre></td></tr></table></figure></li></ul></li><li><p><strong><code>ModuleNotFoundError: No module named &#39;torchvision&#39;</code></strong>  </p><ul><li>原因：TorchVision 尚未安裝。  </li><li>解決：補安裝 TorchVision：  <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install torchvision</span><br></pre></td></tr></table></figure></li></ul></li><li><p><strong>環境衝突或安裝不完整</strong>  </p><ul><li>症狀：明明安裝了套件，仍然報錯。  </li><li>解決：確認 Python 解譯器路徑正確，必要時刪掉 <code>.venv</code>，重新建立乾淨的虛擬環境，並一次安裝所有依賴：  <figure class="highlight bash"><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">python3.10 -m venv .venv310</span><br><span class="line">.\.venv310\Scripts\activate</span><br><span class="line">pip install --upgrade pip wheel setuptools</span><br><span class="line">pip install numpy opencv-python torch torchvision deep-sort-realtime</span><br></pre></td></tr></table></figure></li></ul></li></ul><blockquote><p>💡 建議建立 <code>requirements.txt</code>，一次安裝所有依賴，避免逐一補套件。</p></blockquote><h2 id="⚠️-注意事項"><a href="#⚠️-注意事項" class="headerlink" title="⚠️ 注意事項"></a>⚠️ 注意事項</h2><ul><li>YOLO 模型檔案需先下載並放在專案目錄。</li><li>YOLOv3-tiny 適合即時應用，但準確度有限。</li><li>DeepSORT 需要額外的 ReID 模型，才能維持物件 ID。</li><li>偵測速度與準確度會因模型不同而有差異。</li></ul><h2 id="📊-應用場景"><a href="#📊-應用場景" class="headerlink" title="📊 應用場景"></a>📊 應用場景</h2><ul><li><strong>交通監控</strong>：同時追蹤多輛車輛。</li><li><strong>群眾分析</strong>：統計人流數量與移動路徑。</li><li><strong>智慧零售</strong>：追蹤顧客行為。</li><li><strong>安全監控</strong>：辨識並追蹤可疑人物。</li></ul><h2 id="🎯-結語"><a href="#🎯-結語" class="headerlink" title="🎯 結語"></a>🎯 結語</h2><p>本篇我們學會了如何使用 OpenCV 與 YOLO 模型進行 <strong>多物件偵測與追蹤</strong>，並結合 DeepSORT 維持物件 ID。<br>這是從「物件分類與定位」延伸而來的重要技術，能應用在交通監控、群眾分析與智慧零售等場景。</p><p>📖 如在學習過程中遇到疑問，或是想了解更多相關主題，建議回顧一下 <a href="/python-opencv-20260106-python-opencv-index"><strong>Python | OpenCV 系列導讀</strong></a>，掌握完整的章節目錄，方便快速找到你需要的內容。<br><br></p><blockquote><p>註：以上參考了<br><a href="https://docs.opencv.org/4.x/d9/df8/tutorial_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV 官方文件 — Tutorials</a><br><a href="https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV 官方文件 — Python Tutorials</a><br><a href="https://pjreddie.com/darknet/yolo/" target="_blank" rel="external nofollow noopener noreferrer">YOLO 官方網站 — pjreddie.com/darknet/yolo</a><br><a href="https://github.com/levan92/deep-sort-realtime" target="_blank" rel="external nofollow noopener noreferrer">deep-sort-realtime GitHub</a><br><a href="https://www.pexels.com/zh-tw" target="_blank" rel="external nofollow noopener noreferrer">Pexels — 免費圖片與影片素材</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;📚-前言&quot;&gt;&lt;a href=&quot;#📚-前言&quot; class=&quot;headerlink&quot; title=&quot;📚 前言&quot;&gt;&lt;/a&gt;📚 前言&lt;/h2&gt;&lt;p&gt;在前一篇我們學會了 &lt;strong&gt;物件分類與定位&lt;/strong&gt;。&lt;br&gt;這一篇要進一步介紹 &lt;strong&gt;多
      
    
    </summary>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/categories/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/"/>
    
      <category term="07.物件偵測與辨識篇" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/07-%E7%89%A9%E4%BB%B6%E5%81%B5%E6%B8%AC%E8%88%87%E8%BE%A8%E8%AD%98%E7%AF%87/"/>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/tags/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/tags/OpenCV/"/>
    
  </entry>
  
  <entry>
    <title>Python | OpenCV 物件分類與定位</title>
    <link href="https://morosedog.gitlab.io/python-opencv-20260228-python-opencv-object-localization/"/>
    <id>https://morosedog.gitlab.io/python-opencv-20260228-python-opencv-object-localization/</id>
    <published>2026-02-28T01:00:00.000Z</published>
    <updated>2026-03-02T01:23:05.769Z</updated>
    
    <content type="html"><![CDATA[<h2 id="📚-前言"><a href="#📚-前言" class="headerlink" title="📚 前言"></a>📚 前言</h2><p>在前一篇我們學會了 <strong>人臉偵測與追蹤</strong>。<br>這一篇要進一步介紹 <strong>物件分類與定位 (Object Classification &amp; Localization)</strong>。<br>分類是判斷「這是什麼物件」，定位則是標記「它在哪裡」。這是進入多物件偵測與追蹤之前的重要基礎。</p><h2 id="🎨-範例圖片-影片"><a href="#🎨-範例圖片-影片" class="headerlink" title="🎨 範例圖片/影片"></a>🎨 範例圖片/影片</h2><p>這裡我們使用 Pexels 提供的免費圖片素材：<a href="https://www.pexels.com/zh-tw/photo/29446313/" target="_blank" rel="external nofollow noopener noreferrer">Street Image</a>。<br>下載後將檔名改為 <code>street.jpg</code>，在程式碼範例中使用。這張圖片中有車輛與行人，非常適合用來測試物件分類與定位。</p><p>這裡我們使用 Pexels 提供的免費影片素材：<a href="https://www.pexels.com/zh-tw/video/6648153/" target="_blank" rel="external nofollow noopener noreferrer">Street Video</a>。<br>下載後將檔名改為 <code>street.mp4</code>，在程式碼範例中使用。影片中有車輛與行人，非常適合用來測試物件分類與定位。</p><h2 id="🔎-原理說明"><a href="#🔎-原理說明" class="headerlink" title="🔎 原理說明"></a>🔎 原理說明</h2><ul><li><strong>分類 (Classification)</strong>：利用 CNN/DNN 模型判斷物件的類別，例如「人」、「車」、「狗」。</li><li><strong>定位 (Localization)</strong>：輸出邊界框座標 <code>(x, y, w, h)</code>，標記物件位置。</li><li><strong>OpenCV DNN 模組</strong>：支援 Caffe、TensorFlow、ONNX 等模型，可用於物件分類與定位。</li></ul><h2 id="📂-模型下載與使用說明"><a href="#📂-模型下載與使用說明" class="headerlink" title="📂 模型下載與使用說明"></a>📂 模型下載與使用說明</h2><h3 id="MobileNet-SSD-Caffe"><a href="#MobileNet-SSD-Caffe" class="headerlink" title="MobileNet SSD (Caffe)"></a>MobileNet SSD (Caffe)</h3><ul><li>檔案名稱：<ul><li><code>MobileNetSSD_deploy.prototxt</code></li><li><code>MobileNetSSD_deploy.caffemodel</code></li></ul></li><li>下載來源：<ul><li><a href="https://github.com/chuanqi305/MobileNet-SSD/blob/master/deploy.prototxt" target="_blank" rel="external nofollow noopener noreferrer">GitHub — MobileNetSSD prototxt</a></li><li><a href="https://github.com/chuanqi305/MobileNet-SSD/blob/master/mobilenet_iter_73000.caffemodel" target="_blank" rel="external nofollow noopener noreferrer">GitHub — MobileNetSSD caffemodel</a></li></ul></li><li>使用方式：<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">net = cv2.dnn.readNetFromCaffe(<span class="string">"MobileNetSSD_deploy.prototxt"</span>, <span class="string">"MobileNetSSD_deploy.caffemodel"</span>)</span><br></pre></td></tr></table></figure></li></ul><blockquote><p>💡 下載後的檔案名稱可能不一致，可以自行重新命名即可。</p></blockquote><h2 id="🧠-函式與參數說明"><a href="#🧠-函式與參數說明" class="headerlink" title="🧠 函式與參數說明"></a>🧠 函式與參數說明</h2><h3 id="📌-cv2-dnn-readNetFromCaffe"><a href="#📌-cv2-dnn-readNetFromCaffe" class="headerlink" title="📌 cv2.dnn.readNetFromCaffe()"></a>📌 <code>cv2.dnn.readNetFromCaffe()</code></h3><p>載入 Caffe 模型</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">net = cv2.dnn.readNetFromCaffe(prototxt, model)</span><br></pre></td></tr></table></figure><ul><li><strong>prototxt</strong>：模型結構檔案。</li><li><strong>model</strong>：訓練好的權重檔案。</li></ul><h3 id="📌-cv2-dnn-blobFromImage"><a href="#📌-cv2-dnn-blobFromImage" class="headerlink" title="📌 cv2.dnn.blobFromImage()"></a>📌 <code>cv2.dnn.blobFromImage()</code></h3><p>將圖片轉換成 DNN 輸入格式</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">blob = cv2.dnn.blobFromImage(image, scalefactor, size, mean, swapRB, crop)</span><br></pre></td></tr></table></figure><ul><li><strong>image</strong>：輸入圖片。</li><li><strong>scalefactor</strong>：縮放比例，常用 <code>0.007843</code>。</li><li><strong>size</strong>：輸入大小 <code>(width, height)</code>，例如 <code>(300, 300)</code>。</li><li><strong>mean</strong>：減去的平均值，常用 <code>(127.5, 127.5, 127.5)</code>。</li><li><strong>swapRB</strong>：是否交換 R 與 B 通道，常用 <code>False</code>。</li><li><strong>crop</strong>：是否裁切圖片，常用 <code>False</code>。</li></ul><h2 id="💻-範例程式-—-圖片物件分類與定位"><a href="#💻-範例程式-—-圖片物件分類與定位" class="headerlink" title="💻 範例程式 — 圖片物件分類與定位"></a>💻 範例程式 — 圖片物件分類與定位</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line">net = cv2.dnn.readNetFromCaffe(<span class="string">"models/MobileNetSSD_deploy.prototxt"</span>, <span class="string">"models/MobileNetSSD_deploy.caffemodel"</span>)</span><br><span class="line"></span><br><span class="line">CLASSES = [<span class="string">"background"</span>, <span class="string">"aeroplane"</span>, <span class="string">"bicycle"</span>, <span class="string">"bird"</span>, <span class="string">"boat"</span>,</span><br><span class="line">           <span class="string">"bottle"</span>, <span class="string">"bus"</span>, <span class="string">"car"</span>, <span class="string">"cat"</span>, <span class="string">"chair"</span>, <span class="string">"cow"</span>, <span class="string">"diningtable"</span>,</span><br><span class="line">           <span class="string">"dog"</span>, <span class="string">"horse"</span>, <span class="string">"motorbike"</span>, <span class="string">"person"</span>, <span class="string">"pottedplant"</span>,</span><br><span class="line">           <span class="string">"sheep"</span>, <span class="string">"sofa"</span>, <span class="string">"train"</span>, <span class="string">"tvmonitor"</span>]</span><br><span class="line"></span><br><span class="line">img = cv2.imread(<span class="string">"street.jpg"</span>)</span><br><span class="line">(h, w) = img.shape[:<span class="number">2</span>]</span><br><span class="line">blob = cv2.dnn.blobFromImage(cv2.resize(img, (<span class="number">300</span>, <span class="number">300</span>)), <span class="number">0.007843</span>, (<span class="number">300</span>, <span class="number">300</span>), <span class="number">127.5</span>)</span><br><span class="line"></span><br><span class="line">net.setInput(blob)</span><br><span class="line">detections = net.forward()</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(detections.shape[<span class="number">2</span>]):</span><br><span class="line">    confidence = detections[<span class="number">0</span>, <span class="number">0</span>, i, <span class="number">2</span>]</span><br><span class="line">    <span class="keyword">if</span> confidence &gt; <span class="number">0.5</span>:</span><br><span class="line">        idx = int(detections[<span class="number">0</span>, <span class="number">0</span>, i, <span class="number">1</span>])</span><br><span class="line">        box = detections[<span class="number">0</span>, <span class="number">0</span>, i, <span class="number">3</span>:<span class="number">7</span>] * [w, h, w, h]</span><br><span class="line">        (x1, y1, x2, y2) = box.astype(<span class="string">"int"</span>)</span><br><span class="line">        label = CLASSES[idx]</span><br><span class="line">        cv2.rectangle(img, (x1, y1), (x2, y2), (<span class="number">0</span>, <span class="number">255</span>, <span class="number">0</span>), <span class="number">2</span>)</span><br><span class="line">        cv2.putText(img, label, (x1, y1<span class="number">-10</span>), cv2.FONT_HERSHEY_SIMPLEX, <span class="number">0.5</span>, (<span class="number">0</span>,<span class="number">255</span>,<span class="number">0</span>), <span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">cv2.imshow(<span class="string">"Object Classification &amp; Localization"</span>, img)</span><br><span class="line">cv2.waitKey(<span class="number">0</span>)</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/object-localization/01.gif" alt><br><em>圖：圖片物件分類與定位，框選並標記物件類別</em></p><h2 id="💻-範例程式-—-影片物件分類與定位"><a href="#💻-範例程式-—-影片物件分類與定位" class="headerlink" title="💻 範例程式 — 影片物件分類與定位"></a>💻 範例程式 — 影片物件分類與定位</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line">net = cv2.dnn.readNetFromCaffe(<span class="string">"models/MobileNetSSD_deploy.prototxt"</span>, <span class="string">"models/MobileNetSSD_deploy.caffemodel"</span>)</span><br><span class="line"></span><br><span class="line">CLASSES = [<span class="string">"background"</span>, <span class="string">"aeroplane"</span>, <span class="string">"bicycle"</span>, <span class="string">"bird"</span>, <span class="string">"boat"</span>,</span><br><span class="line">           <span class="string">"bottle"</span>, <span class="string">"bus"</span>, <span class="string">"car"</span>, <span class="string">"cat"</span>, <span class="string">"chair"</span>, <span class="string">"cow"</span>, <span class="string">"diningtable"</span>,</span><br><span class="line">           <span class="string">"dog"</span>, <span class="string">"horse"</span>, <span class="string">"motorbike"</span>, <span class="string">"person"</span>, <span class="string">"pottedplant"</span>,</span><br><span class="line">           <span class="string">"sheep"</span>, <span class="string">"sofa"</span>, <span class="string">"train"</span>, <span class="string">"tvmonitor"</span>]</span><br><span class="line"></span><br><span class="line">cap = cv2.VideoCapture(<span class="string">"street.mp4"</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    ret, frame = cap.read()</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> ret:</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">    (h, w) = frame.shape[:<span class="number">2</span>]</span><br><span class="line">    blob = cv2.dnn.blobFromImage(cv2.resize(frame, (<span class="number">300</span>, <span class="number">300</span>)), <span class="number">0.007843</span>, (<span class="number">300</span>, <span class="number">300</span>), <span class="number">127.5</span>)</span><br><span class="line"></span><br><span class="line">    net.setInput(blob)</span><br><span class="line">    detections = net.forward()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> range(detections.shape[<span class="number">2</span>]):</span><br><span class="line">        confidence = detections[<span class="number">0</span>, <span class="number">0</span>, i, <span class="number">2</span>]</span><br><span class="line">        <span class="keyword">if</span> confidence &gt; <span class="number">0.5</span>:</span><br><span class="line">            idx = int(detections[<span class="number">0</span>, <span class="number">0</span>, i, <span class="number">1</span>])</span><br><span class="line">            box = detections[<span class="number">0</span>, <span class="number">0</span>, i, <span class="number">3</span>:<span class="number">7</span>] * [w, h, w, h]</span><br><span class="line">            (x1, y1, x2, y2) = box.astype(<span class="string">"int"</span>)</span><br><span class="line">            label = CLASSES[idx]</span><br><span class="line">            cv2.rectangle(frame, (x1, y1), (x2, y2), (<span class="number">0</span>, <span class="number">255</span>, <span class="number">0</span>), <span class="number">2</span>)</span><br><span class="line">            cv2.putText(frame, label, (x1, y1<span class="number">-10</span>), cv2.FONT_HERSHEY_SIMPLEX, <span class="number">0.5</span>, (<span class="number">0</span>,<span class="number">255</span>,<span class="number">0</span>), <span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">    cv2.imshow(<span class="string">"Video Object Classification &amp; Localization"</span>, frame)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> cv2.waitKey(<span class="number">30</span>) &amp; <span class="number">0xFF</span> == ord(<span class="string">'q'</span>):</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">cap.release()</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/object-localization/02.gif" alt><br><em>圖：影片物件分類與定位，逐幀框選並標記物件類別</em></p><h2 id="⚠️-注意事項"><a href="#⚠️-注意事項" class="headerlink" title="⚠️ 注意事項"></a>⚠️ 注意事項</h2><ul><li>模型檔案需先下載並放在專案目錄。</li><li>MobileNet SSD 適合即時應用，但準確度有限。</li><li>若需要更高準確度，可以使用 YOLO 或 Faster R-CNN。</li><li>偵測速度與準確度會因模型不同而有差異。</li></ul><h2 id="📊-應用場景"><a href="#📊-應用場景" class="headerlink" title="📊 應用場景"></a>📊 應用場景</h2><ul><li><strong>自駕車</strong>：辨識道路上的車輛與行人。</li><li><strong>監控系統</strong>：即時分類並定位可疑物件。</li><li><strong>智慧零售</strong>：辨識商品種類與位置。</li><li><strong>互動遊戲</strong>：追蹤玩家與物件位置。</li></ul><h2 id="🎯-結語"><a href="#🎯-結語" class="headerlink" title="🎯 結語"></a>🎯 結語</h2><p>本篇我們學會了如何使用 OpenCV <strong>物件分類與定位</strong>，透過 DNN 模型在圖片與影片中辨識物件並標記位置。<br>這是進入 <strong>多物件偵測與追蹤</strong> 的重要基礎，因為只有先理解「分類」與「定位」的概念，才能進一步處理多個物件的同時偵測與持續追蹤。</p><p>📖 如在學習過程中遇到疑問，或是想了解更多相關主題，建議回顧一下 <a href="/python-opencv-20260106-python-opencv-index"><strong>Python | OpenCV 系列導讀</strong></a>，掌握完整的章節目錄，方便快速找到你需要的內容。<br><br></p><blockquote><p>註：以上參考了<br><a href="https://docs.opencv.org/4.x/d9/df8/tutorial_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV 官方文件 — Tutorials</a><br><a href="https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV 官方文件 — Python Tutorials</a><br><a href="https://github.com/opencv/opencv/tree/master/data/haarcascades" target="_blank" rel="external nofollow noopener noreferrer">OpenCV GitHub — Haar Cascades</a><br><a href="https://github.com/opencv/opencv/tree/master/data/lbpcascades" target="_blank" rel="external nofollow noopener noreferrer">OpenCV GitHub — LBP Cascades</a><br><a href="https://github.com/opencv/opencv/tree/master/samples/dnn/face_detector" target="_blank" rel="external nofollow noopener noreferrer">OpenCV GitHub — DNN Face Detector (deploy.prototxt)</a><br><a href="https://github.com/chuanqi305/MobileNet-SSD" target="_blank" rel="external nofollow noopener noreferrer">GitHub — MobileNetSSD 模型</a><br><a href="https://www.pexels.com/zh-tw" target="_blank" rel="external nofollow noopener noreferrer">Pexels — 免費影片素材</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;📚-前言&quot;&gt;&lt;a href=&quot;#📚-前言&quot; class=&quot;headerlink&quot; title=&quot;📚 前言&quot;&gt;&lt;/a&gt;📚 前言&lt;/h2&gt;&lt;p&gt;在前一篇我們學會了 &lt;strong&gt;人臉偵測與追蹤&lt;/strong&gt;。&lt;br&gt;這一篇要進一步介紹 &lt;strong&gt;物
      
    
    </summary>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/categories/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/"/>
    
      <category term="07.物件偵測與辨識篇" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/07-%E7%89%A9%E4%BB%B6%E5%81%B5%E6%B8%AC%E8%88%87%E8%BE%A8%E8%AD%98%E7%AF%87/"/>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/tags/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/tags/OpenCV/"/>
    
  </entry>
  
  <entry>
    <title>Python | OpenCV 人臉偵測與追蹤</title>
    <link href="https://morosedog.gitlab.io/python-opencv-20260227-python-opencv-face-detection/"/>
    <id>https://morosedog.gitlab.io/python-opencv-20260227-python-opencv-face-detection/</id>
    <published>2026-02-27T01:00:00.000Z</published>
    <updated>2026-03-02T01:23:05.769Z</updated>
    
    <content type="html"><![CDATA[<h2 id="📚-前言"><a href="#📚-前言" class="headerlink" title="📚 前言"></a>📚 前言</h2><p>在前一篇我們學會了 <strong>即時馬賽克與模糊處理</strong>。<br>這一篇要進一步介紹 <strong>人臉偵測與追蹤</strong>，並且提供三種常見方式：</p><ol><li>Haar Cascades</li><li>LBP Cascades</li><li>DNN (深度學習模型)</li></ol><h2 id="🎨-範例圖片-影片"><a href="#🎨-範例圖片-影片" class="headerlink" title="🎨 範例圖片/影片"></a>🎨 範例圖片/影片</h2><p>這裡我們使用 Pexels 提供的免費圖片素材：<a href="https://www.pexels.com/zh-tw/photo/3812743/" target="_blank" rel="external nofollow noopener noreferrer">Face Image</a>。<br>下載後將檔名改為 <code>face.jpg</code>，在程式碼範例中使用。這張圖片中有多種臉部表情，非常適合用來測試追蹤演算法。</p><p>這邊一樣使用 Pexels 提供的免費影片素材：<a href="https://www.pexels.com/zh-tw/video/855564/" target="_blank" rel="external nofollow noopener noreferrer">Face Video</a>。<br>下載後將檔名改為 <code>face.mp4</code>，在程式碼範例中使用。這段影片中有明顯的移動物件，非常適合用來測試追蹤演算法。</p><h2 id="🔎-三種方式比較"><a href="#🔎-三種方式比較" class="headerlink" title="🔎 三種方式比較"></a>🔎 三種方式比較</h2><table><thead><tr><th>方法</th><th>檔案來源</th><th>優點</th><th>缺點</th></tr></thead><tbody><tr><td>Haar Cascades</td><td><a href="https://github.com/opencv/opencv/tree/master/data/haarcascades" target="_blank" rel="external nofollow noopener noreferrer">OpenCV GitHub</a></td><td>經典、教學常用</td><td>準確度有限，受光線影響</td></tr><tr><td>LBP Cascades</td><td><a href="https://github.com/opencv/opencv/tree/master/data/lbpcascades" target="_blank" rel="external nofollow noopener noreferrer">OpenCV GitHub</a></td><td>偵測速度快</td><td>準確度比 Haar 稍低</td></tr><tr><td>DNN (Caffe 模型)</td><td><a href="https://github.com/opencv/opencv/tree/master/samples/dnn/face_detector" target="_blank" rel="external nofollow noopener noreferrer">OpenCV Model Zoo</a></td><td>準確度高，抗光線</td><td>模型檔案較大，需 DNN 模組</td></tr></tbody></table><h2 id="📂-檔案下載與使用說明"><a href="#📂-檔案下載與使用說明" class="headerlink" title="📂 檔案下載與使用說明"></a>📂 檔案下載與使用說明</h2><p>在進行人臉偵測與追蹤之前，需要先下載對應的模型檔案，並放到專案目錄或指定路徑。</p><h3 id="Haar-Cascades"><a href="#Haar-Cascades" class="headerlink" title="Haar Cascades"></a>Haar Cascades</h3><ul><li>檔案名稱：<code>haarcascade_frontalface_default.xml</code></li><li>下載來源：<a href="https://github.com/opencv/opencv/tree/master/data/haarcascades" target="_blank" rel="external nofollow noopener noreferrer">OpenCV GitHub — haarcascades</a></li><li>使用方式：<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">face_cascade = cv2.CascadeClassifier(<span class="string">"haarcascade_frontalface_default.xml"</span>)</span><br></pre></td></tr></table></figure></li></ul><h3 id="LBP-Cascades"><a href="#LBP-Cascades" class="headerlink" title="LBP Cascades"></a>LBP Cascades</h3><ul><li>檔案名稱：<code>lbpcascade_frontalface.xml</code></li><li>下載來源：<a href="https://github.com/opencv/opencv/tree/master/data/lbpcascades" target="_blank" rel="external nofollow noopener noreferrer">OpenCV GitHub — lbpcascades</a></li><li>使用方式：<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">face_cascade = cv2.CascadeClassifier(<span class="string">"lbpcascade_frontalface.xml"</span>)</span><br></pre></td></tr></table></figure></li></ul><h3 id="DNN-模型-Caffe"><a href="#DNN-模型-Caffe" class="headerlink" title="DNN 模型 (Caffe)"></a>DNN 模型 (Caffe)</h3><ul><li>檔案名稱：<ul><li><code>deploy.prototxt</code></li><li><code>res10_300x300_ssd_iter_140000.caffemodel</code></li></ul></li><li>下載來源：<ul><li><a href="https://github.com/opencv/opencv/tree/master/samples/dnn/face_detector" target="_blank" rel="external nofollow noopener noreferrer">OpenCV GitHub — DNN Face Detector (deploy.prototxt)</a></li><li><a href="https://github.com/shiyazt/Face-Recognition-using-Opencv-/blob/master/face_detection_model/res10_300x300_ssd_iter_140000.caffemodel" target="_blank" rel="external nofollow noopener noreferrer">GitHub — res10_300x300_ssd_iter_140000.caffemodel</a></li></ul></li><li>使用方式：<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">net = cv2.dnn.readNetFromCaffe(<span class="string">"deploy.prototxt"</span>, <span class="string">"res10_300x300_ssd_iter_140000.caffemodel"</span>)</span><br></pre></td></tr></table></figure></li></ul><blockquote><p>💡 建議將這些檔案放在專案的 <code>models/</code> 目錄下，並在程式中指定完整路徑，避免找不到檔案的錯誤。</p></blockquote><h2 id="🧠-函式與參數說明"><a href="#🧠-函式與參數說明" class="headerlink" title="🧠 函式與參數說明"></a>🧠 函式與參數說明</h2><h3 id="📌-cv2-CascadeClassifier"><a href="#📌-cv2-CascadeClassifier" class="headerlink" title="📌 cv2.CascadeClassifier()"></a>📌 <code>cv2.CascadeClassifier()</code></h3><p>載入人臉偵測模型</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cv2.CascadeClassifier(filename)</span><br></pre></td></tr></table></figure><ul><li><strong>filename</strong>：XML 模型檔案路徑，例如 <code>haarcascade_frontalface_default.xml</code> 或 <code>lbpcascade_frontalface.xml</code>。</li></ul><h3 id="📌-detectMultiScale"><a href="#📌-detectMultiScale" class="headerlink" title="📌 detectMultiScale()"></a>📌 <code>detectMultiScale()</code></h3><p>偵測人臉區域</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">faces = face_cascade.detectMultiScale(img, scaleFactor, minNeighbors, flags, minSize, maxSize)</span><br></pre></td></tr></table></figure><ul><li><strong>img</strong>：要偵測的灰階圖片或影片幀。</li><li><strong>scaleFactor</strong>：縮放比例，常用 <code>1.1</code>，表示每次縮小 10%。</li><li><strong>minNeighbors</strong>：判斷人臉的嚴格程度，數值越大越嚴格。</li><li><strong>flags</strong>：可選參數，通常設為 <code>cv2.CASCADE_SCALE_IMAGE</code>。</li><li><strong>minSize</strong>：最小人臉尺寸 <code>(w, h)</code>。</li><li><strong>maxSize</strong>：最大人臉尺寸 <code>(w, h)</code>。</li></ul><h3 id="📌-cv2-dnn-readNetFromCaffe"><a href="#📌-cv2-dnn-readNetFromCaffe" class="headerlink" title="📌 cv2.dnn.readNetFromCaffe()"></a>📌 <code>cv2.dnn.readNetFromCaffe()</code></h3><p>載入 DNN 模型</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">net = cv2.dnn.readNetFromCaffe(prototxt, model)</span><br></pre></td></tr></table></figure><ul><li><strong>prototxt</strong>：模型結構檔案，例如 <code>deploy.prototxt</code>。</li><li><strong>model</strong>：訓練好的權重檔案，例如 <code>res10_300x300_ssd_iter_140000.caffemodel</code>。</li></ul><h3 id="📌-cv2-dnn-blobFromImage"><a href="#📌-cv2-dnn-blobFromImage" class="headerlink" title="📌 cv2.dnn.blobFromImage()"></a>📌 <code>cv2.dnn.blobFromImage()</code></h3><p>將圖片轉換成 DNN 輸入格式</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">blob = cv2.dnn.blobFromImage(image, scalefactor, size, mean, swapRB, crop)</span><br></pre></td></tr></table></figure><ul><li><strong>image</strong>：輸入圖片。</li><li><strong>scalefactor</strong>：縮放比例，常用 <code>1.0</code>。</li><li><strong>size</strong>：輸入大小 <code>(width, height)</code>，例如 <code>(300, 300)</code>。</li><li><strong>mean</strong>：減去的平均值 <code>(R, G, B)</code>，例如 <code>(104.0, 177.0, 123.0)</code>。</li><li><strong>swapRB</strong>：是否交換 R 與 B 通道，常用 <code>False</code>。</li><li><strong>crop</strong>：是否裁切圖片，常用 <code>False</code>。</li></ul><h3 id="📌-cv2-TrackerKCF-create"><a href="#📌-cv2-TrackerKCF-create" class="headerlink" title="📌 cv2.TrackerKCF_create()"></a>📌 <code>cv2.TrackerKCF_create()</code></h3><p>建立 KCF 追蹤器</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tracker = cv2.TrackerKCF_create()</span><br></pre></td></tr></table></figure><ul><li>建立一個 KCF (Kernelized Correlation Filters) 追蹤器，用於追蹤人臉或物件。</li></ul><h3 id="📌-tracker-init"><a href="#📌-tracker-init" class="headerlink" title="📌 tracker.init()"></a>📌 <code>tracker.init()</code></h3><p>初始化追蹤器</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tracker.init(frame, bbox)</span><br></pre></td></tr></table></figure><ul><li><strong>frame</strong>：影片幀。</li><li><strong>bbox</strong>：初始框選區域 <code>(x, y, w, h)</code>。</li></ul><h3 id="📌-tracker-update"><a href="#📌-tracker-update" class="headerlink" title="📌 tracker.update()"></a>📌 <code>tracker.update()</code></h3><p>更新追蹤器位置</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">success, box = tracker.update(frame)</span><br></pre></td></tr></table></figure><ul><li><strong>frame</strong>：影片幀。</li><li><strong>success</strong>：布林值，表示是否成功追蹤。</li><li><strong>box</strong>：新的框選區域 <code>(x, y, w, h)</code>。</li></ul><h2 id="💻-範例程式-—-Haar-Cascades-人臉偵測-圖片"><a href="#💻-範例程式-—-Haar-Cascades-人臉偵測-圖片" class="headerlink" title="💻 範例程式 — Haar Cascades 人臉偵測 (圖片)"></a>💻 範例程式 — Haar Cascades 人臉偵測 (圖片)</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line">face_cascade = cv2.CascadeClassifier(<span class="string">"models/haarcascade_frontalface_default.xml"</span>)</span><br><span class="line"></span><br><span class="line">img = cv2.imread(<span class="string">"face.jpg"</span>)</span><br><span class="line">gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)</span><br><span class="line"></span><br><span class="line">faces = face_cascade.detectMultiScale(gray, <span class="number">1.1</span>, <span class="number">5</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (x, y, w, h) <span class="keyword">in</span> faces:</span><br><span class="line">    cv2.rectangle(img, (x, y), (x+w, y+h), (<span class="number">0</span>, <span class="number">255</span>, <span class="number">0</span>), <span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">cv2.imshow(<span class="string">"Haar Face Detection"</span>, img)</span><br><span class="line">cv2.waitKey(<span class="number">0</span>)</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/face-detection/01.gif" alt><br><em>圖：圖片人臉偵測，框選人臉區域</em></p><h2 id="💻-範例程式-—-LBP-Cascades-人臉偵測-影片"><a href="#💻-範例程式-—-LBP-Cascades-人臉偵測-影片" class="headerlink" title="💻 範例程式 — LBP Cascades 人臉偵測 (影片)"></a>💻 範例程式 — LBP Cascades 人臉偵測 (影片)</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line">face_cascade = cv2.CascadeClassifier(<span class="string">"models/lbpcascade_frontalface.xml"</span>)</span><br><span class="line">cap = cv2.VideoCapture(<span class="string">"face.mp4"</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    ret, frame = cap.read()</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> ret:</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)</span><br><span class="line">    faces = face_cascade.detectMultiScale(gray, <span class="number">1.1</span>, <span class="number">5</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (x, y, w, h) <span class="keyword">in</span> faces:</span><br><span class="line">        cv2.rectangle(frame, (x, y), (x+w, y+h), (<span class="number">255</span>, <span class="number">0</span>, <span class="number">0</span>), <span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">    cv2.imshow(<span class="string">"LBP Face Detection"</span>, frame)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> cv2.waitKey(<span class="number">30</span>) &amp; <span class="number">0xFF</span> == ord(<span class="string">'q'</span>):</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">cap.release()</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/face-detection/02.gif" alt><br><em>圖：影片人臉偵測，逐幀框選人臉區域</em></p><h2 id="💻-範例程式-—-DNN-人臉偵測-影片"><a href="#💻-範例程式-—-DNN-人臉偵測-影片" class="headerlink" title="💻 範例程式 — DNN 人臉偵測 (影片)"></a>💻 範例程式 — DNN 人臉偵測 (影片)</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line">net = cv2.dnn.readNetFromCaffe(<span class="string">"models/deploy.prototxt"</span>, <span class="string">"models/res10_300x300_ssd_iter_140000.caffemodel"</span>)</span><br><span class="line">cap = cv2.VideoCapture(<span class="string">"face.mp4"</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    ret, frame = cap.read()</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> ret:</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">    h, w = frame.shape[:<span class="number">2</span>]</span><br><span class="line">    blob = cv2.dnn.blobFromImage(cv2.resize(frame, (<span class="number">300</span>, <span class="number">300</span>)), <span class="number">1.0</span>,</span><br><span class="line">                                 (<span class="number">300</span>, <span class="number">300</span>), (<span class="number">104.0</span>, <span class="number">177.0</span>, <span class="number">123.0</span>))</span><br><span class="line">    net.setInput(blob)</span><br><span class="line">    detections = net.forward()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> range(detections.shape[<span class="number">2</span>]):</span><br><span class="line">        confidence = detections[<span class="number">0</span>, <span class="number">0</span>, i, <span class="number">2</span>]</span><br><span class="line">        <span class="keyword">if</span> confidence &gt; <span class="number">0.5</span>:</span><br><span class="line">            box = detections[<span class="number">0</span>, <span class="number">0</span>, i, <span class="number">3</span>:<span class="number">7</span>] * [w, h, w, h]</span><br><span class="line">            (x1, y1, x2, y2) = box.astype(<span class="string">"int"</span>)</span><br><span class="line">            cv2.rectangle(frame, (x1, y1), (x2, y2), (<span class="number">0</span>, <span class="number">255</span>, <span class="number">255</span>), <span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">    cv2.imshow(<span class="string">"DNN Face Detection"</span>, frame)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> cv2.waitKey(<span class="number">30</span>) &amp; <span class="number">0xFF</span> == ord(<span class="string">'q'</span>):</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">cap.release()</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/face-detection/03.gif" alt><br><em>圖：影片人臉偵測 (DNN)，準確度更高</em></p><h2 id="💻-範例程式-—-人臉偵測-追蹤"><a href="#💻-範例程式-—-人臉偵測-追蹤" class="headerlink" title="💻 範例程式 — 人臉偵測 + 追蹤"></a>💻 範例程式 — 人臉偵測 + 追蹤</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用 LBP 模型偵測人臉</span></span><br><span class="line">face_cascade = cv2.CascadeClassifier(<span class="string">"models/lbpcascade_frontalface.xml"</span>)</span><br><span class="line">cap = cv2.VideoCapture(<span class="string">"face.mp4"</span>)</span><br><span class="line"></span><br><span class="line">tracker = <span class="literal">None</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    ret, frame = cap.read()</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> ret:</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 如果追蹤器尚未建立，先偵測人臉</span></span><br><span class="line">    <span class="keyword">if</span> tracker <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)</span><br><span class="line">        faces = face_cascade.detectMultiScale(gray, <span class="number">1.1</span>, <span class="number">5</span>)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> len(faces) &gt; <span class="number">0</span>:</span><br><span class="line">            (x, y, w, h) = faces[<span class="number">0</span>]</span><br><span class="line">            tracker = cv2.TrackerKCF_create()</span><br><span class="line">            tracker.init(frame, (x, y, w, h))</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        <span class="comment"># 更新追蹤器位置</span></span><br><span class="line">        success, box = tracker.update(frame)</span><br><span class="line">        <span class="keyword">if</span> success:</span><br><span class="line">            (x, y, w, h) = [int(v) <span class="keyword">for</span> v <span class="keyword">in</span> box]</span><br><span class="line">            cv2.rectangle(frame, (x, y), (x+w, y+h), (<span class="number">0</span>, <span class="number">255</span>, <span class="number">255</span>), <span class="number">2</span>)</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="comment"># 若追蹤失敗，重置追蹤器</span></span><br><span class="line">            tracker = <span class="literal">None</span></span><br><span class="line"></span><br><span class="line">    cv2.imshow(<span class="string">"Face Tracking"</span>, frame)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> cv2.waitKey(<span class="number">30</span>) &amp; <span class="number">0xFF</span> == ord(<span class="string">'q'</span>):</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">cap.release()</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/face-detection/04.gif" alt><br><em>圖：影片人臉偵測與追蹤，持續追蹤人臉位置</em></p><h2 id="⚠️-注意事項"><a href="#⚠️-注意事項" class="headerlink" title="⚠️ 注意事項"></a>⚠️ 注意事項</h2><ul><li>Haar 與 LBP 模型需先下載 XML 檔案並放在專案目錄。</li><li>DNN 模型需下載 <code>deploy.prototxt</code> 與 <code>res10_300x300_ssd_iter_140000.caffemodel</code>。</li><li>偵測速度與準確度會因方法不同而有差異。</li><li>追蹤器需要先偵測到人臉才能開始追蹤，若人臉消失需重新偵測。</li></ul><h2 id="📊-應用場景"><a href="#📊-應用場景" class="headerlink" title="📊 應用場景"></a>📊 應用場景</h2><ul><li><strong>監控系統</strong>：即時偵測並追蹤人臉。</li><li><strong>互動應用</strong>：遊戲或互動程式中追蹤玩家人臉。</li><li><strong>隱私保護</strong>：結合馬賽克或模糊，遮蔽人臉。</li></ul><h2 id="🎯-結語"><a href="#🎯-結語" class="headerlink" title="🎯 結語"></a>🎯 結語</h2><p>本篇我們學會了如何使用 OpenCV <strong>人臉偵測與追蹤</strong>，並且比較了 Haar、LBP 與 DNN 三種方式。<br>這些技術在監控、互動應用與隱私保護中非常常見，後續可以延伸到 <strong>多物件偵測與追蹤</strong>，打造更智慧的影像系統。</p><p>📖 如在學習過程中遇到疑問，或是想了解更多相關主題，建議回顧一下 <a href="/python-opencv-20260106-python-opencv-index"><strong>Python | OpenCV 系列導讀</strong></a>，掌握完整的章節目錄，方便快速找到你需要的內容。<br><br></p><blockquote><p>註：以上參考了<br><a href="https://docs.opencv.org/4.x/d9/df8/tutorial_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV 官方文件 — Tutorials</a><br><a href="https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV 官方文件 — Python Tutorials</a><br><a href="https://github.com/opencv/opencv/tree/master/data/haarcascades" target="_blank" rel="external nofollow noopener noreferrer">OpenCV GitHub — Haar Cascades</a><br><a href="https://github.com/opencv/opencv/tree/master/data/lbpcascades" target="_blank" rel="external nofollow noopener noreferrer">OpenCV GitHub — LBP Cascades</a><br><a href="https://github.com/opencv/opencv/tree/master/samples/dnn/face_detector" target="_blank" rel="external nofollow noopener noreferrer">OpenCV GitHub — DNN Face Detector (deploy.prototxt)</a><br><a href="https://github.com/shiyazt/Face-Recognition-using-Opencv-/blob/master/face_detection_model/res10_300x300_ssd_iter_140000.caffemodel" target="_blank" rel="external nofollow noopener noreferrer">GitHub — DNN 模型 res10_300x300_ssd_iter_140000.caffemodel</a><br><a href="https://www.pexels.com/zh-tw/" target="_blank" rel="external nofollow noopener noreferrer">Pexels — 免費影片素材</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;📚-前言&quot;&gt;&lt;a href=&quot;#📚-前言&quot; class=&quot;headerlink&quot; title=&quot;📚 前言&quot;&gt;&lt;/a&gt;📚 前言&lt;/h2&gt;&lt;p&gt;在前一篇我們學會了 &lt;strong&gt;即時馬賽克與模糊處理&lt;/strong&gt;。&lt;br&gt;這一篇要進一步介紹 &lt;stron
      
    
    </summary>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/categories/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/"/>
    
      <category term="07.物件偵測與辨識篇" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/07-%E7%89%A9%E4%BB%B6%E5%81%B5%E6%B8%AC%E8%88%87%E8%BE%A8%E8%AD%98%E7%AF%87/"/>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/tags/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/tags/OpenCV/"/>
    
  </entry>
  
  <entry>
    <title>Python | OpenCV 即時馬賽克與模糊處理</title>
    <link href="https://morosedog.gitlab.io/python-opencv-20260226-python-opencv-mosaic-blur/"/>
    <id>https://morosedog.gitlab.io/python-opencv-20260226-python-opencv-mosaic-blur/</id>
    <published>2026-02-26T01:00:00.000Z</published>
    <updated>2026-03-02T01:23:05.769Z</updated>
    
    <content type="html"><![CDATA[<h2 id="📚-前言"><a href="#📚-前言" class="headerlink" title="📚 前言"></a>📚 前言</h2><p>在前一篇我們學會了 <strong>圖片與影片即時標記與繪圖</strong>。<br>這一篇要進一步介紹 <strong>即時馬賽克與模糊處理</strong>，並結合滑鼠事件互動。<br>透過這項技術，我們可以在圖片或影片中框選區域，並即時套用馬賽克或模糊效果，常用於隱私保護、教學影片或監控系統。</p><h2 id="🔎-原理說明"><a href="#🔎-原理說明" class="headerlink" title="🔎 原理說明"></a>🔎 原理說明</h2><ul><li><strong>馬賽克 (Mosaic)</strong>：將區域縮小再放大，形成像素化效果。</li><li><strong>模糊 (Blur)</strong>：使用高斯模糊或平均模糊，讓區域變得柔和不清晰。</li><li><strong>滑鼠事件</strong>：透過 <code>cv2.setMouseCallback()</code> 框選區域，並即時套用效果。</li></ul><h2 id="🧠-函式與參數說明"><a href="#🧠-函式與參數說明" class="headerlink" title="🧠 函式與參數說明"></a>🧠 函式與參數說明</h2><h3 id="📌-cv2-resize"><a href="#📌-cv2-resize" class="headerlink" title="📌 cv2.resize()"></a>📌 <code>cv2.resize()</code></h3><p>縮放圖片，用於馬賽克效果</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cv2.resize(img, dsize, interpolation)</span><br></pre></td></tr></table></figure><ul><li><strong>img</strong>：要縮放的圖片。</li><li><strong>dsize</strong>：縮放後的大小 <code>(width, height)</code>。</li><li><strong>interpolation</strong>：插值方式，例如 <code>cv2.INTER_LINEAR</code> 或 <code>cv2.INTER_NEAREST</code>。</li></ul><h3 id="📌-cv2-GaussianBlur"><a href="#📌-cv2-GaussianBlur" class="headerlink" title="📌 cv2.GaussianBlur()"></a>📌 <code>cv2.GaussianBlur()</code></h3><p>高斯模糊</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cv2.GaussianBlur(img, ksize, sigmaX)</span><br></pre></td></tr></table></figure><ul><li><strong>img</strong>：要模糊的圖片。</li><li><strong>ksize</strong>：模糊範圍大小 <code>(width, height)</code>，必須為奇數。</li><li><strong>sigmaX</strong>：高斯函數在 X 方向的標準差。</li></ul><h2 id="💻-範例程式一-—-圖片即時馬賽克與模糊"><a href="#💻-範例程式一-—-圖片即時馬賽克與模糊" class="headerlink" title="💻 範例程式一 — 圖片即時馬賽克與模糊"></a>💻 範例程式一 — 圖片即時馬賽克與模糊</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line">img = cv2.imread(<span class="string">"test.png"</span>)  <span class="comment"># 部落格頭像</span></span><br><span class="line">backup = img.copy()</span><br><span class="line"></span><br><span class="line">drawing = <span class="literal">False</span></span><br><span class="line">start_point = <span class="literal">None</span></span><br><span class="line">end_point = <span class="literal">None</span></span><br><span class="line">mode = <span class="string">"mosaic"</span>  <span class="comment"># 可切換 "mosaic" 或 "blur"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">mouse_event</span><span class="params">(event, x, y, flags, param)</span>:</span></span><br><span class="line">    <span class="keyword">global</span> drawing, start_point, end_point, img, backup</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> event == cv2.EVENT_LBUTTONDOWN:</span><br><span class="line">        drawing = <span class="literal">True</span></span><br><span class="line">        start_point = (x, y)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">elif</span> event == cv2.EVENT_MOUSEMOVE <span class="keyword">and</span> drawing:</span><br><span class="line">        img = backup.copy()</span><br><span class="line">        cv2.rectangle(img, start_point, (x, y), (<span class="number">0</span>, <span class="number">0</span>, <span class="number">255</span>), <span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">elif</span> event == cv2.EVENT_LBUTTONUP:</span><br><span class="line">        drawing = <span class="literal">False</span></span><br><span class="line">        end_point = (x, y)</span><br><span class="line"></span><br><span class="line">        x1, y1 = start_point</span><br><span class="line">        x2, y2 = end_point</span><br><span class="line">        roi = backup[min(y1,y2):max(y1,y2), min(x1,x2):max(x1,x2)]</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> roi.size &gt; <span class="number">0</span>:</span><br><span class="line">            <span class="keyword">if</span> mode == <span class="string">"mosaic"</span>:</span><br><span class="line">                mosaic = cv2.resize(roi, (<span class="number">20</span>, <span class="number">20</span>), interpolation=cv2.INTER_LINEAR)</span><br><span class="line">                mosaic = cv2.resize(mosaic, (roi.shape[<span class="number">1</span>], roi.shape[<span class="number">0</span>]), interpolation=cv2.INTER_NEAREST)</span><br><span class="line">                backup[min(y1,y2):max(y1,y2), min(x1,x2):max(x1,x2)] = mosaic</span><br><span class="line">            <span class="keyword">else</span>:</span><br><span class="line">                blur = cv2.GaussianBlur(roi, (<span class="number">25</span>, <span class="number">25</span>), <span class="number">30</span>)</span><br><span class="line">                backup[min(y1,y2):max(y1,y2), min(x1,x2):max(x1,x2)] = blur</span><br><span class="line">            img = backup.copy()</span><br><span class="line"></span><br><span class="line">cv2.namedWindow(<span class="string">"Image Effect"</span>)</span><br><span class="line">cv2.setMouseCallback(<span class="string">"Image Effect"</span>, mouse_event)</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    cv2.imshow(<span class="string">"Image Effect"</span>, img)</span><br><span class="line">    <span class="keyword">if</span> cv2.waitKey(<span class="number">1</span>) &amp; <span class="number">0xFF</span> == ord(<span class="string">'q'</span>):</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/mosaic-blur/01.gif" alt><br><em>圖：圖片即時馬賽克與模糊，滑鼠框選區域後套用效果</em></p><h2 id="💻-範例程式二-—-影片即時馬賽克與模糊"><a href="#💻-範例程式二-—-影片即時馬賽克與模糊" class="headerlink" title="💻 範例程式二 — 影片即時馬賽克與模糊"></a>💻 範例程式二 — 影片即時馬賽克與模糊</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line">cap = cv2.VideoCapture(<span class="string">"video.mp4"</span>)</span><br><span class="line"></span><br><span class="line">drawing = <span class="literal">False</span></span><br><span class="line">start_point = <span class="literal">None</span></span><br><span class="line">end_point = <span class="literal">None</span></span><br><span class="line">mode = <span class="string">"blur"</span>  <span class="comment"># 可切換 "mosaic" 或 "blur"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">mouse_event</span><span class="params">(event, x, y, flags, param)</span>:</span></span><br><span class="line">    <span class="keyword">global</span> drawing, start_point, end_point</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> event == cv2.EVENT_LBUTTONDOWN:</span><br><span class="line">        drawing = <span class="literal">True</span></span><br><span class="line">        start_point = (x, y)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">elif</span> event == cv2.EVENT_MOUSEMOVE <span class="keyword">and</span> drawing:</span><br><span class="line">        end_point = (x, y)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">elif</span> event == cv2.EVENT_LBUTTONUP:</span><br><span class="line">        drawing = <span class="literal">False</span></span><br><span class="line">        end_point = (x, y)</span><br><span class="line"></span><br><span class="line">cv2.namedWindow(<span class="string">"Video Effect"</span>)</span><br><span class="line">cv2.setMouseCallback(<span class="string">"Video Effect"</span>, mouse_event)</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    ret, frame = cap.read()</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> ret:</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> start_point <span class="keyword">and</span> end_point:</span><br><span class="line">        x1, y1 = start_point</span><br><span class="line">        x2, y2 = end_point</span><br><span class="line">        roi = frame[min(y1,y2):max(y1,y2), min(x1,x2):max(x1,x2)]</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> roi.size &gt; <span class="number">0</span>:</span><br><span class="line">            <span class="keyword">if</span> mode == <span class="string">"mosaic"</span>:</span><br><span class="line">                mosaic = cv2.resize(roi, (<span class="number">20</span>, <span class="number">20</span>), interpolation=cv2.INTER_LINEAR)</span><br><span class="line">                mosaic = cv2.resize(mosaic, (roi.shape[<span class="number">1</span>], roi.shape[<span class="number">0</span>]), interpolation=cv2.INTER_NEAREST)</span><br><span class="line">                frame[min(y1,y2):max(y1,y2), min(x1,x2):max(x1,x2)] = mosaic</span><br><span class="line">            <span class="keyword">else</span>:</span><br><span class="line">                blur = cv2.GaussianBlur(roi, (<span class="number">25</span>, <span class="number">25</span>), <span class="number">30</span>)</span><br><span class="line">                frame[min(y1,y2):max(y1,y2), min(x1,x2):max(x1,x2)] = blur</span><br><span class="line"></span><br><span class="line">        cv2.rectangle(frame, start_point, end_point, (<span class="number">0</span>, <span class="number">0</span>, <span class="number">255</span>), <span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">    cv2.imshow(<span class="string">"Video Effect"</span>, frame)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> cv2.waitKey(<span class="number">30</span>) &amp; <span class="number">0xFF</span> == ord(<span class="string">'q'</span>):</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">cap.release()</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/mosaic-blur/02.gif" alt><br><em>圖：影片即時馬賽克與模糊，滑鼠框選區域後套用效果</em></p><h2 id="⚠️-注意事項"><a href="#⚠️-注意事項" class="headerlink" title="⚠️ 注意事項"></a>⚠️ 注意事項</h2><ul><li>框選座標需在圖片或影片範圍內，否則會出錯。</li><li>馬賽克效果依縮放大小決定顆粒粗細。</li><li>模糊效果需選擇合適的 <code>ksize</code>，數值越大模糊越明顯。</li><li>在影片範例中需依影片 FPS 調整 <code>cv2.waitKey()</code> 的數值。</li></ul><h2 id="📊-應用場景"><a href="#📊-應用場景" class="headerlink" title="📊 應用場景"></a>📊 應用場景</h2><ul><li><strong>隱私保護</strong>：遮蔽人臉或敏感資訊。</li><li><strong>教學影片</strong>：模糊背景，凸顯重點。</li><li><strong>監控系統</strong>：即時隱藏不必要的資訊。</li><li><strong>互動應用</strong>：讓使用者自由選擇區域並套用效果。</li></ul><h2 id="🎯-結語"><a href="#🎯-結語" class="headerlink" title="🎯 結語"></a>🎯 結語</h2><p>本篇我們學會了如何使用 OpenCV <strong>即時馬賽克與模糊處理</strong>，透過滑鼠事件在圖片或影片中框選區域並套用效果。<br>這樣的互動方式讓影像處理更靈活，後續可以延伸到 <strong>人臉偵測與追蹤</strong>，打造更智慧的影像應用。</p><p>📖 如在學習過程中遇到疑問，或是想了解更多相關主題，建議回顧一下 <a href="/python-opencv-20260106-python-opencv-index"><strong>Python | OpenCV 系列導讀</strong></a>，掌握完整的章節目錄，方便快速找到你需要的內容。<br><br></p><blockquote><p>註：以上參考了<br><a href="https://docs.opencv.org/4.x/d9/df8/tutorial_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV Tutorials</a><br><a href="https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV-Python Tutorials</a><br><a href="https://www.pexels.com/zh-tw" target="_blank" rel="external nofollow noopener noreferrer">Pexels — 免費影片素材</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;📚-前言&quot;&gt;&lt;a href=&quot;#📚-前言&quot; class=&quot;headerlink&quot; title=&quot;📚 前言&quot;&gt;&lt;/a&gt;📚 前言&lt;/h2&gt;&lt;p&gt;在前一篇我們學會了 &lt;strong&gt;圖片與影片即時標記與繪圖&lt;/strong&gt;。&lt;br&gt;這一篇要進一步介紹 &lt;str
      
    
    </summary>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/categories/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/"/>
    
      <category term="06.視訊與互動篇" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/06-%E8%A6%96%E8%A8%8A%E8%88%87%E4%BA%92%E5%8B%95%E7%AF%87/"/>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/tags/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/tags/OpenCV/"/>
    
  </entry>
  
  <entry>
    <title>Python | OpenCV 圖片與影片即時標記與繪圖</title>
    <link href="https://morosedog.gitlab.io/python-opencv-20260225-python-opencv-image-video-draw/"/>
    <id>https://morosedog.gitlab.io/python-opencv-20260225-python-opencv-image-video-draw/</id>
    <published>2026-02-25T01:00:00.000Z</published>
    <updated>2026-03-02T01:23:05.768Z</updated>
    
    <content type="html"><![CDATA[<h2 id="📚-前言"><a href="#📚-前言" class="headerlink" title="📚 前言"></a>📚 前言</h2><p>在前一篇我們學會了 <strong>滑鼠與鍵盤事件互動</strong>。<br>這一篇要介紹 <strong>圖片與影片即時標記與繪圖</strong>，並且加入滑鼠事件互動。<br>透過這項技術，我們可以在單張圖片或播放影片時，用滑鼠圈選矩形區域，動態標記內容。這在教學、監控或互動應用中非常常見。</p><h2 id="🔎-原理說明"><a href="#🔎-原理說明" class="headerlink" title="🔎 原理說明"></a>🔎 原理說明</h2><ul><li><strong>圖片</strong>：載入單張圖片，透過滑鼠事件在圖片上繪製矩形。</li><li><strong>影片</strong>：逐幀讀取影片或攝影機影像，並在每一幀上根據滑鼠座標繪製矩形。</li><li><strong>滑鼠事件</strong>：透過 <code>cv2.setMouseCallback()</code> 監聽滑鼠操作。</li></ul><h2 id="🧠-函式與參數說明"><a href="#🧠-函式與參數說明" class="headerlink" title="🧠 函式與參數說明"></a>🧠 函式與參數說明</h2><h3 id="📌-cv2-setMouseCallback"><a href="#📌-cv2-setMouseCallback" class="headerlink" title="📌 cv2.setMouseCallback()"></a>📌 <code>cv2.setMouseCallback()</code></h3><p>設定滑鼠事件回呼函式</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cv2.setMouseCallback(winname, onMouse)</span><br></pre></td></tr></table></figure><ul><li><strong>winname</strong>：視窗名稱。</li><li><strong>onMouse</strong>：滑鼠事件處理函式。</li></ul><hr><h3 id="📌-cv2-rectangle"><a href="#📌-cv2-rectangle" class="headerlink" title="📌 cv2.rectangle()"></a>📌 <code>cv2.rectangle()</code></h3><p>繪製矩形</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cv2.rectangle(img, pt1, pt2, color, thickness)</span><br></pre></td></tr></table></figure><ul><li><strong>img</strong>：要繪製的圖片或影片幀。</li><li><strong>pt1</strong>：左上角座標 <code>(x1, y1)</code>。</li><li><strong>pt2</strong>：右下角座標 <code>(x2, y2)</code>。</li><li><strong>color</strong>：矩形顏色 <code>(B, G, R)</code>。</li><li><strong>thickness</strong>：邊框粗細，若設為 <code>-1</code> 則填滿矩形。</li></ul><h2 id="💻-範例程式一-—-圖片即時滑鼠繪圖"><a href="#💻-範例程式一-—-圖片即時滑鼠繪圖" class="headerlink" title="💻 範例程式一 — 圖片即時滑鼠繪圖"></a>💻 範例程式一 — 圖片即時滑鼠繪圖</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line">img = cv2.imread(<span class="string">"test.png"</span>)</span><br><span class="line">backup = img.copy()</span><br><span class="line"></span><br><span class="line">drawing = <span class="literal">False</span></span><br><span class="line">start_point = <span class="literal">None</span></span><br><span class="line">end_point = <span class="literal">None</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">mouse_event</span><span class="params">(event, x, y, flags, param)</span>:</span></span><br><span class="line">    <span class="keyword">global</span> drawing, start_point, end_point, img</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> event == cv2.EVENT_LBUTTONDOWN:   <span class="comment"># 左鍵按下，開始框選</span></span><br><span class="line">        drawing = <span class="literal">True</span></span><br><span class="line">        start_point = (x, y)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">elif</span> event == cv2.EVENT_MOUSEMOVE <span class="keyword">and</span> drawing:  <span class="comment"># 框選過程</span></span><br><span class="line">        img = backup.copy()</span><br><span class="line">        cv2.rectangle(img, start_point, (x, y), (<span class="number">0</span>, <span class="number">0</span>, <span class="number">255</span>), <span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">elif</span> event == cv2.EVENT_LBUTTONUP:   <span class="comment"># 左鍵放開，完成框選</span></span><br><span class="line">        drawing = <span class="literal">False</span></span><br><span class="line">        end_point = (x, y)</span><br><span class="line">        cv2.rectangle(img, start_point, end_point, (<span class="number">0</span>, <span class="number">255</span>, <span class="number">0</span>), <span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">cv2.namedWindow(<span class="string">"Image Drawing"</span>)</span><br><span class="line">cv2.setMouseCallback(<span class="string">"Image Drawing"</span>, mouse_event)</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    cv2.imshow(<span class="string">"Image Drawing"</span>, img)</span><br><span class="line">    <span class="keyword">if</span> cv2.waitKey(<span class="number">1</span>) &amp; <span class="number">0xFF</span> == ord(<span class="string">'q'</span>):  <span class="comment"># 按下 q 鍵退出</span></span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/image-video-draw/01.gif" alt><br><em>圖：圖片即時滑鼠繪圖，使用滑鼠框選矩形區域</em></p><h2 id="💻-範例程式二-—-影片即時滑鼠繪圖"><a href="#💻-範例程式二-—-影片即時滑鼠繪圖" class="headerlink" title="💻 範例程式二 — 影片即時滑鼠繪圖"></a>💻 範例程式二 — 影片即時滑鼠繪圖</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line">cap = cv2.VideoCapture(<span class="string">"video.mp4"</span>)</span><br><span class="line"></span><br><span class="line">drawing = <span class="literal">False</span></span><br><span class="line">start_point = <span class="literal">None</span></span><br><span class="line">end_point = <span class="literal">None</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">mouse_event</span><span class="params">(event, x, y, flags, param)</span>:</span></span><br><span class="line">    <span class="keyword">global</span> drawing, start_point, end_point</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> event == cv2.EVENT_LBUTTONDOWN:   <span class="comment"># 左鍵按下，開始框選</span></span><br><span class="line">        drawing = <span class="literal">True</span></span><br><span class="line">        start_point = (x, y)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">elif</span> event == cv2.EVENT_MOUSEMOVE <span class="keyword">and</span> drawing:  <span class="comment"># 框選過程</span></span><br><span class="line">        end_point = (x, y)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">elif</span> event == cv2.EVENT_LBUTTONUP:   <span class="comment"># 左鍵放開，完成框選</span></span><br><span class="line">        drawing = <span class="literal">False</span></span><br><span class="line">        end_point = (x, y)</span><br><span class="line"></span><br><span class="line">cv2.namedWindow(<span class="string">"Video Drawing"</span>)</span><br><span class="line">cv2.setMouseCallback(<span class="string">"Video Drawing"</span>, mouse_event)</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    ret, frame = cap.read()</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> ret:</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 如果正在框選或已完成框選，畫矩形</span></span><br><span class="line">    <span class="keyword">if</span> start_point <span class="keyword">and</span> end_point:</span><br><span class="line">        cv2.rectangle(frame, start_point, end_point, (<span class="number">0</span>, <span class="number">0</span>, <span class="number">255</span>), <span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">    cv2.imshow(<span class="string">"Video Drawing"</span>, frame)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> cv2.waitKey(<span class="number">30</span>) &amp; <span class="number">0xFF</span> == ord(<span class="string">'q'</span>):  <span class="comment"># 按下 q 鍵退出</span></span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">cap.release()</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/image-video-draw/02.gif" alt><br><em>圖：影片即時滑鼠繪圖，使用滑鼠框選矩形區域</em></p><h2 id="⚠️-注意事項"><a href="#⚠️-注意事項" class="headerlink" title="⚠️ 注意事項"></a>⚠️ 注意事項</h2><ul><li>滑鼠事件必須綁定在 <code>cv2.namedWindow()</code> 建立的視窗上。</li><li>框選座標需在圖片或影片範圍內，否則會出錯。</li><li>在圖片範例中使用 <code>backup.copy()</code>，避免矩形疊加造成畫面混亂。</li><li>在影片範例中需依影片 FPS 調整 <code>cv2.waitKey()</code> 的數值，常用 30 毫秒。</li></ul><h2 id="📊-應用場景"><a href="#📊-應用場景" class="headerlink" title="📊 應用場景"></a>📊 應用場景</h2><ul><li><strong>圖片標註</strong>：在圖片上框選重點區域。</li><li><strong>教學影片標註</strong>：在影片中即時框選重點。</li><li><strong>監控系統</strong>：即時標記偵測到的物件。</li><li><strong>互動應用</strong>：讓使用者在圖片或影片中自由繪製。</li></ul><h2 id="🎯-結語"><a href="#🎯-結語" class="headerlink" title="🎯 結語"></a>🎯 結語</h2><p>本篇我們學會了如何使用 OpenCV <strong>圖片與影片即時標記與繪圖</strong>，透過滑鼠事件在圖片或影片中框選矩形。<br>這樣的互動方式讓標記更靈活，後續可以延伸到 <strong>即時馬賽克或模糊處理</strong>，打造更實用的影像應用。</p><p>📖 如在學習過程中遇到疑問，或是想了解更多相關主題，建議回顧一下 <a href="/python-opencv-20260106-python-opencv-index"><strong>Python | OpenCV 系列導讀</strong></a>，掌握完整的章節目錄，方便快速找到你需要的內容。<br><br></p><blockquote><p>註：以上參考了<br><a href="https://docs.opencv.org/4.x/d9/df8/tutorial_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV Tutorials</a><br><a href="https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV-Python Tutorials</a><br><a href="https://www.pexels.com/zh-tw" target="_blank" rel="external nofollow noopener noreferrer">Pexels — 免費影片素材</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;📚-前言&quot;&gt;&lt;a href=&quot;#📚-前言&quot; class=&quot;headerlink&quot; title=&quot;📚 前言&quot;&gt;&lt;/a&gt;📚 前言&lt;/h2&gt;&lt;p&gt;在前一篇我們學會了 &lt;strong&gt;滑鼠與鍵盤事件互動&lt;/strong&gt;。&lt;br&gt;這一篇要介紹 &lt;strong&gt;圖片
      
    
    </summary>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/categories/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/"/>
    
      <category term="06.視訊與互動篇" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/06-%E8%A6%96%E8%A8%8A%E8%88%87%E4%BA%92%E5%8B%95%E7%AF%87/"/>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/tags/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/tags/OpenCV/"/>
    
  </entry>
  
  <entry>
    <title>Python | OpenCV 滑鼠與鍵盤事件互動</title>
    <link href="https://morosedog.gitlab.io/python-opencv-20260224-python-opencv-mouse-keyboard/"/>
    <id>https://morosedog.gitlab.io/python-opencv-20260224-python-opencv-mouse-keyboard/</id>
    <published>2026-02-24T01:00:00.000Z</published>
    <updated>2026-03-02T01:23:05.768Z</updated>
    
    <content type="html"><![CDATA[<h2 id="📚-前言"><a href="#📚-前言" class="headerlink" title="📚 前言"></a>📚 前言</h2><p>在前一篇我們學會了 <strong>光流分析 (Optical Flow)</strong>。<br>這一篇要介紹 <strong>滑鼠與鍵盤事件互動</strong>，這是 OpenCV 提供的基礎功能之一。<br>透過事件回呼 (callback) 與按鍵控制，我們可以讓程式更具互動性，例如：</p><ul><li>滑鼠點擊選取 ROI 區域</li><li>按鍵控制圖片顯示或儲存</li><li>結合互動操作進行物件追蹤或圖片編輯</li></ul><h2 id="🔎-原理說明"><a href="#🔎-原理說明" class="headerlink" title="🔎 原理說明"></a>🔎 原理說明</h2><ul><li><strong>滑鼠事件</strong>：透過 <code>cv2.setMouseCallback()</code> 設定事件回呼函式，偵測滑鼠在視窗中的操作。</li><li><strong>鍵盤事件</strong>：透過 <code>cv2.waitKey()</code> 取得使用者按下的鍵盤按鍵，進行互動控制。</li></ul><h2 id="🧠-函式與參數說明"><a href="#🧠-函式與參數說明" class="headerlink" title="🧠 函式與參數說明"></a>🧠 函式與參數說明</h2><h3 id="📌-cv2-setMouseCallback"><a href="#📌-cv2-setMouseCallback" class="headerlink" title="📌 cv2.setMouseCallback()"></a>📌 <code>cv2.setMouseCallback()</code></h3><p><strong>用途</strong>：設定滑鼠事件回呼函式。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cv2.setMouseCallback(<span class="string">"Window"</span>, mouse_event)</span><br></pre></td></tr></table></figure><ul><li><strong>“Window”</strong>：視窗名稱。</li><li><strong>mouse_event</strong>：自訂的事件函式。</li></ul><h3 id="📌-滑鼠事件回呼函式"><a href="#📌-滑鼠事件回呼函式" class="headerlink" title="📌 滑鼠事件回呼函式"></a>📌 滑鼠事件回呼函式</h3><figure class="highlight python"><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="function"><span class="keyword">def</span> <span class="title">mouse_event</span><span class="params">(event, x, y, flags, param)</span>:</span></span><br><span class="line">    ...</span><br></pre></td></tr></table></figure><ul><li><strong>event</strong>：事件類型，例如 <code>cv2.EVENT_LBUTTONDOWN</code> (左鍵按下)、<code>cv2.EVENT_RBUTTONDOWN</code> (右鍵按下)、<code>cv2.EVENT_MOUSEMOVE</code> (滑鼠移動)。</li><li><strong>x, y</strong>：滑鼠座標位置。</li><li><strong>flags</strong>：事件狀態，例如是否按下 Ctrl、Shift。</li><li><strong>param</strong>：額外參數，可自訂傳入。</li></ul><h4 id="🏷️-常見滑鼠事件-event"><a href="#🏷️-常見滑鼠事件-event" class="headerlink" title="🏷️ 常見滑鼠事件 (event)"></a>🏷️ 常見滑鼠事件 (event)</h4><ul><li><code>cv2.EVENT_LBUTTONDOWN</code>：左鍵按下</li><li><code>cv2.EVENT_LBUTTONUP</code>：左鍵放開</li><li><code>cv2.EVENT_RBUTTONDOWN</code>：右鍵按下</li><li><code>cv2.EVENT_RBUTTONUP</code>：右鍵放開</li><li><code>cv2.EVENT_MBUTTONDOWN</code>：中鍵按下</li><li><code>cv2.EVENT_MOUSEMOVE</code>：滑鼠移動</li><li><code>cv2.EVENT_LBUTTONDBLCLK</code>：左鍵雙擊</li></ul><h4 id="🏷️-常見滑鼠旗標-flags"><a href="#🏷️-常見滑鼠旗標-flags" class="headerlink" title="🏷️ 常見滑鼠旗標 (flags)"></a>🏷️ 常見滑鼠旗標 (flags)</h4><ul><li><code>cv2.EVENT_FLAG_CTRLKEY</code>：Ctrl 鍵按下</li><li><code>cv2.EVENT_FLAG_SHIFTKEY</code>：Shift 鍵按下</li><li><code>cv2.EVENT_FLAG_ALTKEY</code>：Alt 鍵按下</li><li><code>cv2.EVENT_FLAG_LBUTTON</code>：左鍵按住</li><li><code>cv2.EVENT_FLAG_RBUTTON</code>：右鍵按住</li><li><code>cv2.EVENT_FLAG_MBUTTON</code>：中鍵按住</li></ul><h3 id="📌-cv2-waitKey"><a href="#📌-cv2-waitKey" class="headerlink" title="📌 cv2.waitKey()"></a>📌 <code>cv2.waitKey()</code></h3><p><strong>用途</strong>：等待鍵盤事件。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">key = cv2.waitKey(<span class="number">0</span>)</span><br></pre></td></tr></table></figure><ul><li><strong>0</strong>：無限等待。</li><li><strong>正數</strong>：等待指定毫秒數。</li><li><strong>回傳值</strong>：按下的鍵盤代碼 (ASCII)。</li></ul><h4 id="🏷️-ASCII-簡介"><a href="#🏷️-ASCII-簡介" class="headerlink" title="🏷️ ASCII 簡介"></a>🏷️ ASCII 簡介</h4><ul><li><strong>ASCII (American Standard Code for Information Interchange)</strong>：一種字元編碼標準，用數字表示字母、數字與符號。</li><li>例如：<code>ord(&#39;a&#39;) = 97</code>，<code>ord(&#39;A&#39;) = 65</code>。</li><li>在 OpenCV 中，<code>cv2.waitKey()</code> 會回傳按鍵的 ASCII 值，我們可以用 <code>ord()</code> 來比對。</li></ul><p><img loading="lazy" src="/images/python/opencv/mouse-keyboard/ascii.png" alt><br><em>圖：ASCII 編碼表</em></p><blockquote><p>來源：<a href="https://shihyu.github.io/books/apas01.html" target="_blank" rel="external nofollow noopener noreferrer">ASCII 編碼表</a></p></blockquote><h2 id="💻-範例程式-—-滑鼠事件互動"><a href="#💻-範例程式-—-滑鼠事件互動" class="headerlink" title="💻 範例程式 — 滑鼠事件互動"></a>💻 範例程式 — 滑鼠事件互動</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line"><span class="comment"># 滑鼠事件回呼函式</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">mouse_event</span><span class="params">(event, x, y, flags, param)</span>:</span></span><br><span class="line">    <span class="keyword">if</span> event == cv2.EVENT_LBUTTONDOWN:   <span class="comment"># 左鍵按下</span></span><br><span class="line">        print(<span class="string">f"左鍵點擊位置: (<span class="subst">&#123;x&#125;</span>, <span class="subst">&#123;y&#125;</span>)"</span>)</span><br><span class="line">        cv2.circle(img, (x, y), <span class="number">5</span>, (<span class="number">0</span>, <span class="number">0</span>, <span class="number">255</span>), <span class="number">-1</span>)  <span class="comment"># 在點擊位置畫紅色圓點</span></span><br><span class="line">    <span class="keyword">elif</span> event == cv2.EVENT_RBUTTONDOWN: <span class="comment"># 右鍵按下</span></span><br><span class="line">        print(<span class="string">f"右鍵點擊位置: (<span class="subst">&#123;x&#125;</span>, <span class="subst">&#123;y&#125;</span>)"</span>)</span><br><span class="line">        cv2.rectangle(img, (x<span class="number">-10</span>, y<span class="number">-10</span>), (x+<span class="number">10</span>, y+<span class="number">10</span>), (<span class="number">0</span>, <span class="number">255</span>, <span class="number">0</span>), <span class="number">2</span>)  <span class="comment"># 畫綠色方框</span></span><br><span class="line">    <span class="keyword">elif</span> event == cv2.EVENT_MOUSEMOVE:   <span class="comment"># 滑鼠移動</span></span><br><span class="line">        print(<span class="string">f"滑鼠移動位置: (<span class="subst">&#123;x&#125;</span>, <span class="subst">&#123;y&#125;</span>)"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 建立視窗</span></span><br><span class="line">cv2.namedWindow(<span class="string">"Image"</span>)</span><br><span class="line">cv2.setMouseCallback(<span class="string">"Image"</span>, mouse_event)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 建立一張白色圖片</span></span><br><span class="line">img = <span class="number">255</span> * np.ones((<span class="number">400</span>, <span class="number">600</span>, <span class="number">3</span>), dtype=np.uint8)</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    cv2.imshow(<span class="string">"Image"</span>, img)</span><br><span class="line">    <span class="keyword">if</span> cv2.waitKey(<span class="number">1</span>) &amp; <span class="number">0xFF</span> == ord(<span class="string">'q'</span>):  <span class="comment"># 按下 q 鍵退出</span></span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/mouse-keyboard/01.gif" alt><br><em>圖：滑鼠事件互動，點擊位置會顯示圓點或方框</em></p><h2 id="💻-範例程式-—-滑鼠事件互動-座標、畫點、連線"><a href="#💻-範例程式-—-滑鼠事件互動-座標、畫點、連線" class="headerlink" title="💻 範例程式 — 滑鼠事件互動 (座標、畫點、連線)"></a>💻 範例程式 — 滑鼠事件互動 (座標、畫點、連線)</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line">points = []  <span class="comment"># 儲存點座標</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">mouse_event</span><span class="params">(event, x, y, flags, param)</span>:</span></span><br><span class="line">    <span class="keyword">global</span> points</span><br><span class="line">    <span class="keyword">if</span> event == cv2.EVENT_LBUTTONDOWN:   <span class="comment"># 左鍵按下</span></span><br><span class="line">        print(<span class="string">f"左鍵點擊位置: (<span class="subst">&#123;x&#125;</span>, <span class="subst">&#123;y&#125;</span>)"</span>)</span><br><span class="line">        points.append((x, y))</span><br><span class="line">        cv2.circle(img, (x, y), <span class="number">5</span>, (<span class="number">0</span>, <span class="number">0</span>, <span class="number">255</span>), <span class="number">-1</span>)  <span class="comment"># 畫紅色圓點</span></span><br><span class="line">        <span class="keyword">if</span> len(points) &gt;= <span class="number">2</span>:</span><br><span class="line">            cv2.line(img, points[<span class="number">-2</span>], points[<span class="number">-1</span>], (<span class="number">255</span>, <span class="number">0</span>, <span class="number">0</span>), <span class="number">2</span>)  <span class="comment"># 連線</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 建立視窗</span></span><br><span class="line">cv2.namedWindow(<span class="string">"Image"</span>)</span><br><span class="line">cv2.setMouseCallback(<span class="string">"Image"</span>, mouse_event)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 建立一張白色圖片</span></span><br><span class="line">img = <span class="number">255</span> * np.ones((<span class="number">400</span>, <span class="number">600</span>, <span class="number">3</span>), dtype=np.uint8)</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    cv2.imshow(<span class="string">"Image"</span>, img)</span><br><span class="line">    <span class="keyword">if</span> cv2.waitKey(<span class="number">1</span>) &amp; <span class="number">0xFF</span> == ord(<span class="string">'q'</span>):  <span class="comment"># 按下 q 鍵退出</span></span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/mouse-keyboard/02.gif" alt><br><em>圖：滑鼠事件互動，點擊位置會顯示圓點並連線</em></p><h2 id="💻-範例程式-—-滑鼠圈選框框並馬賽克化"><a href="#💻-範例程式-—-滑鼠圈選框框並馬賽克化" class="headerlink" title="💻 範例程式 — 滑鼠圈選框框並馬賽克化"></a>💻 範例程式 — 滑鼠圈選框框並馬賽克化</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line">img = cv2.imread(<span class="string">"test.png"</span>)  <span class="comment"># 部落格頭像</span></span><br><span class="line">backup = img.copy()           <span class="comment"># 保留原始圖片</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 儲存框選座標</span></span><br><span class="line">rect_start = <span class="literal">None</span></span><br><span class="line">rect_end = <span class="literal">None</span></span><br><span class="line">drawing = <span class="literal">False</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">mouse_event</span><span class="params">(event, x, y, flags, param)</span>:</span></span><br><span class="line">    <span class="keyword">global</span> rect_start, rect_end, drawing, img, backup</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> event == cv2.EVENT_LBUTTONDOWN:   <span class="comment"># 左鍵按下，開始框選</span></span><br><span class="line">        rect_start = (x, y)</span><br><span class="line">        drawing = <span class="literal">True</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">elif</span> event == cv2.EVENT_MOUSEMOVE <span class="keyword">and</span> drawing:  <span class="comment"># 框選過程</span></span><br><span class="line">        img = backup.copy()</span><br><span class="line">        cv2.rectangle(img, rect_start, (x, y), (<span class="number">0</span>, <span class="number">0</span>, <span class="number">255</span>), <span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">elif</span> event == cv2.EVENT_LBUTTONUP:   <span class="comment"># 左鍵放開，完成框選</span></span><br><span class="line">        rect_end = (x, y)</span><br><span class="line">        drawing = <span class="literal">False</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># 取得框選範圍</span></span><br><span class="line">        x1, y1 = rect_start</span><br><span class="line">        x2, y2 = rect_end</span><br><span class="line">        roi = backup[min(y1,y2):max(y1,y2), min(x1,x2):max(x1,x2)]</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 對 ROI 區域進行馬賽克化</span></span><br><span class="line">        <span class="keyword">if</span> roi.size &gt; <span class="number">0</span>:</span><br><span class="line">            mosaic = cv2.resize(roi, (<span class="number">20</span>, <span class="number">20</span>), interpolation=cv2.INTER_LINEAR)</span><br><span class="line">            mosaic = cv2.resize(mosaic, (roi.shape[<span class="number">1</span>], roi.shape[<span class="number">0</span>]), interpolation=cv2.INTER_NEAREST)</span><br><span class="line">            backup[min(y1,y2):max(y1,y2), min(x1,x2):max(x1,x2)] = mosaic</span><br><span class="line">            img = backup.copy()</span><br><span class="line">            print(<span class="string">f"框選區域已馬賽克化: (<span class="subst">&#123;x1&#125;</span>, <span class="subst">&#123;y1&#125;</span>) ~ (<span class="subst">&#123;x2&#125;</span>, <span class="subst">&#123;y2&#125;</span>)"</span>)</span><br><span class="line"></span><br><span class="line">cv2.namedWindow(<span class="string">"Mosaic Demo"</span>)</span><br><span class="line">cv2.setMouseCallback(<span class="string">"Mosaic Demo"</span>, mouse_event)</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    cv2.imshow(<span class="string">"Mosaic Demo"</span>, img)</span><br><span class="line">    <span class="keyword">if</span> cv2.waitKey(<span class="number">1</span>) &amp; <span class="number">0xFF</span> == ord(<span class="string">'q'</span>):  <span class="comment"># 按下 q 鍵退出</span></span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/mouse-keyboard/03.gif" alt><br><em>圖：滑鼠事件互動，圈選框框後該區域會被馬賽克化</em></p><h2 id="💻-範例程式-—-鍵盤事件互動-調整亮度與對比度"><a href="#💻-範例程式-—-鍵盤事件互動-調整亮度與對比度" class="headerlink" title="💻 範例程式 — 鍵盤事件互動 (調整亮度與對比度)"></a>💻 範例程式 — 鍵盤事件互動 (調整亮度與對比度)</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line"><span class="comment"># 建立一張灰色圖片</span></span><br><span class="line">img = np.ones((<span class="number">400</span>, <span class="number">600</span>, <span class="number">3</span>), dtype=np.uint8) * <span class="number">127</span></span><br><span class="line">alpha, beta = <span class="number">1.0</span>, <span class="number">0</span>  <span class="comment"># 對比度、亮度</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    adjusted = cv2.convertScaleAbs(img, alpha=alpha, beta=beta)</span><br><span class="line">    cv2.imshow(<span class="string">"Keyboard Demo"</span>, adjusted)</span><br><span class="line"></span><br><span class="line">    key = cv2.waitKey(<span class="number">0</span>) &amp; <span class="number">0xFF</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> key == ord(<span class="string">'+'</span>):   <span class="comment"># 增加亮度</span></span><br><span class="line">        beta += <span class="number">10</span></span><br><span class="line">        print(<span class="string">f"亮度增加: <span class="subst">&#123;beta&#125;</span>"</span>)</span><br><span class="line">    <span class="keyword">elif</span> key == ord(<span class="string">'-'</span>): <span class="comment"># 減少亮度</span></span><br><span class="line">        beta -= <span class="number">10</span></span><br><span class="line">        print(<span class="string">f"亮度減少: <span class="subst">&#123;beta&#125;</span>"</span>)</span><br><span class="line">    <span class="keyword">elif</span> key == ord(<span class="string">']'</span>): <span class="comment"># 增加對比度</span></span><br><span class="line">        alpha += <span class="number">0.1</span></span><br><span class="line">        print(<span class="string">f"對比度增加: <span class="subst">&#123;alpha&#125;</span>"</span>)</span><br><span class="line">    <span class="keyword">elif</span> key == ord(<span class="string">'['</span>): <span class="comment"># 減少對比度</span></span><br><span class="line">        alpha = max(<span class="number">0.1</span>, alpha - <span class="number">0.1</span>)</span><br><span class="line">        print(<span class="string">f"對比度減少: <span class="subst">&#123;alpha&#125;</span>"</span>)</span><br><span class="line">    <span class="keyword">elif</span> key == ord(<span class="string">'r'</span>): <span class="comment"># 重置</span></span><br><span class="line">        alpha, beta = <span class="number">1.0</span>, <span class="number">0</span></span><br><span class="line">        print(<span class="string">"重置亮度與對比度"</span>)</span><br><span class="line">    <span class="keyword">elif</span> key == <span class="number">27</span> <span class="keyword">or</span> key == ord(<span class="string">'q'</span>): <span class="comment"># ESC 或 q 鍵退出</span></span><br><span class="line">        print(<span class="string">"程式結束"</span>)</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/mouse-keyboard/04.gif" alt><br><em>圖：鍵盤事件互動，透過鍵盤調整亮度與對比度</em></p><h2 id="💻-範例程式-—-鍵盤事件互動"><a href="#💻-範例程式-—-鍵盤事件互動" class="headerlink" title="💻 範例程式 — 鍵盤事件互動"></a>💻 範例程式 — 鍵盤事件互動</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line"><span class="comment"># 建立一張白色圖片</span></span><br><span class="line">img = <span class="number">255</span> * np.ones((<span class="number">400</span>, <span class="number">600</span>, <span class="number">3</span>), dtype=np.uint8)</span><br><span class="line">backup = img.copy()  <span class="comment"># 保留原始圖片</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 滑鼠事件：左鍵畫紅色圓點</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">mouse_event</span><span class="params">(event, x, y, flags, param)</span>:</span></span><br><span class="line">    <span class="keyword">global</span> img</span><br><span class="line">    <span class="keyword">if</span> event == cv2.EVENT_LBUTTONDOWN:</span><br><span class="line">        cv2.circle(img, (x, y), <span class="number">5</span>, (<span class="number">0</span>, <span class="number">0</span>, <span class="number">255</span>), <span class="number">-1</span>)</span><br><span class="line">        print(<span class="string">f"畫紅點於座標: (<span class="subst">&#123;x&#125;</span>, <span class="subst">&#123;y&#125;</span>)"</span>)</span><br><span class="line"></span><br><span class="line">cv2.namedWindow(<span class="string">"Demo"</span>)</span><br><span class="line">cv2.setMouseCallback(<span class="string">"Demo"</span>, mouse_event)</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    cv2.imshow(<span class="string">"Demo"</span>, img)</span><br><span class="line">    key = cv2.waitKey(<span class="number">1</span>) &amp; <span class="number">0xFF</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> key == ord(<span class="string">'s'</span>):   <span class="comment"># 按下 s 鍵儲存圖片</span></span><br><span class="line">        cv2.imwrite(<span class="string">"output.png"</span>, img)</span><br><span class="line">        print(<span class="string">"圖片已儲存為 output.png"</span>)</span><br><span class="line">    <span class="keyword">elif</span> key == ord(<span class="string">'c'</span>): <span class="comment"># 按下 c 鍵清除圖片 (回到原始白色)</span></span><br><span class="line">        img = backup.copy()</span><br><span class="line">        print(<span class="string">"圖片已清除"</span>)</span><br><span class="line">    <span class="keyword">elif</span> key == ord(<span class="string">'q'</span>): <span class="comment"># 按下 q 鍵退出</span></span><br><span class="line">        print(<span class="string">"程式結束"</span>)</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/mouse-keyboard/05.gif" alt><br><em>圖：鍵盤事件互動，支援滑鼠畫點，並可透過 s 鍵儲存圖片、c 鍵清除畫面、q 鍵退出</em></p><h2 id="⚠️-注意事項"><a href="#⚠️-注意事項" class="headerlink" title="⚠️ 注意事項"></a>⚠️ 注意事項</h2><ul><li>滑鼠事件必須搭配 <code>cv2.namedWindow()</code> 與 <code>cv2.setMouseCallback()</code> 使用。</li><li><code>cv2.waitKey()</code> 的回傳值需搭配 <code>ord()</code> 判斷字元。</li><li>不同作業系統的鍵盤代碼可能略有差異。</li><li>建議在迴圈中使用 <code>cv2.waitKey(1)</code>，避免程式卡住</li><li>滑鼠事件與鍵盤事件可以結合，例如：滑鼠框選 ROI，再用鍵盤確認或儲存。</li></ul><h2 id="📊-應用場景"><a href="#📊-應用場景" class="headerlink" title="📊 應用場景"></a>📊 應用場景</h2><ul><li><strong>ROI 選取</strong>：透過滑鼠框選物件區域。</li><li><strong>互動式標註</strong>：人工標記圖片中的特徵點。</li><li><strong>遊戲互動</strong>：結合鍵盤控制與圖片顯示。</li><li><strong>圖片編輯</strong>：透過滑鼠與鍵盤操作進行裁切、儲存。</li></ul><h2 id="🎯-結語"><a href="#🎯-結語" class="headerlink" title="🎯 結語"></a>🎯 結語</h2><p>本篇我們學會了如何使用 OpenCV <strong>滑鼠與鍵盤事件互動</strong>，透過事件回呼與按鍵控制，讓程式更具互動性。<br>這些技術在 ROI 選取、圖片標註、互動式應用中非常常見，後續可以結合物件追蹤或光流分析，打造更完整的互動系統。</p><p>📖 如在學習過程中遇到疑問，或是想了解更多相關主題，建議回顧一下 <a href="/python-opencv-20260106-python-opencv-index"><strong>Python | OpenCV 系列導讀</strong></a>，掌握完整的章節目錄，方便快速找到你需要的內容。<br><br></p><blockquote><p>註：以上參考了<br><a href="https://docs.opencv.org/4.x/d9/df8/tutorial_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV Tutorials</a><br><a href="https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV-Python Tutorials</a><br><a href="https://www.pexels.com/zh-tw" target="_blank" rel="external nofollow noopener noreferrer">Pexels — 免費影片素材</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;📚-前言&quot;&gt;&lt;a href=&quot;#📚-前言&quot; class=&quot;headerlink&quot; title=&quot;📚 前言&quot;&gt;&lt;/a&gt;📚 前言&lt;/h2&gt;&lt;p&gt;在前一篇我們學會了 &lt;strong&gt;光流分析 (Optical Flow)&lt;/strong&gt;。&lt;br&gt;這一篇要介紹 
      
    
    </summary>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/categories/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/"/>
    
      <category term="06.視訊與互動篇" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/06-%E8%A6%96%E8%A8%8A%E8%88%87%E4%BA%92%E5%8B%95%E7%AF%87/"/>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/tags/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/tags/OpenCV/"/>
    
  </entry>
  
  <entry>
    <title>Python | OpenCV 光流分析 (Optical Flow)</title>
    <link href="https://morosedog.gitlab.io/python-opencv-20260223-python-opencv-opticalflow/"/>
    <id>https://morosedog.gitlab.io/python-opencv-20260223-python-opencv-opticalflow/</id>
    <published>2026-02-23T01:00:00.000Z</published>
    <updated>2026-03-02T01:23:05.768Z</updated>
    
    <content type="html"><![CDATA[<h2 id="📚-前言"><a href="#📚-前言" class="headerlink" title="📚 前言"></a>📚 前言</h2><p>在前一篇我們學會了 <strong>即時圖片中的物件追蹤</strong>。<br>這一篇要介紹 <strong>光流分析 (Optical Flow)</strong>，它是一種用來估計圖片中物件移動的方法。<br>光流分析常用於運動偵測、物件追蹤、影片穩定化等場景。</p><h2 id="🎨-範例影片"><a href="#🎨-範例影片" class="headerlink" title="🎨 範例影片"></a>🎨 範例影片</h2><p>這裡我們使用 Pexels 提供的免費影片素材：<a href="https://www.pexels.com/zh-tw/video/8224211/" target="_blank" rel="external nofollow noopener noreferrer">Tennis Video</a>。<br>下載後將檔名改為 <code>tennis.mp4</code>，在程式碼範例中使用。這段影片中有明顯的移動物件，非常適合用來測試追蹤演算法。</p><h2 id="🔎-原理說明"><a href="#🔎-原理說明" class="headerlink" title="🔎 原理說明"></a>🔎 原理說明</h2><ul><li><strong>光流 (Optical Flow)</strong>：描述圖片中像素隨時間的移動。</li><li><strong>兩種常用方法</strong>：<ol><li><strong>Lucas-Kanade Optical Flow</strong>：追蹤特徵點的移動，適合少量特徵點。</li><li><strong>Dense Optical Flow (Farneback)</strong>：計算整張圖片的光流，適合分析整體運動。</li></ol></li></ul><h2 id="🧠-函式與參數說明"><a href="#🧠-函式與參數說明" class="headerlink" title="🧠 函式與參數說明"></a>🧠 函式與參數說明</h2><h3 id="📌-cv2-goodFeaturesToTrack"><a href="#📌-cv2-goodFeaturesToTrack" class="headerlink" title="📌 cv2.goodFeaturesToTrack()"></a>📌 <code>cv2.goodFeaturesToTrack()</code></h3><p><strong>用途</strong>：偵測圖片中的角點 (特徵點)。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">p0 = cv2.goodFeaturesToTrack(old_gray, maxCorners=<span class="number">100</span>, qualityLevel=<span class="number">0.3</span>, minDistance=<span class="number">7</span>)</span><br></pre></td></tr></table></figure><ul><li><strong>maxCorners</strong>：最多偵測多少角點。</li><li><strong>qualityLevel</strong>：角點品質閾值。</li><li><strong>minDistance</strong>：角點之間的最小距離。</li></ul><h3 id="📌-cv2-calcOpticalFlowPyrLK"><a href="#📌-cv2-calcOpticalFlowPyrLK" class="headerlink" title="📌 cv2.calcOpticalFlowPyrLK()"></a>📌 <code>cv2.calcOpticalFlowPyrLK()</code></h3><p><strong>用途</strong>：計算 Lucas-Kanade 光流，追蹤特徵點移動。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">p1, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, <span class="literal">None</span>)</span><br></pre></td></tr></table></figure><ul><li><strong>old_gray</strong>：前一幀圖片 (灰階)。</li><li><strong>frame_gray</strong>：當前圖片 (灰階)。</li><li><strong>p0</strong>：前一幀的特徵點。</li><li><strong>p1</strong>：計算後的新位置。</li><li><strong>st</strong>：追蹤狀態 (1=成功, 0=失敗)。</li><li><strong>err</strong>：誤差值。</li></ul><h3 id="📌-cv2-calcOpticalFlowFarneback"><a href="#📌-cv2-calcOpticalFlowFarneback" class="headerlink" title="📌 cv2.calcOpticalFlowFarneback()"></a>📌 <code>cv2.calcOpticalFlowFarneback()</code></h3><p><strong>用途</strong>：計算 Dense Optical Flow，分析整張圖片的運動。</p><figure class="highlight python"><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">flow = cv2.calcOpticalFlowFarneback(prvs, next, <span class="literal">None</span>,</span><br><span class="line">                                    <span class="number">0.5</span>, <span class="number">3</span>, <span class="number">15</span>, <span class="number">3</span>, <span class="number">5</span>, <span class="number">1.2</span>, <span class="number">0</span>)</span><br></pre></td></tr></table></figure><ul><li><strong>prvs</strong>：前一幀圖片 (灰階)。</li><li><strong>next</strong>：當前圖片 (灰階)。</li><li><strong>其他參數</strong>：控制金字塔層數、窗口大小、迭代次數等。</li></ul><h3 id="📌-cv2-cartToPolar"><a href="#📌-cv2-cartToPolar" class="headerlink" title="📌 cv2.cartToPolar()"></a>📌 <code>cv2.cartToPolar()</code></h3><p><strong>用途</strong>：將光流的水平與垂直分量轉換為角度與大小。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mag, ang = cv2.cartToPolar(flow[..., <span class="number">0</span>], flow[..., <span class="number">1</span>])</span><br></pre></td></tr></table></figure><ul><li><strong>mag</strong>：光流大小 (速度)。</li><li><strong>ang</strong>：光流方向 (角度)。</li></ul><h2 id="💻-範例程式-—-Lucas-Kanade-Optical-Flow"><a href="#💻-範例程式-—-Lucas-Kanade-Optical-Flow" class="headerlink" title="💻 範例程式 — Lucas-Kanade Optical Flow"></a>💻 範例程式 — Lucas-Kanade Optical Flow</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line"><span class="comment"># 讀取影片</span></span><br><span class="line">cap = cv2.VideoCapture(<span class="string">"tennis.mp4"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 讀取第一幀並轉灰階</span></span><br><span class="line">ret, old_frame = cap.read()</span><br><span class="line">old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 偵測角點 (特徵點)</span></span><br><span class="line">p0 = cv2.goodFeaturesToTrack(old_gray, maxCorners=<span class="number">100</span>, qualityLevel=<span class="number">0.3</span>, minDistance=<span class="number">7</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 建立顏色用於繪製軌跡</span></span><br><span class="line">mask = np.zeros_like(old_frame)</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    ret, frame = cap.read()</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> ret:</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">    frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 計算光流</span></span><br><span class="line">    p1, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, <span class="literal">None</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 選取成功追蹤的點</span></span><br><span class="line">    good_new = p1[st == <span class="number">1</span>]</span><br><span class="line">    good_old = p0[st == <span class="number">1</span>]</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 繪製軌跡</span></span><br><span class="line">    <span class="keyword">for</span> i, (new, old) <span class="keyword">in</span> enumerate(zip(good_new, good_old)):</span><br><span class="line">        x_new, y_new = new.ravel()</span><br><span class="line">        x_old, y_old = old.ravel()</span><br><span class="line">        mask = cv2.line(mask, (int(x_new), int(y_new)), (int(x_old), int(y_old)), (<span class="number">0</span>, <span class="number">255</span>, <span class="number">0</span>), <span class="number">2</span>)</span><br><span class="line">        frame = cv2.circle(frame, (int(x_new), int(y_new)), <span class="number">5</span>, (<span class="number">0</span>, <span class="number">0</span>, <span class="number">255</span>), <span class="number">-1</span>)</span><br><span class="line"></span><br><span class="line">    img = cv2.add(frame, mask)</span><br><span class="line">    cv2.imshow(<span class="string">"Lucas-Kanade Optical Flow"</span>, img)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> cv2.waitKey(<span class="number">30</span>) &amp; <span class="number">0xFF</span> == ord(<span class="string">'q'</span>):</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">    old_gray = frame_gray.copy()</span><br><span class="line">    p0 = good_new.reshape(<span class="number">-1</span>, <span class="number">1</span>, <span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">cap.release()</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/opticalflow/01.gif" alt><br><em>圖：Lucas-Kanade 光流分析，顯示特徵點的移動軌跡</em></p><h2 id="💻-範例程式-—-Dense-Optical-Flow-Farneback"><a href="#💻-範例程式-—-Dense-Optical-Flow-Farneback" class="headerlink" title="💻 範例程式 — Dense Optical Flow (Farneback)"></a>💻 範例程式 — Dense Optical Flow (Farneback)</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line">cap = cv2.VideoCapture(<span class="string">"tennis.mp4"</span>)</span><br><span class="line"></span><br><span class="line">ret, frame1 = cap.read()</span><br><span class="line">prvs = cv2.cvtColor(frame1, cv2.COLOR_BGR2GRAY)</span><br><span class="line"></span><br><span class="line">hsv = np.zeros_like(frame1)</span><br><span class="line">hsv[..., <span class="number">1</span>] = <span class="number">255</span>  <span class="comment"># 設定飽和度</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    ret, frame2 = cap.read()</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> ret:</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">    next = cv2.cvtColor(frame2, cv2.COLOR_BGR2GRAY)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 計算光流</span></span><br><span class="line">    flow = cv2.calcOpticalFlowFarneback(prvs, next, <span class="literal">None</span>,</span><br><span class="line">                                        <span class="number">0.5</span>, <span class="number">3</span>, <span class="number">15</span>, <span class="number">3</span>, <span class="number">5</span>, <span class="number">1.2</span>, <span class="number">0</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 將光流轉換為顏色顯示</span></span><br><span class="line">    mag, ang = cv2.cartToPolar(flow[..., <span class="number">0</span>], flow[..., <span class="number">1</span>])</span><br><span class="line">    hsv[..., <span class="number">0</span>] = ang * <span class="number">180</span> / np.pi / <span class="number">2</span></span><br><span class="line">    hsv[..., <span class="number">2</span>] = cv2.normalize(mag, <span class="literal">None</span>, <span class="number">0</span>, <span class="number">255</span>, cv2.NORM_MINMAX)</span><br><span class="line">    bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)</span><br><span class="line"></span><br><span class="line">    cv2.imshow(<span class="string">"Dense Optical Flow"</span>, bgr)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> cv2.waitKey(<span class="number">30</span>) &amp; <span class="number">0xFF</span> == ord(<span class="string">'q'</span>):</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">    prvs = next</span><br><span class="line"></span><br><span class="line">cap.release()</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/opticalflow/02.gif" alt><br><em>圖：Dense Optical Flow，顯示整張圖片的運動方向與速度</em></p><h2 id="⚠️-注意事項"><a href="#⚠️-注意事項" class="headerlink" title="⚠️ 注意事項"></a>⚠️ 注意事項</h2><ul><li>光流分析需要 <strong>連續影格</strong>，若影片斷裂或幀率過低，效果會不佳。</li><li>Lucas-Kanade 適合少量特徵點，Dense Optical Flow 適合整體運動分析。</li><li>光流結果容易受光線變化、遮擋影響。</li><li>建議在 <strong>灰階圖片</strong>上計算光流，以減少計算量。</li></ul><h2 id="📊-應用場景"><a href="#📊-應用場景" class="headerlink" title="📊 應用場景"></a>📊 應用場景</h2><ul><li><strong>運動分析</strong>：分析球員或球的移動軌跡。</li><li><strong>監控系統</strong>：偵測移動物體。</li><li><strong>影片穩定化</strong>：估計相機晃動並修正。</li><li><strong>自動駕駛</strong>：分析道路上物件的移動。</li></ul><h2 id="🎯-結語"><a href="#🎯-結語" class="headerlink" title="🎯 結語"></a>🎯 結語</h2><p>本篇我們學會了如何使用 OpenCV <strong>光流分析 (Optical Flow)</strong>，並透過 Lucas-Kanade 與 Dense Optical Flow 方法來追蹤圖片中物件的移動。<br>這些技術在運動分析、監控與自動駕駛中非常常見，後續可以結合物件偵測與追蹤，打造更完整的系統。</p><p>📖 如在學習過程中遇到疑問，或是想了解更多相關主題，建議回顧一下 <a href="/python-opencv-20260106-python-opencv-index"><strong>Python | OpenCV 系列導讀</strong></a>，掌握完整的章節目錄，方便快速找到你需要的內容。<br><br></p><blockquote><p>註：以上參考了<br><a href="https://docs.opencv.org/4.x/d9/df8/tutorial_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV Tutorials</a><br><a href="https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV-Python Tutorials</a><br><a href="https://www.pexels.com/zh-tw" target="_blank" rel="external nofollow noopener noreferrer">Pexels — 免費影片素材</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;📚-前言&quot;&gt;&lt;a href=&quot;#📚-前言&quot; class=&quot;headerlink&quot; title=&quot;📚 前言&quot;&gt;&lt;/a&gt;📚 前言&lt;/h2&gt;&lt;p&gt;在前一篇我們學會了 &lt;strong&gt;即時圖片中的物件追蹤&lt;/strong&gt;。&lt;br&gt;這一篇要介紹 &lt;strong&gt;光
      
    
    </summary>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/categories/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/"/>
    
      <category term="06.視訊與互動篇" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/06-%E8%A6%96%E8%A8%8A%E8%88%87%E4%BA%92%E5%8B%95%E7%AF%87/"/>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/tags/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/tags/OpenCV/"/>
    
  </entry>
  
  <entry>
    <title>Python | OpenCV 即時圖片中的物件追蹤</title>
    <link href="https://morosedog.gitlab.io/python-opencv-20260213-python-opencv-tracking/"/>
    <id>https://morosedog.gitlab.io/python-opencv-20260213-python-opencv-tracking/</id>
    <published>2026-02-13T01:00:00.000Z</published>
    <updated>2026-03-02T01:23:05.768Z</updated>
    
    <content type="html"><![CDATA[<h2 id="📚-前言"><a href="#📚-前言" class="headerlink" title="📚 前言"></a>📚 前言</h2><p>在前一篇我們學會了 <strong>視訊檔案處理與剪輯</strong>。<br>這一篇要介紹如何使用 OpenCV <strong>即時圖片中的物件追蹤</strong>。<br>物件追蹤是電腦視覺的重要應用之一，常用於監控、運動分析、互動系統等場景。<br>除了攝影機，我們也可以用影片檔來模擬，方便測試追蹤效果。</p><h2 id="🎨-範例影片"><a href="#🎨-範例影片" class="headerlink" title="🎨 範例影片"></a>🎨 範例影片</h2><p>這裡我們使用 Pexels 提供的免費影片素材：<a href="https://www.pexels.com/zh-tw/video/8224211/" target="_blank" rel="external nofollow noopener noreferrer">Tennis Video</a>。<br>下載後將檔名改為 <code>tennis.mp4</code>，在程式碼範例中使用。這段影片中有明顯的移動物件，非常適合用來測試追蹤演算法。</p><h2 id="🔎-原理說明"><a href="#🔎-原理說明" class="headerlink" title="🔎 原理說明"></a>🔎 原理說明</h2><ul><li><strong>物件追蹤 (Object Tracking)</strong>：在影片或即時圖片中，持續追蹤某個選定的目標。</li><li><strong>流程</strong>：<ol><li>讀取來源 (攝影機 / 影片檔 / 網路串流)。</li><li>選定要追蹤的物件區域 (ROI)。</li><li>建立追蹤器 (Tracker)。</li><li>在迴圈中更新追蹤結果，並顯示。</li></ol></li></ul><h2 id="🧠-八種追蹤演算法比較"><a href="#🧠-八種追蹤演算法比較" class="headerlink" title="🧠 八種追蹤演算法比較"></a>🧠 八種追蹤演算法比較</h2><p>OpenCV 提供了八種追蹤器，各有速度與精準度上的差異：</p><table><thead><tr><th>演算法</th><th>速度</th><th>精準度</th><th>說明</th></tr></thead><tbody><tr><td>BOOSTING</td><td>慢</td><td>差</td><td>最早期的追蹤器，速度慢且不精準</td></tr><tr><td>MIL</td><td>慢</td><td>差</td><td>比 BOOSTING 稍好，但仍不精準</td></tr><tr><td>GOTURN</td><td>中</td><td>中</td><td>需搭配深度學習模型才能運作</td></tr><tr><td>TLD</td><td>中</td><td>中</td><td>速度與精準度一般</td></tr><tr><td>MEDIANFLOW</td><td>中</td><td>中</td><td>對快速移動或跳動物件不準確</td></tr><tr><td>KCF</td><td>快</td><td>高</td><td>常用追蹤器，速度快但遮擋時不準</td></tr><tr><td>MOSSE</td><td>最快</td><td>高</td><td>速度最快，精準度略低於 KCF/CSRT</td></tr><tr><td>CSRT</td><td>快</td><td>最高</td><td>精準度最佳，但速度比 KCF 慢</td></tr></tbody></table><p>建立追蹤器的語法 (新版 OpenCV 需使用 <code>cv2.legacy</code>)：</p><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></pre></td><td class="code"><pre><span class="line">tracker = cv2.legacy.TrackerKCF_create()      <span class="comment"># KCF</span></span><br><span class="line">tracker = cv2.legacy.TrackerCSRT_create()     <span class="comment"># CSRT</span></span><br><span class="line">tracker = cv2.legacy.TrackerMOSSE_create()    <span class="comment"># MOSSE</span></span><br></pre></td></tr></table></figure><h2 id="⚠️-常見問題與解決方式"><a href="#⚠️-常見問題與解決方式" class="headerlink" title="⚠️ 常見問題與解決方式"></a>⚠️ 常見問題與解決方式</h2><p>在新版 OpenCV (4.x) 中，如果直接使用 <code>cv2.TrackerKCF_create()</code> 會出現以下錯誤：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">AttributeError: module &#39;cv2&#39; has no attribute &#39;TrackerKCF_create&#39;</span><br></pre></td></tr></table></figure><h3 id="解決方式："><a href="#解決方式：" class="headerlink" title="解決方式："></a>解決方式：</h3><ol><li>安裝 <strong>opencv-contrib-python</strong> 套件，因為追蹤器屬於 contrib 模組：<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install opencv-contrib-python</span><br></pre></td></tr></table></figure></li><li>使用新版 API：<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tracker = cv2.legacy.TrackerKCF_create()</span><br></pre></td></tr></table></figure></li></ol><p>這樣就能正常執行物件追蹤範例。</p><h2 id="💻-範例程式-—-即時攝影機物件追蹤"><a href="#💻-範例程式-—-即時攝影機物件追蹤" class="headerlink" title="💻 範例程式 — 即時攝影機物件追蹤"></a>💻 範例程式 — 即時攝影機物件追蹤</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line"><span class="comment"># 建立攝影機物件 (0 = 預設攝影機)</span></span><br><span class="line">cap = cv2.VideoCapture(<span class="number">0</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 讀取第一幀，作為選擇追蹤區域的基準</span></span><br><span class="line">ret, frame = cap.read()</span><br><span class="line"><span class="keyword">if</span> <span class="keyword">not</span> ret:</span><br><span class="line">    print(<span class="string">"無法讀取攝影機"</span>)</span><br><span class="line">    cap.release()</span><br><span class="line">    exit()</span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用滑鼠選擇 ROI (欲追蹤的物件區域)</span></span><br><span class="line">roi = cv2.selectROI(<span class="string">"Select Object"</span>, frame, <span class="literal">False</span>)</span><br><span class="line">cv2.destroyWindow(<span class="string">"Select Object"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 建立追蹤器 (這裡使用 CSRT，可改成 KCF 或 MOSSE)</span></span><br><span class="line">tracker = cv2.legacy.TrackerCSRT_create()</span><br><span class="line">tracker.init(frame, roi)</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    <span class="comment"># 逐幀讀取攝影機圖片</span></span><br><span class="line">    ret, frame = cap.read()</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> ret:</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 更新追蹤器狀態</span></span><br><span class="line">    success, box = tracker.update(frame)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> success:</span><br><span class="line">        <span class="comment"># 追蹤成功，繪製矩形框出物件</span></span><br><span class="line">        x, y, w, h = [int(v) <span class="keyword">for</span> v <span class="keyword">in</span> box]</span><br><span class="line">        cv2.rectangle(frame, (x, y), (x+w, y+h), (<span class="number">0</span>, <span class="number">255</span>, <span class="number">0</span>), <span class="number">2</span>)</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        <span class="comment"># 追蹤失敗，顯示提示文字</span></span><br><span class="line">        cv2.putText(frame, <span class="string">"Lost"</span>, (<span class="number">50</span>, <span class="number">50</span>),</span><br><span class="line">                    cv2.FONT_HERSHEY_SIMPLEX, <span class="number">0.8</span>, (<span class="number">0</span>,<span class="number">0</span>,<span class="number">255</span>), <span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 顯示結果圖片</span></span><br><span class="line">    cv2.imshow(<span class="string">"Tracking"</span>, frame)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 按下 q 鍵退出</span></span><br><span class="line">    <span class="keyword">if</span> cv2.waitKey(<span class="number">30</span>) &amp; <span class="number">0xFF</span> == ord(<span class="string">'q'</span>):</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">cap.release()</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/tracking/01.gif" alt><br><em>圖：即時追蹤選定的物件</em></p><blockquote><p>註：由於本身沒有攝影鏡頭，所以無法示範效果。</p></blockquote><h2 id="💻-範例程式-—-使用影片檔進行物件追蹤"><a href="#💻-範例程式-—-使用影片檔進行物件追蹤" class="headerlink" title="💻 範例程式 — 使用影片檔進行物件追蹤"></a>💻 範例程式 — 使用影片檔進行物件追蹤</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line"><span class="comment"># 讀取範例影片 (檔名改為 tennis.mp4)</span></span><br><span class="line">cap = cv2.VideoCapture(<span class="string">"tennis.mp4"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 讀取第一幀，作為選擇追蹤區域的基準</span></span><br><span class="line">ret, frame = cap.read()</span><br><span class="line"><span class="keyword">if</span> <span class="keyword">not</span> ret:</span><br><span class="line">    print(<span class="string">"無法讀取影片"</span>)</span><br><span class="line">    cap.release()</span><br><span class="line">    exit()</span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用滑鼠選擇 ROI (欲追蹤的物件區域)</span></span><br><span class="line">roi = cv2.selectROI(<span class="string">"Select Object"</span>, frame, <span class="literal">False</span>)</span><br><span class="line">cv2.destroyWindow(<span class="string">"Select Object"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 建立追蹤器 (這裡使用 KCF，可改成 CSRT 或 MOSSE)</span></span><br><span class="line">tracker = cv2.legacy.TrackerKCF_create()</span><br><span class="line">tracker.init(frame, roi)</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    <span class="comment"># 逐幀讀取影片</span></span><br><span class="line">    ret, frame = cap.read()</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> ret:</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 更新追蹤器狀態</span></span><br><span class="line">    success, box = tracker.update(frame)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> success:</span><br><span class="line">        <span class="comment"># 追蹤成功，繪製矩形框出物件</span></span><br><span class="line">        x, y, w, h = [int(v) <span class="keyword">for</span> v <span class="keyword">in</span> box]</span><br><span class="line">        cv2.rectangle(frame, (x, y), (x+w, y+h), (<span class="number">0</span>, <span class="number">255</span>, <span class="number">0</span>), <span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 顯示結果圖片</span></span><br><span class="line">    cv2.imshow(<span class="string">"Tracking"</span>, frame)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 按下 q 鍵退出</span></span><br><span class="line">    <span class="keyword">if</span> cv2.waitKey(<span class="number">30</span>) &amp; <span class="number">0xFF</span> == ord(<span class="string">'q'</span>):</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">cap.release()</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/tracking/02.gif" alt><br><em>圖：在影片檔中追蹤選定的物件</em></p><h2 id="⚠️-注意事項"><a href="#⚠️-注意事項" class="headerlink" title="⚠️ 注意事項"></a>⚠️ 注意事項</h2><ul><li>追蹤器需要先選定 ROI，建議在第一幀選擇。</li><li>不同演算法有不同特性，需依場景選擇。</li><li>若物件消失或被遮擋，追蹤器可能失敗。</li><li>測試時可使用交通影片或運動影片，物件移動明顯，追蹤效果更佳。</li></ul><h2 id="📊-應用場景"><a href="#📊-應用場景" class="headerlink" title="📊 應用場景"></a>📊 應用場景</h2><ul><li><strong>監控系統</strong>：追蹤特定人物或車輛。</li><li><strong>運動分析</strong>：追蹤球員或球的移動。</li><li><strong>互動應用</strong>：即時追蹤手勢或物件。</li><li><strong>自動化系統</strong>：追蹤工業流程中的物件。</li></ul><h2 id="🎯-結語"><a href="#🎯-結語" class="headerlink" title="🎯 結語"></a>🎯 結語</h2><p>本篇我們學會了如何使用 OpenCV <strong>即時圖片中的物件追蹤</strong>，並透過不同演算法持續追蹤選定的目標。<br>同時也解決了新版 OpenCV 中常見的錯誤，確保程式能在最新環境下正常執行。<br>除了攝影機，也能用影片檔來測試，方便在沒有攝影機的環境下練習。<br>這些技術在監控、運動分析與互動應用中非常常見，後續可以結合人臉偵測或特徵匹配，打造更完整的系統。</p><p>📖 如在學習過程中遇到疑問，或是想了解更多相關主題，建議回顧一下 <a href="/python-opencv-20260106-python-opencv-index"><strong>Python | OpenCV 系列導讀</strong></a>，掌握完整的章節目錄，方便快速找到你需要的內容。<br><br></p><blockquote><p>註：以上參考了<br><a href="https://docs.opencv.org/4.x/d9/df8/tutorial_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV Tutorials</a><br><a href="https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV-Python Tutorials</a><br><a href="https://www.pexels.com/zh-tw" target="_blank" rel="external nofollow noopener noreferrer">Pexels — 免費影片素材</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;📚-前言&quot;&gt;&lt;a href=&quot;#📚-前言&quot; class=&quot;headerlink&quot; title=&quot;📚 前言&quot;&gt;&lt;/a&gt;📚 前言&lt;/h2&gt;&lt;p&gt;在前一篇我們學會了 &lt;strong&gt;視訊檔案處理與剪輯&lt;/strong&gt;。&lt;br&gt;這一篇要介紹如何使用 OpenCV
      
    
    </summary>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/categories/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/"/>
    
      <category term="06.視訊與互動篇" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/06-%E8%A6%96%E8%A8%8A%E8%88%87%E4%BA%92%E5%8B%95%E7%AF%87/"/>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/tags/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/tags/OpenCV/"/>
    
  </entry>
  
  <entry>
    <title>Python | OpenCV 視訊檔案處理與剪輯</title>
    <link href="https://morosedog.gitlab.io/python-opencv-20260212-python-opencv-video-operate/"/>
    <id>https://morosedog.gitlab.io/python-opencv-20260212-python-opencv-video-operate/</id>
    <published>2026-02-12T01:00:00.000Z</published>
    <updated>2026-03-02T01:23:05.768Z</updated>
    
    <content type="html"><![CDATA[<h2 id="📚-前言"><a href="#📚-前言" class="headerlink" title="📚 前言"></a>📚 前言</h2><p>在前一篇我們學會了 <strong>攝影機即時圖片讀取</strong>。<br>這一篇要介紹如何使用 OpenCV <strong>處理與剪輯視訊檔案</strong>。<br>這是電腦視覺與多媒體應用的重要基礎，常用於影片分析、監控錄影、資料集製作等場景。</p><h2 id="🔎-原理說明"><a href="#🔎-原理說明" class="headerlink" title="🔎 原理說明"></a>🔎 原理說明</h2><ul><li><strong>視訊檔案讀取</strong>：透過 <code>cv2.VideoCapture()</code> 讀取影片檔。</li><li><strong>視訊檔案儲存</strong>：透過 <code>cv2.VideoWriter()</code> 將影格寫入新影片。</li><li><strong>剪輯與處理</strong>：可在迴圈中對每個影格進行操作，例如裁切、灰階化、加文字。</li></ul><h2 id="🧠-函式與參數說明"><a href="#🧠-函式與參數說明" class="headerlink" title="🧠 函式與參數說明"></a>🧠 函式與參數說明</h2><h3 id="📌-cv2-VideoCapture"><a href="#📌-cv2-VideoCapture" class="headerlink" title="📌 cv2.VideoCapture()"></a>📌 <code>cv2.VideoCapture()</code></h3><p><strong>用途</strong>：讀取影片檔或串流。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cap = cv2.VideoCapture(<span class="string">"video.mp4"</span>)</span><br></pre></td></tr></table></figure><ul><li><strong>“video.mp4”</strong>：影片檔路徑。</li><li><strong>cap</strong>：影片物件。</li></ul><hr><h3 id="📌-cap-get"><a href="#📌-cap-get" class="headerlink" title="📌 cap.get()"></a>📌 <code>cap.get()</code></h3><p><strong>用途</strong>：取得影片屬性。</p><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></pre></td><td class="code"><pre><span class="line">fps = cap.get(cv2.CAP_PROP_FPS)</span><br><span class="line">width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))</span><br><span class="line">height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))</span><br></pre></td></tr></table></figure><ul><li><strong>CAP_PROP_FPS</strong>：每秒影格數。</li><li><strong>CAP_PROP_FRAME_WIDTH</strong>：影格寬度。</li><li><strong>CAP_PROP_FRAME_HEIGHT</strong>：影格高度。</li></ul><hr><h3 id="📌-cv2-VideoWriter"><a href="#📌-cv2-VideoWriter" class="headerlink" title="📌 cv2.VideoWriter()"></a>📌 <code>cv2.VideoWriter()</code></h3><p><strong>用途</strong>：建立影片輸出物件。</p><figure class="highlight python"><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">fourcc = cv2.VideoWriter_fourcc(*<span class="string">'XVID'</span>)</span><br><span class="line">out = cv2.VideoWriter(<span class="string">"output.avi"</span>, fourcc, fps, (width, height))</span><br></pre></td></tr></table></figure><ul><li><strong>fourcc</strong>：編碼格式，例如 <code>&#39;XVID&#39;</code>、<code>&#39;MJPG&#39;</code>。</li><li><strong>fps</strong>：每秒影格數。</li><li><strong>(width, height)</strong>：影格大小。</li><li><strong>out</strong>：影片輸出物件。</li></ul><h2 id="💻-範例程式-—-讀取並顯示影片"><a href="#💻-範例程式-—-讀取並顯示影片" class="headerlink" title="💻 範例程式 — 讀取並顯示影片"></a>💻 範例程式 — 讀取並顯示影片</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line">cap = cv2.VideoCapture(<span class="string">"video.mp4"</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    ret, frame = cap.read()</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> ret:</span><br><span class="line">        print(<span class="string">"影片結束或無法讀取"</span>)</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">    cv2.imshow(<span class="string">"Video"</span>, frame)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> cv2.waitKey(<span class="number">30</span>) &amp; <span class="number">0xFF</span> == ord(<span class="string">'q'</span>):</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">cap.release()</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/video-operate/01.gif" alt><br><em>圖：逐格讀取並顯示影片</em></p><h2 id="💻-範例程式-—-儲存影片檔"><a href="#💻-範例程式-—-儲存影片檔" class="headerlink" title="💻 範例程式 — 儲存影片檔"></a>💻 範例程式 — 儲存影片檔</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line">cap = cv2.VideoCapture(<span class="string">"video.mp4"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 取得影片屬性</span></span><br><span class="line">fps = cap.get(cv2.CAP_PROP_FPS)</span><br><span class="line">width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))</span><br><span class="line">height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))</span><br><span class="line"></span><br><span class="line"><span class="comment"># 建立輸出物件</span></span><br><span class="line">fourcc = cv2.VideoWriter_fourcc(*<span class="string">'XVID'</span>)</span><br><span class="line">out = cv2.VideoWriter(<span class="string">"output.avi"</span>, fourcc, fps, (width, height))</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    ret, frame = cap.read()</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> ret:</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 在影格上加文字</span></span><br><span class="line">    cv2.putText(frame, <span class="string">"OpenCV Demo"</span>, (<span class="number">50</span>, <span class="number">50</span>),</span><br><span class="line">                cv2.FONT_HERSHEY_SIMPLEX, <span class="number">1</span>, (<span class="number">0</span>, <span class="number">255</span>, <span class="number">0</span>), <span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 寫入輸出影片</span></span><br><span class="line">    out.write(frame)</span><br><span class="line"></span><br><span class="line">    cv2.imshow(<span class="string">"Output"</span>, frame)</span><br><span class="line">    <span class="keyword">if</span> cv2.waitKey(<span class="number">30</span>) &amp; <span class="number">0xFF</span> == ord(<span class="string">'q'</span>):</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">cap.release()</span><br><span class="line">out.release()</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/video-operate/02.gif" alt><br><em>圖：將影片加上文字並輸出</em></p><h2 id="💻-範例程式-—-剪輯影片-裁切區域"><a href="#💻-範例程式-—-剪輯影片-裁切區域" class="headerlink" title="💻 範例程式 — 剪輯影片 (裁切區域)"></a>💻 範例程式 — 剪輯影片 (裁切區域)</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line">cap = cv2.VideoCapture(<span class="string">"video.mp4"</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    ret, frame = cap.read()</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> ret:</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 裁切區域 (y:100~400, x:200~600)</span></span><br><span class="line">    cropped = frame[<span class="number">100</span>:<span class="number">400</span>, <span class="number">200</span>:<span class="number">600</span>]</span><br><span class="line"></span><br><span class="line">    cv2.imshow(<span class="string">"Cropped"</span>, cropped)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> cv2.waitKey(<span class="number">30</span>) &amp; <span class="number">0xFF</span> == ord(<span class="string">'q'</span>):</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">cap.release()</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/video-operate/03.gif" alt><br><em>圖：裁切影片區域並顯示</em></p><h2 id="⚠️-注意事項"><a href="#⚠️-注意事項" class="headerlink" title="⚠️ 注意事項"></a>⚠️ 注意事項</h2><ul><li><code>VideoWriter</code> 的編碼格式需安裝對應的編碼器，否則可能無法輸出。</li><li>FPS 與影格大小需與輸入影片一致，否則可能出現錯誤。</li><li>剪輯操作需注意效能，建議只處理必要的區域。</li></ul><h2 id="📊-應用場景"><a href="#📊-應用場景" class="headerlink" title="📊 應用場景"></a>📊 應用場景</h2><ul><li><strong>影片分析</strong>：逐格處理影片，提取特徵。</li><li><strong>監控錄影</strong>：儲存即時圖片成影片檔。</li><li><strong>資料集製作</strong>：裁切或標註影片，建立訓練資料。</li><li><strong>影片剪輯</strong>：加文字、裁切、合併影片。</li></ul><h2 id="🎯-結語"><a href="#🎯-結語" class="headerlink" title="🎯 結語"></a>🎯 結語</h2><p>本篇我們學會了如何使用 OpenCV <strong>讀取、儲存與剪輯視訊檔案</strong>，並能在迴圈中對影格進行處理。<br>這些技術是電腦視覺與多媒體應用的基礎，後續可以結合特徵偵測、物件追蹤等功能，打造更完整的影片分析系統。</p><p>📖 如在學習過程中遇到疑問，或是想了解更多相關主題，建議回顧一下 <a href="/python-opencv-20260106-python-opencv-index"><strong>Python | OpenCV 系列導讀</strong></a>，掌握完整的章節目錄，方便快速找到你需要的內容。<br><br></p><blockquote><p>註：以上參考了<br><a href="https://docs.opencv.org/4.x/d9/df8/tutorial_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV Tutorials</a><br><a href="https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV-Python Tutorials</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;📚-前言&quot;&gt;&lt;a href=&quot;#📚-前言&quot; class=&quot;headerlink&quot; title=&quot;📚 前言&quot;&gt;&lt;/a&gt;📚 前言&lt;/h2&gt;&lt;p&gt;在前一篇我們學會了 &lt;strong&gt;攝影機即時圖片讀取&lt;/strong&gt;。&lt;br&gt;這一篇要介紹如何使用 OpenCV
      
    
    </summary>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/categories/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/"/>
    
      <category term="06.視訊與互動篇" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/06-%E8%A6%96%E8%A8%8A%E8%88%87%E4%BA%92%E5%8B%95%E7%AF%87/"/>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/tags/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/tags/OpenCV/"/>
    
  </entry>
  
  <entry>
    <title>Python | OpenCV 攝影機即時圖片讀取</title>
    <link href="https://morosedog.gitlab.io/python-opencv-20260211-python-opencv-camera/"/>
    <id>https://morosedog.gitlab.io/python-opencv-20260211-python-opencv-camera/</id>
    <published>2026-02-11T01:00:00.000Z</published>
    <updated>2026-03-02T01:23:05.768Z</updated>
    
    <content type="html"><![CDATA[<h2 id="📚-前言"><a href="#📚-前言" class="headerlink" title="📚 前言"></a>📚 前言</h2><p>在前一篇我們學會了 <strong>圖片金字塔與多尺度分析</strong>。<br>這一篇要介紹如何使用 OpenCV <strong>即時讀取攝影機圖片</strong>。<br>這是電腦視覺的基礎操作之一，常用於人臉偵測、物件追蹤、即時監控等應用。<br>即使沒有攝影機，也可以用影片檔或網路串流來模擬。</p><h2 id="🔎-原理說明"><a href="#🔎-原理說明" class="headerlink" title="🔎 原理說明"></a>🔎 原理說明</h2><ul><li><strong>即時讀取</strong>：透過 <code>cv2.VideoCapture()</code> 連接來源，持續讀取影格 (frame)。</li><li><strong>流程</strong>：<ol><li>建立來源物件 (攝影機 / 影片檔 / 網路串流)。</li><li>迴圈讀取影格。</li><li>顯示影格。</li><li>按下指定按鍵結束程式。</li></ol></li></ul><h2 id="🧠-函式與參數說明"><a href="#🧠-函式與參數說明" class="headerlink" title="🧠 函式與參數說明"></a>🧠 函式與參數說明</h2><h3 id="📌-cv2-VideoCapture"><a href="#📌-cv2-VideoCapture" class="headerlink" title="📌 cv2.VideoCapture()"></a>📌 <code>cv2.VideoCapture()</code></h3><p><strong>用途</strong>：建立攝影機物件，讀取即時圖片。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cap = cv2.VideoCapture(<span class="number">0</span>)</span><br></pre></td></tr></table></figure><ul><li><strong>0</strong>：代表預設攝影機。</li><li><strong>1, 2…</strong>：代表其他攝影機。</li><li><strong>cap</strong>：攝影機物件。</li></ul><h3 id="📌-cap-read"><a href="#📌-cap-read" class="headerlink" title="📌 cap.read()"></a>📌 <code>cap.read()</code></h3><p><strong>用途</strong>：讀取一個影格。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ret, frame = cap.read()</span><br></pre></td></tr></table></figure><ul><li><strong>ret</strong>：是否成功讀取 (True/False)。</li><li><strong>frame</strong>：讀取到的圖片。</li></ul><h3 id="📌-cv2-imshow"><a href="#📌-cv2-imshow" class="headerlink" title="📌 cv2.imshow()"></a>📌 <code>cv2.imshow()</code></h3><p><strong>用途</strong>：顯示圖片。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cv2.imshow(<span class="string">"Camera"</span>, frame)</span><br></pre></td></tr></table></figure><ul><li><strong>“Camera”</strong>：視窗名稱。</li><li><strong>frame</strong>：要顯示的圖片。</li></ul><h2 id="💻-範例程式-—-即時讀取攝影機圖片"><a href="#💻-範例程式-—-即時讀取攝影機圖片" class="headerlink" title="💻 範例程式 — 即時讀取攝影機圖片"></a>💻 範例程式 — 即時讀取攝影機圖片</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line"><span class="comment"># 建立攝影機物件 (0 = 預設攝影機)</span></span><br><span class="line">cap = cv2.VideoCapture(<span class="number">0</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    <span class="comment"># 讀取影格</span></span><br><span class="line">    ret, frame = cap.read()</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> ret:</span><br><span class="line">        print(<span class="string">"無法讀取圖片"</span>)</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 顯示圖片</span></span><br><span class="line">    cv2.imshow(<span class="string">"Camera"</span>, frame)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 按下 q 鍵退出</span></span><br><span class="line">    <span class="keyword">if</span> cv2.waitKey(<span class="number">1</span>) &amp; <span class="number">0xFF</span> == ord(<span class="string">'q'</span>):</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 釋放攝影機資源</span></span><br><span class="line">cap.release()</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/camera/01.gif" alt><br><em>圖：即時讀取攝影機圖片並顯示</em></p><blockquote><p>註：由於本身沒有攝影鏡頭，所以無法示範效果。</p></blockquote><h2 id="💻-範例程式-—-讀取影片檔"><a href="#💻-範例程式-—-讀取影片檔" class="headerlink" title="💻 範例程式 — 讀取影片檔"></a>💻 範例程式 — 讀取影片檔</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line"><span class="comment"># 讀取影片檔</span></span><br><span class="line">cap = cv2.VideoCapture(<span class="string">"video.mp4"</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    ret, frame = cap.read()</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> ret:</span><br><span class="line">        print(<span class="string">"影片結束或無法讀取"</span>)</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">    cv2.imshow(<span class="string">"Video"</span>, frame)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 按下 q 鍵退出</span></span><br><span class="line">    <span class="keyword">if</span> cv2.waitKey(<span class="number">30</span>) &amp; <span class="number">0xFF</span> == ord(<span class="string">'q'</span>):</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">cap.release()</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/camera/02.gif" alt><br><em>圖：從影片檔逐格讀取並顯示</em></p><h2 id="💻-範例程式-—-讀取交通監視器-MJPEG-串流"><a href="#💻-範例程式-—-讀取交通監視器-MJPEG-串流" class="headerlink" title="💻 範例程式 — 讀取交通監視器 MJPEG 串流"></a>💻 範例程式 — 讀取交通監視器 MJPEG 串流</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line"><span class="comment"># MJPEG 串流 URL (交通監視器)</span></span><br><span class="line">url = <span class="string">"https://cctvn.freeway.gov.tw/abs2mjpg/bmjpg?camera=73270"</span></span><br><span class="line"></span><br><span class="line">cap = cv2.VideoCapture(url)</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    ret, frame = cap.read()</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> ret:</span><br><span class="line">        print(<span class="string">"無法讀取串流"</span>)</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">    cv2.imshow(<span class="string">"Traffic Camera"</span>, frame)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 按下 q 鍵退出</span></span><br><span class="line">    <span class="keyword">if</span> cv2.waitKey(<span class="number">30</span>) &amp; <span class="number">0xFF</span> == ord(<span class="string">'q'</span>):</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">cap.release()</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/camera/03.gif" alt><br><em>圖：即時讀取交通監視器的 MJPEG 串流</em></p><h2 id="⚠️-注意事項"><a href="#⚠️-注意事項" class="headerlink" title="⚠️ 注意事項"></a>⚠️ 注意事項</h2><ul><li>攝影機編號可能不同，<code>0</code> 是預設，若有多個攝影機可嘗試 <code>1</code>、<code>2</code>。</li><li><code>cv2.VideoCapture()</code> 可讀取 <strong>攝影機、影片檔、網路串流</strong>。</li><li>MJPEG 串流需要網路連線，若 URL 無效或網路不通，會無法讀取。</li><li>即時圖片處理需要效能，建議在迴圈中避免過度複雜的運算。</li></ul><h2 id="📊-應用場景"><a href="#📊-應用場景" class="headerlink" title="📊 應用場景"></a>📊 應用場景</h2><ul><li><strong>人臉偵測</strong>：即時讀取並進行人臉辨識。</li><li><strong>物件追蹤</strong>：即時追蹤移動物體。</li><li><strong>監控系統</strong>：即時顯示並儲存圖片。</li><li><strong>交通監控</strong>：讀取交通監視器串流，分析即時路況。</li><li><strong>互動應用</strong>：即時圖片結合 AR/VR。</li></ul><h2 id="🎯-結語"><a href="#🎯-結語" class="headerlink" title="🎯 結語"></a>🎯 結語</h2><p>本篇我們學會了如何使用 OpenCV <strong>即時讀取攝影機圖片</strong>，並且能顯示影格。<br>即使沒有攝影機，也能用影片檔或網路串流來模擬。<br>這是電腦視覺的基礎操作，後續可以結合人臉偵測、物件追蹤等功能，打造更完整的應用。</p><p>📖 如在學習過程中遇到疑問，或是想了解更多相關主題，建議回顧一下 <a href="/python-opencv-20260106-python-opencv-index"><strong>Python | OpenCV 系列導讀</strong></a>，掌握完整的章節目錄，方便快速找到你需要的內容。<br><br></p><blockquote><p>註：以上參考了<br><a href="https://docs.opencv.org/4.x/d9/df8/tutorial_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV Tutorials</a><br><a href="https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV-Python Tutorials</a><br><a href="https://www.1968services.tw/" target="_blank" rel="external nofollow noopener noreferrer">1968-高速公路資訊網</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;📚-前言&quot;&gt;&lt;a href=&quot;#📚-前言&quot; class=&quot;headerlink&quot; title=&quot;📚 前言&quot;&gt;&lt;/a&gt;📚 前言&lt;/h2&gt;&lt;p&gt;在前一篇我們學會了 &lt;strong&gt;圖片金字塔與多尺度分析&lt;/strong&gt;。&lt;br&gt;這一篇要介紹如何使用 Open
      
    
    </summary>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/categories/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/"/>
    
      <category term="06.視訊與互動篇" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/06-%E8%A6%96%E8%A8%8A%E8%88%87%E4%BA%92%E5%8B%95%E7%AF%87/"/>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/tags/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/tags/OpenCV/"/>
    
  </entry>
  
  <entry>
    <title>Python | OpenCV 圖片金字塔與多尺度分析</title>
    <link href="https://morosedog.gitlab.io/python-opencv-20260210-python-opencv-pyramid/"/>
    <id>https://morosedog.gitlab.io/python-opencv-20260210-python-opencv-pyramid/</id>
    <published>2026-02-10T01:00:00.000Z</published>
    <updated>2026-03-02T01:23:05.768Z</updated>
    
    <content type="html"><![CDATA[<h2 id="📚-前言"><a href="#📚-前言" class="headerlink" title="📚 前言"></a>📚 前言</h2><p>在前一篇我們學會了 <strong>相機校正與畸變矯正</strong>。<br>在圖片處理與電腦視覺中，常常需要在不同解析度下觀察圖片特徵。<br>例如：人臉偵測、物件追蹤、特徵匹配，都需要在多種尺度下分析圖片。<br>這時候就會用到 <strong>圖片金字塔 (Image Pyramid)</strong> 與 <strong>多尺度分析 (Multi-scale Analysis)</strong>。</p><h2 id="🔎-原理說明"><a href="#🔎-原理說明" class="headerlink" title="🔎 原理說明"></a>🔎 原理說明</h2><ul><li><strong>圖片金字塔</strong>：將圖片逐層縮小或放大，形成一個「金字塔結構」。<ul><li><strong>高層</strong>：解析度低，圖片小。</li><li><strong>低層</strong>：解析度高，圖片大。</li></ul></li><li><strong>用途</strong>：<ul><li>在不同尺度下偵測特徵。</li><li>加速演算法，避免在原始大圖片上做大量計算。</li><li>提供多層次的圖片表示，方便進行匹配或搜尋。</li></ul></li></ul><h2 id="🧠-函式與參數說明"><a href="#🧠-函式與參數說明" class="headerlink" title="🧠 函式與參數說明"></a>🧠 函式與參數說明</h2><h3 id="📌-cv2-pyrDown"><a href="#📌-cv2-pyrDown" class="headerlink" title="📌 cv2.pyrDown()"></a>📌 <code>cv2.pyrDown()</code></h3><p><strong>用途</strong>：將圖片縮小一半，並同時進行高斯模糊，避免縮小時產生鋸齒或 aliasing。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">smaller = cv2.pyrDown(img)</span><br></pre></td></tr></table></figure><ul><li><strong>img</strong>：輸入圖片。</li><li><strong>smaller</strong>：縮小並平滑後的圖片。</li></ul><h3 id="📌-cv2-pyrUp"><a href="#📌-cv2-pyrUp" class="headerlink" title="📌 cv2.pyrUp()"></a>📌 <code>cv2.pyrUp()</code></h3><p><strong>用途</strong>：將圖片放大一倍，並同時進行插值與模糊，使放大後的圖片保持平滑。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">larger = cv2.pyrUp(img)</span><br></pre></td></tr></table></figure><ul><li><strong>img</strong>：輸入圖片。</li><li><strong>larger</strong>：放大並平滑後的圖片。</li></ul><h3 id="📌-拉普拉斯金字塔-Laplacian-Pyramid"><a href="#📌-拉普拉斯金字塔-Laplacian-Pyramid" class="headerlink" title="📌 拉普拉斯金字塔 (Laplacian Pyramid)"></a>📌 拉普拉斯金字塔 (Laplacian Pyramid)</h3><p><strong>用途</strong>：由高斯金字塔相鄰層的差異構成，常用於圖片壓縮與融合。<br>它能保留圖片的細節資訊，並在多尺度下進行分析。<br>例如：將兩張圖片利用拉普拉斯金字塔進行平滑融合，避免邊界突兀。</p><figure class="highlight python"><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">gaussian_expanded = cv2.pyrUp(gp[i], dstsize=gp[i<span class="number">-1</span>].shape[<span class="number">1</span>::<span class="number">-1</span>])</span><br><span class="line">laplacian = cv2.subtract(gp[i<span class="number">-1</span>], gaussian_expanded)</span><br></pre></td></tr></table></figure><ul><li><strong>gp</strong>：高斯金字塔。</li><li><strong>gaussian_expanded</strong>：放大後的高斯圖片，與上一層對齊。</li><li><strong>laplacian</strong>：相鄰層的差異，代表該層的細節資訊。</li></ul><h2 id="💻-範例程式-—-建立圖片金字塔"><a href="#💻-範例程式-—-建立圖片金字塔" class="headerlink" title="💻 範例程式 — 建立圖片金字塔"></a>💻 範例程式 — 建立圖片金字塔</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line">img = cv2.imread(<span class="string">"test.png"</span>)  <span class="comment"># 部落格頭像</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 建立金字塔</span></span><br><span class="line">smaller = cv2.pyrDown(img)</span><br><span class="line">larger = cv2.pyrUp(img)</span><br><span class="line"></span><br><span class="line">cv2.imshow(<span class="string">"Original"</span>, img)</span><br><span class="line">cv2.imshow(<span class="string">"PyrDown"</span>, smaller)</span><br><span class="line">cv2.imshow(<span class="string">"PyrUp"</span>, larger)</span><br><span class="line"></span><br><span class="line">cv2.waitKey(<span class="number">0</span>)</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/pyramid/01.png" alt><br><em>圖：原始圖片、縮小一層、放大一層</em></p><h2 id="💻-範例程式-—-多層金字塔"><a href="#💻-範例程式-—-多層金字塔" class="headerlink" title="💻 範例程式 — 多層金字塔"></a>💻 範例程式 — 多層金字塔</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line">img = cv2.imread(<span class="string">"test.png"</span>)  <span class="comment"># 部落格頭像</span></span><br><span class="line"></span><br><span class="line">layer = img.copy()</span><br><span class="line">gp = [layer]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 建立高斯金字塔 (Gaussian Pyramid)</span></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">3</span>):</span><br><span class="line">    layer = cv2.pyrDown(layer)</span><br><span class="line">    gp.append(layer)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 顯示不同層次</span></span><br><span class="line"><span class="keyword">for</span> i, layer <span class="keyword">in</span> enumerate(gp):</span><br><span class="line">    cv2.imshow(<span class="string">f"Layer <span class="subst">&#123;i&#125;</span>"</span>, layer)</span><br><span class="line"></span><br><span class="line">cv2.waitKey(<span class="number">0</span>)</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/pyramid/02.png" alt><br><em>圖：高斯金字塔 — 逐層縮小圖片</em></p><h2 id="💻-範例程式-—-拉普拉斯金字塔-Laplacian-Pyramid"><a href="#💻-範例程式-—-拉普拉斯金字塔-Laplacian-Pyramid" class="headerlink" title="💻 範例程式 — 拉普拉斯金字塔 (Laplacian Pyramid)"></a>💻 範例程式 — 拉普拉斯金字塔 (Laplacian Pyramid)</h2><p>拉普拉斯金字塔常用於圖片壓縮與重建。<br>它是由高斯金字塔相鄰層的差異構成。</p><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line">img = cv2.imread(<span class="string">"test.png"</span>)  <span class="comment"># 部落格頭像</span></span><br><span class="line">layer = img.copy()</span><br><span class="line">gp = [layer]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 建立高斯金字塔</span></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">3</span>):</span><br><span class="line">    layer = cv2.pyrDown(layer)</span><br><span class="line">    gp.append(layer)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 建立拉普拉斯金字塔</span></span><br><span class="line">lp = []</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">3</span>, <span class="number">0</span>, <span class="number">-1</span>):</span><br><span class="line">    <span class="comment"># 保證大小一致</span></span><br><span class="line">    gaussian_expanded = cv2.pyrUp(gp[i], dstsize=gp[i<span class="number">-1</span>].shape[<span class="number">1</span>::<span class="number">-1</span>])</span><br><span class="line">    laplacian = cv2.subtract(gp[i<span class="number">-1</span>], gaussian_expanded)</span><br><span class="line">    lp.append(laplacian)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 顯示拉普拉斯金字塔</span></span><br><span class="line"><span class="keyword">for</span> i, layer <span class="keyword">in</span> enumerate(lp):</span><br><span class="line">    cv2.imshow(<span class="string">f"Laplacian <span class="subst">&#123;i&#125;</span>"</span>, layer)</span><br><span class="line"></span><br><span class="line">cv2.waitKey(<span class="number">0</span>)</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/pyramid/03.png" alt><br><em>圖：拉普拉斯金字塔 — 顯示不同層次的差異</em></p><h2 id="⚠️-注意事項"><a href="#⚠️-注意事項" class="headerlink" title="⚠️ 注意事項"></a>⚠️ 注意事項</h2><ul><li><code>pyrDown</code> 與 <code>pyrUp</code> 只能處理圖片大小為偶數的情況，否則可能會有誤差。</li><li>金字塔層數不宜過多，否則圖片會過度模糊或失真。</li><li>拉普拉斯金字塔常用於圖片壓縮與融合。</li></ul><h2 id="📊-應用場景"><a href="#📊-應用場景" class="headerlink" title="📊 應用場景"></a>📊 應用場景</h2><ul><li><strong>人臉偵測</strong>：在不同尺度下搜尋人臉。</li><li><strong>圖片融合</strong>：利用拉普拉斯金字塔進行平滑融合。</li><li><strong>特徵匹配</strong>：在多尺度下比對圖片特徵。</li><li><strong>圖片壓縮</strong>：利用金字塔結構進行高效壓縮。</li></ul><h2 id="🎯-結語"><a href="#🎯-結語" class="headerlink" title="🎯 結語"></a>🎯 結語</h2><p>本篇我們學會了 <strong>圖片金字塔 (Image Pyramid)</strong> 與 <strong>多尺度分析 (Multi-scale Analysis)</strong>，並透過 OpenCV 的 <code>pyrDown</code>、<code>pyrUp</code> 建立高斯金字塔與拉普拉斯金字塔。<br>這些技術在電腦視覺中非常常見，尤其在人臉偵測、圖片融合與特徵匹配等應用中。</p><p>📖 如在學習過程中遇到疑問，或是想了解更多相關主題，建議回顧一下 <a href="/python-opencv-20260106-python-opencv-index"><strong>Python | OpenCV 系列導讀</strong></a>，掌握完整的章節目錄，方便快速找到你需要的內容。<br><br></p><blockquote><p>註：以上參考了<br><a href="https://docs.opencv.org/4.x/d9/df8/tutorial_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV Tutorials</a><br><a href="https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV-Python Tutorials</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;📚-前言&quot;&gt;&lt;a href=&quot;#📚-前言&quot; class=&quot;headerlink&quot; title=&quot;📚 前言&quot;&gt;&lt;/a&gt;📚 前言&lt;/h2&gt;&lt;p&gt;在前一篇我們學會了 &lt;strong&gt;相機校正與畸變矯正&lt;/strong&gt;。&lt;br&gt;在圖片處理與電腦視覺中，常常需要在
      
    
    </summary>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/categories/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/"/>
    
      <category term="05.特徵與進階篇" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/05-%E7%89%B9%E5%BE%B5%E8%88%87%E9%80%B2%E9%9A%8E%E7%AF%87/"/>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/tags/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/tags/OpenCV/"/>
    
  </entry>
  
  <entry>
    <title>Python | OpenCV 相機校正與畸變矯正</title>
    <link href="https://morosedog.gitlab.io/python-opencv-20260209-python-opencv-calibration/"/>
    <id>https://morosedog.gitlab.io/python-opencv-20260209-python-opencv-calibration/</id>
    <published>2026-02-09T01:00:00.000Z</published>
    <updated>2026-03-02T01:23:05.768Z</updated>
    
    <content type="html"><![CDATA[<h2 id="📚-前言"><a href="#📚-前言" class="headerlink" title="📚 前言"></a>📚 前言</h2><p>在前一篇我們學會了 <strong>透視變換與圖片校正</strong>。<br>這一篇要介紹 <strong>相機校正 (Camera Calibration)</strong> 與 <strong>畸變矯正 (Distortion Correction)</strong>。<br>相機鏡頭常會造成桶狀或枕狀畸變，導致圖片變形。透過校正，我們能得到更精準的圖片，這在 <strong>AR、3D 重建、工業檢測</strong> 等應用中非常重要。</p><h2 id="🎨-範例圖片"><a href="#🎨-範例圖片" class="headerlink" title="🎨 範例圖片"></a>🎨 範例圖片</h2><p>這裡我們使用 GitHub 專案 <a href="https://github.com/tarkers/Camera_Calibration_OpenCV/tree/main/Test" target="_blank" rel="external nofollow noopener noreferrer">tarkers/Camera_Calibration_OpenCV</a> 提供的棋盤格圖片。<br>下載 <code>Test</code> 資料夾，裡面有多張棋盤格照片，非常適合用來做校正。</p><h2 id="🔎-原理說明"><a href="#🔎-原理說明" class="headerlink" title="🔎 原理說明"></a>🔎 原理說明</h2><ul><li><strong>棋盤格</strong>：我們用棋盤格當作「標準尺」，因為它的格子大小固定。</li><li><strong>流程</strong>：<ol><li>拍或下載多張棋盤格照片。</li><li>找出棋盤格角落。</li><li>用這些角落去計算相機的「內部參數」和「畸變係數」。</li><li>用這些參數去修正照片。</li></ol></li></ul><h2 id="🧠-函式與參數說明"><a href="#🧠-函式與參數說明" class="headerlink" title="🧠 函式與參數說明"></a>🧠 函式與參數說明</h2><h3 id="📌-cv2-findChessboardCorners"><a href="#📌-cv2-findChessboardCorners" class="headerlink" title="📌 cv2.findChessboardCorners()"></a>📌 <code>cv2.findChessboardCorners()</code></h3><p><strong>用途</strong>：偵測棋盤格圖片中的角點位置，作為相機校正的基礎資料。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ret, corners = cv2.findChessboardCorners(image, patternSize)</span><br></pre></td></tr></table></figure><ul><li><strong>image</strong>：輸入圖片 (灰階)。</li><li><strong>patternSize</strong>：棋盤格內角點數量，例如 (9,6)。</li><li><strong>ret</strong>：是否成功找到角點。</li><li><strong>corners</strong>：角點座標。</li></ul><h3 id="📌-cv2-calibrateCamera"><a href="#📌-cv2-calibrateCamera" class="headerlink" title="📌 cv2.calibrateCamera()"></a>📌 <code>cv2.calibrateCamera()</code></h3><p><strong>用途</strong>：利用多張棋盤格圖片的角點，計算相機的內部參數與畸變係數。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, imageSize, <span class="literal">None</span>, <span class="literal">None</span>)</span><br></pre></td></tr></table></figure><ul><li><strong>objpoints</strong>：棋盤格的 3D 座標。</li><li><strong>imgpoints</strong>：圖片中的 2D 座標。</li><li><strong>imageSize</strong>：圖片大小。</li><li><strong>mtx</strong>：相機矩陣 (焦距、主點位置)。</li><li><strong>dist</strong>：畸變係數。</li><li><strong>rvecs, tvecs</strong>：旋轉與平移向量。</li></ul><h3 id="📌-cv2-undistort"><a href="#📌-cv2-undistort" class="headerlink" title="📌 cv2.undistort()"></a>📌 <code>cv2.undistort()</code></h3><p><strong>用途</strong>：利用校正得到的相機矩陣與畸變係數，修正圖片的變形。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dst = cv2.undistort(img, mtx, dist, <span class="literal">None</span>, newcameramtx)</span><br></pre></td></tr></table></figure><ul><li><strong>img</strong>：輸入圖片。</li><li><strong>mtx</strong>：相機矩陣。</li><li><strong>dist</strong>：畸變係數。</li><li><strong>newcameramtx</strong>：優化後的相機矩陣。</li><li><strong>dst</strong>：輸出矯正後的圖片。</li></ul><h2 id="💻-範例程式-—-相機校正"><a href="#💻-範例程式-—-相機校正" class="headerlink" title="💻 範例程式 — 相機校正"></a>💻 範例程式 — 相機校正</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"><span class="keyword">import</span> glob</span><br><span class="line"></span><br><span class="line"><span class="comment"># 棋盤格大小 (內角點數量)</span></span><br><span class="line">chessboard_size = (<span class="number">9</span>, <span class="number">6</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 建立棋盤格的 3D 座標</span></span><br><span class="line">objp = np.zeros((np.prod(chessboard_size), <span class="number">3</span>), np.float32)</span><br><span class="line">objp[:,:<span class="number">2</span>] = np.mgrid[<span class="number">0</span>:chessboard_size[<span class="number">0</span>], <span class="number">0</span>:chessboard_size[<span class="number">1</span>]].T.reshape(<span class="number">-1</span>,<span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">objpoints = []  <span class="comment"># 3D 點</span></span><br><span class="line">imgpoints = []  <span class="comment"># 2D 點</span></span><br><span class="line"></span><br><span class="line">images = glob.glob(<span class="string">"Test/*.jpg"</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> fname <span class="keyword">in</span> images:</span><br><span class="line">    img = cv2.imread(fname)</span><br><span class="line">    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)</span><br><span class="line"></span><br><span class="line">    ret, corners = cv2.findChessboardCorners(gray, chessboard_size, <span class="literal">None</span>)</span><br><span class="line">    <span class="keyword">if</span> ret:</span><br><span class="line">        objpoints.append(objp)</span><br><span class="line">        imgpoints.append(corners)</span><br><span class="line"></span><br><span class="line">        cv2.drawChessboardCorners(img, chessboard_size, corners, ret)</span><br><span class="line">        cv2.imshow(<span class="string">"Corners"</span>, img)</span><br><span class="line">        cv2.waitKey(<span class="number">500</span>)</span><br><span class="line"></span><br><span class="line">cv2.destroyAllWindows()</span><br><span class="line"></span><br><span class="line"><span class="comment"># 相機校正</span></span><br><span class="line">ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::<span class="number">-1</span>], <span class="literal">None</span>, <span class="literal">None</span>)</span><br><span class="line"></span><br><span class="line">print(<span class="string">"Camera Matrix:\n"</span>, mtx)</span><br><span class="line">print(<span class="string">"Distortion Coefficients:\n"</span>, dist)</span><br></pre></td></tr></table></figure><p>輸出：</p><figure class="highlight console"><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">Camera Matrix:</span><br><span class="line"> [[532.35423658   0.         342.42264659]</span><br><span class="line"> [  0.         532.53995406 235.04228201]</span><br><span class="line"> [  0.           0.           1.        ]]</span><br><span class="line">Distortion Coefficients:</span><br><span class="line"> [[-2.73462172e-01 -3.85288349e-02  1.12629584e-03 -1.75411180e-04</span><br><span class="line">   2.85170396e-01]]</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/calibration/01.gif" alt><br><em>圖：程式碼執行結果 — 取得相機矩陣與畸變係數</em></p><h2 id="💻-範例程式-—-畸變矯正"><a href="#💻-範例程式-—-畸變矯正" class="headerlink" title="💻 範例程式 — 畸變矯正"></a>💻 範例程式 — 畸變矯正</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line"><span class="comment"># 手動輸入相機矩陣 (mtx)</span></span><br><span class="line">mtx = np.array([[<span class="number">532.35423658</span>,   <span class="number">0.</span>        , <span class="number">342.42264659</span>],</span><br><span class="line">                [  <span class="number">0.</span>        , <span class="number">532.53995406</span>, <span class="number">235.04228201</span>],</span><br><span class="line">                [  <span class="number">0.</span>        ,   <span class="number">0.</span>        ,   <span class="number">1.</span>        ]])</span><br><span class="line"></span><br><span class="line"><span class="comment"># 手動輸入畸變係數 (dist)</span></span><br><span class="line">dist = np.array([[<span class="number">-2.73462172e-01</span>, <span class="number">-3.85288349e-02</span>,</span><br><span class="line">                   <span class="number">1.12629584e-03</span>, <span class="number">-1.75411180e-04</span>,</span><br><span class="line">                   <span class="number">2.85170396e-01</span>]])</span><br><span class="line"></span><br><span class="line"><span class="comment"># 讀取一張測試圖片</span></span><br><span class="line">img = cv2.imread(<span class="string">"Test/left01.jpg"</span>)</span><br><span class="line">h, w = img.shape[:<span class="number">2</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 計算最佳相機矩陣</span></span><br><span class="line">newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w,h), <span class="number">1</span>, (w,h))</span><br><span class="line"></span><br><span class="line"><span class="comment"># 矯正圖片</span></span><br><span class="line">dst = cv2.undistort(img, mtx, dist, <span class="literal">None</span>, newcameramtx)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 裁切 ROI (避免黑邊)</span></span><br><span class="line">x, y, w, h = roi</span><br><span class="line">dst = dst[y:y+h, x:x+w]</span><br><span class="line"></span><br><span class="line">cv2.imshow(<span class="string">"Original"</span>, img)</span><br><span class="line">cv2.imshow(<span class="string">"Undistorted"</span>, dst)</span><br><span class="line">cv2.waitKey(<span class="number">0</span>)</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/calibration/02.png" alt><br><em>圖：程式碼執行結果 — 修正鏡頭畸變後的圖片</em></p><h2 id="⚠️-注意事項"><a href="#⚠️-注意事項" class="headerlink" title="⚠️ 注意事項"></a>⚠️ 注意事項</h2><ul><li>校正需要 <strong>多張棋盤格圖片</strong>，角度與距離要多樣化。</li><li>畸變矯正後可能需要裁切 ROI，避免黑邊。</li><li>相機校正結果依鏡頭與拍攝環境而異。</li></ul><h2 id="📊-應用場景"><a href="#📊-應用場景" class="headerlink" title="📊 應用場景"></a>📊 應用場景</h2><ul><li><strong>AR/VR</strong>：提升圖片精準度。</li><li><strong>工業檢測</strong>：避免因畸變造成測量誤差。</li><li><strong>3D 重建</strong>：確保投影模型正確。</li><li><strong>攝影應用</strong>：修正廣角鏡頭的桶狀畸變。</li></ul><h2 id="🎯-結語"><a href="#🎯-結語" class="headerlink" title="🎯 結語"></a>🎯 結語</h2><p>本篇我們學會了 <strong>相機校正與畸變矯正</strong>，並理解了如何利用棋盤格圖片計算相機矩陣與畸變係數，最後使用 <code>cv2.undistort()</code> 修正圖片。<br>這是電腦視覺中非常重要的一步，能讓後續的圖片處理與分析更加精準。</p><p>📖 如在學習過程中遇到疑問，或是想了解更多相關主題，建議回顧一下 <a href="/python-opencv-20260106-python-opencv-index"><strong>Python | OpenCV 系列導讀</strong></a>，掌握完整的章節目錄，方便快速找到你需要的內容。<br><br></p><blockquote><p>註：以上參考了<br><a href="https://docs.opencv.org/4.x/d9/df8/tutorial_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV Tutorials</a><br><a href="https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV-Python Tutorials</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;📚-前言&quot;&gt;&lt;a href=&quot;#📚-前言&quot; class=&quot;headerlink&quot; title=&quot;📚 前言&quot;&gt;&lt;/a&gt;📚 前言&lt;/h2&gt;&lt;p&gt;在前一篇我們學會了 &lt;strong&gt;透視變換與圖片校正&lt;/strong&gt;。&lt;br&gt;這一篇要介紹 &lt;strong&gt;相機
      
    
    </summary>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/categories/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/"/>
    
      <category term="05.特徵與進階篇" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/05-%E7%89%B9%E5%BE%B5%E8%88%87%E9%80%B2%E9%9A%8E%E7%AF%87/"/>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/tags/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/tags/OpenCV/"/>
    
  </entry>
  
  <entry>
    <title>Python | OpenCV 透視變換與圖片校正</title>
    <link href="https://morosedog.gitlab.io/python-opencv-20260208-python-opencv-perspective/"/>
    <id>https://morosedog.gitlab.io/python-opencv-20260208-python-opencv-perspective/</id>
    <published>2026-02-08T01:00:00.000Z</published>
    <updated>2026-03-02T01:23:05.768Z</updated>
    
    <content type="html"><![CDATA[<h2 id="📚-前言"><a href="#📚-前言" class="headerlink" title="📚 前言"></a>📚 前言</h2><p>在前一篇我們學會了 <strong>特徵點偵測與匹配</strong>。<br>這一篇要介紹 <strong>透視變換 (Perspective Transform)</strong> 與 <strong>圖片校正 (Image Rectification)</strong>，這是圖片幾何處理的重要技巧，能將傾斜或變形的圖片校正回正常視角，常用於文件掃描、場景校正、投影轉換等應用。</p><h2 id="🎨-範例圖片"><a href="#🎨-範例圖片" class="headerlink" title="🎨 範例圖片"></a>🎨 範例圖片</h2><p><img loading="lazy" src="/images/python/opencv/perspective/document.png" alt><br><em>圖：範例圖片 document.png — 傾斜的文件</em><br><img loading="lazy" src="/images/python/opencv/perspective/board.png" alt><br><em>圖：範例圖片 board.png — 傾斜的投影幕</em></p><h2 id="🔎-透視變換原理"><a href="#🔎-透視變換原理" class="headerlink" title="🔎 透視變換原理"></a>🔎 透視變換原理</h2><h3 id="原理說明"><a href="#原理說明" class="headerlink" title="原理說明"></a>原理說明</h3><ul><li>透視變換是一種 <strong>射影變換 (Projective Transform)</strong>，能將圖片中的四邊形區域映射到另一個矩形區域。</li><li>常用於校正傾斜的文件、投影幕或場景。</li><li>核心函式是 <code>cv2.getPerspectiveTransform()</code> 與 <code>cv2.warpPerspective()</code>。</li></ul><h3 id="參數說明"><a href="#參數說明" class="headerlink" title="參數說明"></a>參數說明</h3><figure class="highlight python"><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">cv2.getPerspectiveTransform(srcPoints, dstPoints)</span><br><span class="line">cv2.warpPerspective(src, M, dsize)</span><br></pre></td></tr></table></figure><ul><li><strong>srcPoints</strong>：原始圖片中的四個點座標。</li><li><strong>dstPoints</strong>：目標圖片中的四個點座標。</li><li><strong>M</strong>：透視變換矩陣 (3x3)。</li><li><strong>dsize</strong>：輸出圖片大小 (寬, 高)。</li></ul><h2 id="💻-範例程式-—-文件校正"><a href="#💻-範例程式-—-文件校正" class="headerlink" title="💻 範例程式 — 文件校正"></a>💻 範例程式 — 文件校正</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line">img = cv2.imread(<span class="string">"document.png"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 假設透視後矩形的四個角點座標 (需人工量測或用工具選取)</span></span><br><span class="line">pts1 = np.float32([[<span class="number">180</span>,<span class="number">180</span>],[<span class="number">620</span>,<span class="number">130</span>],[<span class="number">220</span>,<span class="number">470</span>],[<span class="number">580</span>,<span class="number">500</span>]])</span><br><span class="line"></span><br><span class="line"><span class="comment"># 設定校正後的矩形大小</span></span><br><span class="line">width, height = <span class="number">400</span>, <span class="number">300</span></span><br><span class="line">pts2 = np.float32([[<span class="number">0</span>,<span class="number">0</span>],[width,<span class="number">0</span>],[<span class="number">0</span>,height],[width,height]])</span><br><span class="line"></span><br><span class="line">M = cv2.getPerspectiveTransform(pts1, pts2)</span><br><span class="line">dst = cv2.warpPerspective(img, M, (width, height))</span><br><span class="line"></span><br><span class="line">cv2.imshow(<span class="string">"Original"</span>, img)</span><br><span class="line">cv2.imshow(<span class="string">"Corrected"</span>, dst)</span><br><span class="line">cv2.waitKey(<span class="number">0</span>)</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/perspective/01.png" alt><br><em>圖：程式碼執行結果 — 原始傾斜文件與校正後的矩形文件</em></p><h2 id="💻-範例程式-—-場景校正"><a href="#💻-範例程式-—-場景校正" class="headerlink" title="💻 範例程式 — 場景校正"></a>💻 範例程式 — 場景校正</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line">img = cv2.imread(<span class="string">"board.png"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用生成時的四個角點</span></span><br><span class="line">pts1 = np.float32([[<span class="number">80</span>,<span class="number">100</span>],[<span class="number">520</span>,<span class="number">70</span>],[<span class="number">120</span>,<span class="number">320</span>],[<span class="number">480</span>,<span class="number">330</span>]])</span><br><span class="line">pts2 = np.float32([[<span class="number">0</span>,<span class="number">0</span>],[<span class="number">400</span>,<span class="number">0</span>],[<span class="number">0</span>,<span class="number">300</span>],[<span class="number">400</span>,<span class="number">300</span>]])</span><br><span class="line"></span><br><span class="line">M = cv2.getPerspectiveTransform(pts1, pts2)</span><br><span class="line">dst = cv2.warpPerspective(img, M, (<span class="number">400</span>,<span class="number">300</span>))</span><br><span class="line"></span><br><span class="line">cv2.imshow(<span class="string">"Original"</span>, img)</span><br><span class="line">cv2.imshow(<span class="string">"Corrected"</span>, dst)</span><br><span class="line">cv2.waitKey(<span class="number">0</span>)</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/perspective/02.png" alt><br><em>圖：程式碼執行結果 — 投影幕校正</em></p><h2 id="📊-文件校正-vs-場景校正"><a href="#📊-文件校正-vs-場景校正" class="headerlink" title="📊 文件校正 vs 場景校正"></a>📊 文件校正 vs 場景校正</h2><table><thead><tr><th>類型</th><th>目的</th><th>角點來源</th><th>重點</th><th>常見應用</th></tr></thead><tbody><tr><td><strong>文件校正</strong></td><td>把拍攝時傾斜的文件校正成正矩形，方便閱讀或 OCR 辨識</td><td>文件四角 (票據、身分證、書頁)</td><td>保持文字比例正確，避免字體拉伸</td><td>掃描文件、票據辨識、證件 OCR</td></tr><tr><td><strong>場景校正</strong></td><td>把場景中的矩形物件校正成正視角，方便顯示或分析</td><td>投影幕、看板、地板方格的四角</td><td>幾何對齊即可，比例不一定要符合真實物件</td><td>投影幕校正、AR 虛擬投影、場景重建</td></tr></tbody></table><h3 id="🧾-小結"><a href="#🧾-小結" class="headerlink" title="🧾 小結"></a>🧾 <strong>小結</strong></h3><ul><li><strong>文件校正</strong>：強調「文字比例正確」，目的是讓文件可讀性和 OCR 辨識效果更好。</li><li><strong>場景校正</strong>：強調「幾何對齊」，目的是讓場景顯示或分析更方便。</li><li>技術上都是用 <code>cv2.getPerspectiveTransform()</code> + <code>cv2.warpPerspective()</code>，差別在 <strong>角點選取方式</strong> 和 <strong>輸出大小設定</strong>。</li></ul><h2 id="⚠️-注意事項"><a href="#⚠️-注意事項" class="headerlink" title="⚠️ 注意事項"></a>⚠️ 注意事項</h2><ul><li>四個角點座標必須正確，否則校正結果會失真。</li><li>建議搭配 <strong>特徵點偵測</strong> 或 <strong>邊緣檢測</strong> 自動取得角點。</li><li>輸出大小需根據目標矩形設定，否則可能造成拉伸。</li></ul><h2 id="📊-應用場景"><a href="#📊-應用場景" class="headerlink" title="📊 應用場景"></a>📊 應用場景</h2><ul><li><strong>文件掃描校正</strong>：將拍攝的傾斜文件校正成正矩形，方便 OCR 辨識。</li><li><strong>投影幕校正</strong>：修正投影畫面傾斜，讓顯示內容保持比例。</li><li><strong>場景重建</strong>：將相機拍攝的斜角場景轉換成俯視視角。</li><li><strong>AR 應用</strong>：將虛擬物件正確投影到真實場景中。</li></ul><h2 id="🎯-結語"><a href="#🎯-結語" class="headerlink" title="🎯 結語"></a>🎯 結語</h2><p>本篇我們學會了 <strong>透視變換與圖片校正</strong>，能將傾斜或變形的圖片轉換成正常視角。<br>這些技巧在 <strong>文件掃描、投影校正、場景重建、AR</strong> 等領域非常常見。</p><p>📖 如在學習過程中遇到疑問，或是想了解更多相關主題，建議回顧一下 <a href="/python-opencv-20260106-python-opencv-index"><strong>Python | OpenCV 系列導讀</strong></a>，掌握完整的章節目錄，方便快速找到你需要的內容。<br><br></p><blockquote><p>註：以上參考了<br><a href="https://docs.opencv.org/4.x/d9/df8/tutorial_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV Tutorials</a><br><a href="https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV-Python Tutorials</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;📚-前言&quot;&gt;&lt;a href=&quot;#📚-前言&quot; class=&quot;headerlink&quot; title=&quot;📚 前言&quot;&gt;&lt;/a&gt;📚 前言&lt;/h2&gt;&lt;p&gt;在前一篇我們學會了 &lt;strong&gt;特徵點偵測與匹配&lt;/strong&gt;。&lt;br&gt;這一篇要介紹 &lt;strong&gt;透視變
      
    
    </summary>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/categories/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/"/>
    
      <category term="05.特徵與進階篇" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/05-%E7%89%B9%E5%BE%B5%E8%88%87%E9%80%B2%E9%9A%8E%E7%AF%87/"/>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/tags/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/tags/OpenCV/"/>
    
  </entry>
  
  <entry>
    <title>Python | OpenCV 特徵點偵測與匹配</title>
    <link href="https://morosedog.gitlab.io/python-opencv-20260207-python-opencv-features/"/>
    <id>https://morosedog.gitlab.io/python-opencv-20260207-python-opencv-features/</id>
    <published>2026-02-07T01:00:00.000Z</published>
    <updated>2026-03-02T01:23:05.768Z</updated>
    
    <content type="html"><![CDATA[<h2 id="📚-前言"><a href="#📚-前言" class="headerlink" title="📚 前言"></a>📚 前言</h2><p>在前一篇我們學會了 <strong>直方圖均衡化與圖片增強</strong>。<br>這一篇要介紹 <strong>特徵點偵測 (Feature Detection)</strong> 與 <strong>特徵匹配 (Feature Matching)</strong>，包含常見的 <strong>SIFT、ORB</strong> 演算法，以及 <strong>BFMatcher、FLANN</strong> 匹配方法，這些技巧在圖片比對、物件辨識、拼接 (Stitching) 等應用中非常重要。</p><h2 id="🎨-範例圖片"><a href="#🎨-範例圖片" class="headerlink" title="🎨 範例圖片"></a>🎨 範例圖片</h2><p>在進行特徵匹配之前，我們需要兩張圖片：</p><ul><li><strong>test1.png</strong>：原始圖片（這裡使用部落格頭像 <code>test.png</code>）</li><li><strong>test2.png</strong>：經過縮小或旋轉的版本，用來模擬真實場景中的角度或大小差異。</li></ul><h3 id="💻-範例程式-—-產生-test1-png-與-test2-png"><a href="#💻-範例程式-—-產生-test1-png-與-test2-png" class="headerlink" title="💻 範例程式 — 產生 test1.png 與 test2.png"></a>💻 範例程式 — 產生 test1.png 與 test2.png</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line"><span class="comment"># 讀取原始圖片 (部落格頭像)</span></span><br><span class="line">img = cv2.imread(<span class="string">"test.png"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 儲存原始圖片為 test1.png</span></span><br><span class="line">cv2.imwrite(<span class="string">"test1.png"</span>, img)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 縮小</span></span><br><span class="line">small = cv2.resize(img, <span class="literal">None</span>, fx=<span class="number">0.7</span>, fy=<span class="number">0.7</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 旋轉</span></span><br><span class="line">(h, w) = img.shape[:<span class="number">2</span>]</span><br><span class="line">M = cv2.getRotationMatrix2D((w//<span class="number">2</span>, h//<span class="number">2</span>), <span class="number">30</span>, <span class="number">1.0</span>)</span><br><span class="line">rotated = cv2.warpAffine(img, M, (w, h))</span><br><span class="line"></span><br><span class="line"><span class="comment"># 透視變換 (模擬角度改變)</span></span><br><span class="line">pts1 = np.float32([[<span class="number">0</span>,<span class="number">0</span>],[w,<span class="number">0</span>],[<span class="number">0</span>,h],[w,h]])</span><br><span class="line">pts2 = np.float32([[<span class="number">50</span>,<span class="number">50</span>],[w<span class="number">-50</span>,<span class="number">30</span>],[<span class="number">30</span>,h<span class="number">-50</span>],[w<span class="number">-30</span>,h<span class="number">-30</span>]])</span><br><span class="line">M = cv2.getPerspectiveTransform(pts1, pts2)</span><br><span class="line">warped = cv2.warpPerspective(img, M, (w, h))</span><br><span class="line"></span><br><span class="line"><span class="comment"># 儲存其中一個版本作為 test2.png</span></span><br><span class="line">cv2.imwrite(<span class="string">"test2.png"</span>, rotated)</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/features/test1.png" alt><br><em>圖：原始圖片 test1.png (部落格頭像)</em></p><p><img loading="lazy" src="/images/python/opencv/features/test2.png" alt><br><em>圖：旋轉後的圖片 test2.png，用於特徵匹配測試</em></p><h2 id="🔎-SIFT-Scale-Invariant-Feature-Transform"><a href="#🔎-SIFT-Scale-Invariant-Feature-Transform" class="headerlink" title="🔎 SIFT (Scale-Invariant Feature Transform)"></a>🔎 SIFT (Scale-Invariant Feature Transform)</h2><h3 id="原理說明"><a href="#原理說明" class="headerlink" title="原理說明"></a>原理說明</h3><ul><li>SIFT 能偵測圖片中的 <strong>關鍵點 (Keypoints)</strong>，並計算其 <strong>描述子 (Descriptors)</strong>。</li><li>特點是對 <strong>縮放、旋轉、亮度變化</strong> 具有不變性。</li><li>適合用於精準的圖片匹配。</li></ul><h3 id="參數說明"><a href="#參數說明" class="headerlink" title="參數說明"></a>參數說明</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cv2.SIFT_create(nfeatures=<span class="number">0</span>, nOctaveLayers=<span class="number">3</span>, contrastThreshold=<span class="number">0.04</span>, edgeThreshold=<span class="number">10</span>, sigma=<span class="number">1.6</span>)</span><br></pre></td></tr></table></figure><ul><li><strong>nfeatures</strong>：最多保留的特徵點數量，0 表示不限制。</li><li><strong>nOctaveLayers</strong>：金字塔層數，預設 3。</li><li><strong>contrastThreshold</strong>：對比度閾值，過低的特徵點會被忽略。</li><li><strong>edgeThreshold</strong>：邊緣閾值，過強的邊緣點會被忽略。</li><li><strong>sigma</strong>：高斯模糊的標準差。</li></ul><h3 id="💻-範例程式"><a href="#💻-範例程式" class="headerlink" title="💻 範例程式"></a>💻 範例程式</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line">img = cv2.imread(<span class="string">"test1.png"</span>)</span><br><span class="line">gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)</span><br><span class="line"></span><br><span class="line">sift = cv2.SIFT_create()</span><br><span class="line">keypoints, descriptors = sift.detectAndCompute(gray, <span class="literal">None</span>)</span><br><span class="line"></span><br><span class="line">sift_img = cv2.drawKeypoints(img, keypoints, <span class="literal">None</span>, flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)</span><br><span class="line"></span><br><span class="line">cv2.imshow(<span class="string">"SIFT Keypoints"</span>, sift_img)</span><br><span class="line">cv2.waitKey(<span class="number">0</span>)</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/features/01.png" alt><br><em>圖：程式碼執行結果 — SIFT 偵測到的特徵點</em></p><h2 id="🔎-ORB-Oriented-FAST-and-Rotated-BRIEF"><a href="#🔎-ORB-Oriented-FAST-and-Rotated-BRIEF" class="headerlink" title="🔎 ORB (Oriented FAST and Rotated BRIEF)"></a>🔎 ORB (Oriented FAST and Rotated BRIEF)</h2><h3 id="原理說明-1"><a href="#原理說明-1" class="headerlink" title="原理說明"></a>原理說明</h3><ul><li>ORB 是 <strong>FAST + BRIEF</strong> 的改良版，速度快且效果好。</li><li>特點是 <strong>免費、輕量</strong>，適合即時應用。</li><li>對旋轉與縮放有一定不變性。</li></ul><h3 id="參數說明-1"><a href="#參數說明-1" class="headerlink" title="參數說明"></a>參數說明</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cv2.ORB_create(nfeatures=<span class="number">500</span>)</span><br></pre></td></tr></table></figure><ul><li><strong>nfeatures</strong>：最多保留的特徵點數量。</li></ul><h3 id="💻-範例程式-1"><a href="#💻-範例程式-1" class="headerlink" title="💻 範例程式"></a>💻 範例程式</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line">img = cv2.imread(<span class="string">"test1.png"</span>)</span><br><span class="line">gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)</span><br><span class="line"></span><br><span class="line">orb = cv2.ORB_create(nfeatures=<span class="number">500</span>)</span><br><span class="line">keypoints, descriptors = orb.detectAndCompute(gray, <span class="literal">None</span>)</span><br><span class="line"></span><br><span class="line">orb_img = cv2.drawKeypoints(img, keypoints, <span class="literal">None</span>, color=(<span class="number">0</span>,<span class="number">255</span>,<span class="number">0</span>), flags=<span class="number">0</span>)</span><br><span class="line"></span><br><span class="line">cv2.imshow(<span class="string">"ORB Keypoints"</span>, orb_img)</span><br><span class="line">cv2.waitKey(<span class="number">0</span>)</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/features/02.png" alt><br><em>圖：程式碼執行結果 — ORB 偵測到的特徵點</em></p><h2 id="🔎-特徵匹配-—-BFMatcher"><a href="#🔎-特徵匹配-—-BFMatcher" class="headerlink" title="🔎 特徵匹配 — BFMatcher"></a>🔎 特徵匹配 — BFMatcher</h2><h3 id="原理說明-2"><a href="#原理說明-2" class="headerlink" title="原理說明"></a>原理說明</h3><ul><li><strong>BFMatcher (Brute-Force Matcher)</strong> 逐一比較描述子，找出最佳匹配。  </li><li>適合小型資料集，簡單直接。</li></ul><h3 id="參數說明-2"><a href="#參數說明-2" class="headerlink" title="參數說明"></a>參數說明</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cv2.BFMatcher(normType=cv2.NORM_L2, crossCheck=<span class="literal">False</span>)</span><br></pre></td></tr></table></figure><ul><li><strong>normType</strong>：距離度量方式，SIFT 用 <code>cv2.NORM_L2</code>，ORB 用 <code>cv2.NORM_HAMMING</code>。</li><li><strong>crossCheck</strong>：是否雙向檢查匹配，預設 False。</li></ul><h3 id="💻-範例程式-2"><a href="#💻-範例程式-2" class="headerlink" title="💻 範例程式"></a>💻 範例程式</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line">img1 = cv2.imread(<span class="string">"test1.png"</span>, cv2.IMREAD_GRAYSCALE)</span><br><span class="line">img2 = cv2.imread(<span class="string">"test2.png"</span>, cv2.IMREAD_GRAYSCALE)</span><br><span class="line"></span><br><span class="line">sift = cv2.SIFT_create()</span><br><span class="line">kp1, des1 = sift.detectAndCompute(img1, <span class="literal">None</span>)</span><br><span class="line">kp2, des2 = sift.detectAndCompute(img2, <span class="literal">None</span>)</span><br><span class="line"></span><br><span class="line">bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=<span class="literal">True</span>)</span><br><span class="line">matches = bf.match(des1, des2)</span><br><span class="line">matches = sorted(matches, key=<span class="keyword">lambda</span> x:x.distance)</span><br><span class="line"></span><br><span class="line">match_img = cv2.drawMatches(img1, kp1, img2, kp2, matches[:<span class="number">20</span>], <span class="literal">None</span>, flags=<span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">cv2.imshow(<span class="string">"BFMatcher"</span>, match_img)</span><br><span class="line">cv2.waitKey(<span class="number">0</span>)</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/features/03.png" alt><br><em>圖：程式碼執行結果 — BFMatcher 匹配結果</em></p><h2 id="🔎-特徵匹配-—-FLANN"><a href="#🔎-特徵匹配-—-FLANN" class="headerlink" title="🔎 特徵匹配 — FLANN"></a>🔎 特徵匹配 — FLANN</h2><h3 id="原理說明-3"><a href="#原理說明-3" class="headerlink" title="原理說明"></a>原理說明</h3><ul><li><strong>FLANN (Fast Library for Approximate Nearest Neighbors)</strong> 適合大型資料集。</li><li>使用近似最近鄰演算法，比 BFMatcher 更快。</li><li>常用於圖片拼接、場景比對等需要大量匹配的應用。</li></ul><h3 id="參數說明-3"><a href="#參數說明-3" class="headerlink" title="參數說明"></a>參數說明</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cv2.FlannBasedMatcher(indexParams, searchParams)</span><br></pre></td></tr></table></figure><ul><li><strong>indexParams</strong>：索引參數，例如 <code>dict(algorithm=1, trees=5)</code>。</li><li><strong>searchParams</strong>：搜尋參數，例如 <code>dict(checks=50)</code>。</li></ul><h3 id="💻-範例程式-3"><a href="#💻-範例程式-3" class="headerlink" title="💻 範例程式"></a>💻 範例程式</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line">img1 = cv2.imread(<span class="string">"test1.png"</span>, cv2.IMREAD_GRAYSCALE)</span><br><span class="line">img2 = cv2.imread(<span class="string">"test2.png"</span>, cv2.IMREAD_GRAYSCALE)</span><br><span class="line"></span><br><span class="line">sift = cv2.SIFT_create()</span><br><span class="line">kp1, des1 = sift.detectAndCompute(img1, <span class="literal">None</span>)</span><br><span class="line">kp2, des2 = sift.detectAndCompute(img2, <span class="literal">None</span>)</span><br><span class="line"></span><br><span class="line">index_params = dict(algorithm=<span class="number">1</span>, trees=<span class="number">5</span>)</span><br><span class="line">search_params = dict(checks=<span class="number">50</span>)</span><br><span class="line">flann = cv2.FlannBasedMatcher(index_params, search_params)</span><br><span class="line"></span><br><span class="line">matches = flann.knnMatch(des1, des2, k=<span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">good = []</span><br><span class="line"><span class="keyword">for</span> m, n <span class="keyword">in</span> matches:</span><br><span class="line">    <span class="keyword">if</span> m.distance &lt; <span class="number">0.7</span> * n.distance:</span><br><span class="line">        good.append(m)</span><br><span class="line"></span><br><span class="line">match_img = cv2.drawMatches(img1, kp1, img2, kp2, good, <span class="literal">None</span>, flags=<span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">cv2.imshow(<span class="string">"FLANN Matcher"</span>, match_img)</span><br><span class="line">cv2.waitKey(<span class="number">0</span>)</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/features/04.png" alt><br><em>圖：程式碼執行結果 — FLANN 匹配結果</em></p><h2 id="📊-效果比較"><a href="#📊-效果比較" class="headerlink" title="📊 效果比較"></a>📊 效果比較</h2><table><thead><tr><th>方法</th><th>特點</th><th>適用情境</th></tr></thead><tbody><tr><td>SIFT</td><td>精準、對縮放旋轉不變</td><td>高精度圖片比對</td></tr><tr><td>ORB</td><td>快速、免費、輕量</td><td>即時應用、嵌入式系統</td></tr><tr><td>BFMatcher</td><td>暴力匹配，簡單直接</td><td>小型資料集</td></tr><tr><td>FLANN</td><td>近似最近鄰，速度快</td><td>大型資料集</td></tr></tbody></table><h2 id="🚀-應用場景"><a href="#🚀-應用場景" class="headerlink" title="🚀 應用場景"></a>🚀 應用場景</h2><p>特徵點偵測與匹配雖然看起來抽象，但在電腦視覺中非常常見，以下是幾個實際用途：</p><ul><li><strong>圖片拼接 (Image Stitching)</strong>：把多張照片自動拼接成全景圖，常見於手機相機的「全景模式」。</li><li><strong>物件辨識 (Object Recognition)</strong>：偵測並辨識特定物件，例如商標、書籍封面、地標建築。</li><li><strong>場景比對 (Scene Matching)</strong>：在不同角度或光線下拍攝的照片中，找出相同的場景，常用於 AR 或機器人定位。</li><li><strong>文件比對 (Document Matching)</strong>：比對掃描文件或照片，確認是否為同一份文件，即使有縮放或旋轉。</li><li><strong>追蹤與定位 (Tracking &amp; Localization)</strong>：在影片中追蹤某個物件，或在地圖上定位相機位置，常用於無人機或機器人導航。</li><li><strong>3D 重建 (3D Reconstruction)</strong>：從多張不同角度的照片中，找出共同特徵點，進而重建三維模型。</li></ul><h2 id="⚠️-注意事項"><a href="#⚠️-注意事項" class="headerlink" title="⚠️ 注意事項"></a>⚠️ 注意事項</h2><ul><li>SIFT 在部分 OpenCV 版本需額外安裝 <code>opencv-contrib-python</code>。</li><li>ORB 適合快速應用，但精度略低於 SIFT。</li><li>BFMatcher 適合小型資料集，FLANN 適合大型資料集。</li><li>特徵匹配常搭配 <strong>RANSAC</strong> 過濾錯誤匹配，提升準確度。</li></ul><h2 id="🎯-結語"><a href="#🎯-結語" class="headerlink" title="🎯 結語"></a>🎯 結語</h2><p>本篇我們學會了 <strong>SIFT、ORB 特徵點偵測</strong>，以及 <strong>BFMatcher、FLANN 特徵匹配</strong>，能找出圖片中的關鍵點並進行比對。<br>這些技巧在 <strong>圖片拼接、物件辨識、場景比對、AR、導航、3D 重建</strong> 等領域都有廣泛應用。</p><p>📖 如在學習過程中遇到疑問，或是想了解更多相關主題，建議回顧一下 <a href="/python-opencv-20260106-python-opencv-index"><strong>Python | OpenCV 系列導讀</strong></a>，掌握完整的章節目錄，方便快速找到你需要的內容。<br><br></p><blockquote><p>註：以上參考了<br><a href="https://docs.opencv.org/4.x/d9/df8/tutorial_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV Tutorials</a><br><a href="https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV-Python Tutorials</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;📚-前言&quot;&gt;&lt;a href=&quot;#📚-前言&quot; class=&quot;headerlink&quot; title=&quot;📚 前言&quot;&gt;&lt;/a&gt;📚 前言&lt;/h2&gt;&lt;p&gt;在前一篇我們學會了 &lt;strong&gt;直方圖均衡化與圖片增強&lt;/strong&gt;。&lt;br&gt;這一篇要介紹 &lt;strong&gt;
      
    
    </summary>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/categories/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/"/>
    
      <category term="05.特徵與進階篇" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/05-%E7%89%B9%E5%BE%B5%E8%88%87%E9%80%B2%E9%9A%8E%E7%AF%87/"/>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/tags/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/tags/OpenCV/"/>
    
  </entry>
  
  <entry>
    <title>Python | OpenCV 直方圖均衡化與圖片增強</title>
    <link href="https://morosedog.gitlab.io/python-opencv-20260206-python-opencv-histogram/"/>
    <id>https://morosedog.gitlab.io/python-opencv-20260206-python-opencv-histogram/</id>
    <published>2026-02-06T01:00:00.000Z</published>
    <updated>2026-03-02T01:23:05.768Z</updated>
    
    <content type="html"><![CDATA[<h2 id="📚-前言"><a href="#📚-前言" class="headerlink" title="📚 前言"></a>📚 前言</h2><p>在前一篇我們學會了 <strong>直線與圓形偵測 (霍夫變換)</strong>。<br>這一篇要介紹 <strong>直方圖均衡化 (Histogram Equalization)</strong> 與 <strong>圖片增強 (Image Enhancement)</strong>，包含 <strong>全域均衡化、CLAHE 自適應均衡化、彩色圖片均衡化</strong> 三種方法，這是圖片前處理的重要技巧，能改善圖片對比度，讓細節更清晰，常用於夜間圖片、醫學圖片或文件辨識。</p><h2 id="🎨-範例圖片"><a href="#🎨-範例圖片" class="headerlink" title="🎨 範例圖片"></a>🎨 範例圖片</h2><p><img loading="lazy" src="/images/python/opencv/histogram/dark.png" alt><br><em>圖：範例圖片 — 灰階暗圖，用於均衡化測試</em><br><img loading="lazy" src="/images/python/opencv/histogram/color.png" alt><br><em>圖：範例圖片 — 彩色暗圖，用於 Y 通道均衡化測試</em></p><h2 id="🔎-全域直方圖均衡化"><a href="#🔎-全域直方圖均衡化" class="headerlink" title="🔎 全域直方圖均衡化"></a>🔎 全域直方圖均衡化</h2><h3 id="原理說明"><a href="#原理說明" class="headerlink" title="原理說明"></a>原理說明</h3><ul><li>將圖片的灰階值重新分配，使亮度分布更均勻。</li><li>適合整體偏暗或偏亮的圖片。</li><li>OpenCV 提供 <code>cv2.equalizeHist()</code>。</li></ul><h3 id="參數說明"><a href="#參數說明" class="headerlink" title="參數說明"></a>參數說明</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cv2.equalizeHist(src)</span><br></pre></td></tr></table></figure><ul><li><strong>src</strong>：輸入圖片，必須是單通道灰階 (<code>uint8</code>)。</li><li><strong>回傳值</strong>：均衡化後的灰階圖片。</li></ul><blockquote><p>📌 注意：無法直接處理彩色圖片，需先轉換色彩空間。</p></blockquote><h3 id="💻-範例程式"><a href="#💻-範例程式" class="headerlink" title="💻 範例程式"></a>💻 範例程式</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line"><span class="comment"># 讀取灰階圖片</span></span><br><span class="line">img = cv2.imread(<span class="string">"dark.png"</span>, cv2.IMREAD_GRAYSCALE)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 直方圖均衡化</span></span><br><span class="line">equalized = cv2.equalizeHist(img)</span><br><span class="line"></span><br><span class="line">cv2.imshow(<span class="string">"Original"</span>, img)</span><br><span class="line">cv2.imshow(<span class="string">"Equalized"</span>, equalized)</span><br><span class="line">cv2.waitKey(<span class="number">0</span>)</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/histogram/01.png" alt><br><em>圖：程式碼執行結果 — 灰階圖片均衡化，對比度提升</em></p><h2 id="🔎-CLAHE-自適應直方圖均衡化"><a href="#🔎-CLAHE-自適應直方圖均衡化" class="headerlink" title="🔎 CLAHE (自適應直方圖均衡化)"></a>🔎 CLAHE (自適應直方圖均衡化)</h2><h3 id="原理說明-1"><a href="#原理說明-1" class="headerlink" title="原理說明"></a>原理說明</h3><ul><li>將圖片分成小區塊，分別均衡化後再合併。</li><li>適合局部亮度差異大的圖片。 </li><li>可避免全域均衡化造成雜訊過度增強。</li></ul><h3 id="參數說明-1"><a href="#參數說明-1" class="headerlink" title="參數說明"></a>參數說明</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cv2.createCLAHE(clipLimit=<span class="number">2.0</span>, tileGridSize=(<span class="number">8</span>,<span class="number">8</span>))</span><br></pre></td></tr></table></figure><ul><li><strong>clipLimit</strong>：對比度限制值，防止過度增強。數值越大，對比度越強。</li><li><strong>tileGridSize</strong>：分割區塊大小，例如 <code>(8,8)</code> 表示分成 8x8 區塊。</li></ul><h3 id="💻-範例程式-1"><a href="#💻-範例程式-1" class="headerlink" title="💻 範例程式"></a>💻 範例程式</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line"><span class="comment"># 讀取灰階圖片</span></span><br><span class="line">img = cv2.imread(<span class="string">"dark.png"</span>, cv2.IMREAD_GRAYSCALE)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 建立 CLAHE 物件</span></span><br><span class="line">clahe = cv2.createCLAHE(clipLimit=<span class="number">50.0</span>, tileGridSize=(<span class="number">8</span>,<span class="number">8</span>))</span><br><span class="line"></span><br><span class="line"><span class="comment"># 套用 CLAHE</span></span><br><span class="line">clahe_img = clahe.apply(img)</span><br><span class="line"></span><br><span class="line">cv2.imshow(<span class="string">"Original"</span>, img)</span><br><span class="line">cv2.imshow(<span class="string">"CLAHE"</span>, clahe_img)</span><br><span class="line">cv2.waitKey(<span class="number">0</span>)</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/histogram/02.png" alt><br><em>圖：程式碼執行結果 — CLAHE 增強效果，局部對比度提升</em></p><h2 id="🔎-彩色圖片均衡化"><a href="#🔎-彩色圖片均衡化" class="headerlink" title="🔎 彩色圖片均衡化"></a>🔎 彩色圖片均衡化</h2><h3 id="原理說明-2"><a href="#原理說明-2" class="headerlink" title="原理說明"></a>原理說明</h3><ul><li>彩色圖片不能直接均衡化，否則會破壞色彩。</li><li>正確做法是轉換到 <strong>YCrCb 色彩空間</strong>，只均衡化亮度通道 (Y)。</li></ul><h3 id="參數說明-2"><a href="#參數說明-2" class="headerlink" title="參數說明"></a>參數說明</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></pre></td><td class="code"><pre><span class="line">cv2.cvtColor(src, cv2.COLOR_BGR2YCrCb)</span><br><span class="line">cv2.equalizeHist(y_channel)</span><br><span class="line">cv2.cvtColor(ycrcb, cv2.COLOR_YCrCb2BGR)</span><br></pre></td></tr></table></figure><ul><li><strong>cv2.cvtColor</strong>：轉換色彩空間。</li><li><strong>y_channel</strong>：Y 通道（亮度），只對這一通道做均衡化。</li><li><strong>回傳值</strong>：保留色彩的均衡化圖片。</li></ul><h3 id="💻-範例程式-2"><a href="#💻-範例程式-2" class="headerlink" title="💻 範例程式"></a>💻 範例程式</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line"><span class="comment"># 讀取彩色圖片</span></span><br><span class="line">img = cv2.imread(<span class="string">"color.png"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 轉換到 YCrCb</span></span><br><span class="line">ycrcb = cv2.cvtColor(img, cv2.COLOR_BGR2YCrCb)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 均衡化 Y 通道</span></span><br><span class="line">ycrcb[:,:,<span class="number">0</span>] = cv2.equalizeHist(ycrcb[:,:,<span class="number">0</span>])</span><br><span class="line"></span><br><span class="line"><span class="comment"># 轉回 BGR</span></span><br><span class="line">equalized_color = cv2.cvtColor(ycrcb, cv2.COLOR_YCrCb2BGR)</span><br><span class="line"></span><br><span class="line">cv2.imshow(<span class="string">"Original"</span>, img)</span><br><span class="line">cv2.imshow(<span class="string">"Equalized Color"</span>, equalized_color)</span><br><span class="line">cv2.waitKey(<span class="number">0</span>)</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/histogram/03.png" alt><br><em>圖：程式碼執行結果 — 彩色圖片均衡化，亮度提升但色彩保持不變</em></p><h2 id="📊-效果比較"><a href="#📊-效果比較" class="headerlink" title="📊 效果比較"></a>📊 效果比較</h2><table><thead><tr><th>方法</th><th>特點</th><th>適用情境</th></tr></thead><tbody><tr><td><code>equalizeHist()</code></td><td>全域均衡化，提升整體對比度</td><td>適合灰階圖片，整體偏暗或偏亮</td></tr><tr><td>CLAHE</td><td>局部均衡化，避免過度增強</td><td>適合醫學圖片、夜間圖片、局部亮度差異大</td></tr><tr><td>彩色均衡化 (Y 通道)</td><td>保留色彩，只增強亮度</td><td>適合彩色照片，避免色偏</td></tr></tbody></table><h2 id="⚠️-注意事項"><a href="#⚠️-注意事項" class="headerlink" title="⚠️ 注意事項"></a>⚠️ 注意事項</h2><ul><li>直方圖均衡化主要用於 <strong>灰階圖片</strong>，彩色圖片需轉換到 YCrCb 或 HSV，只處理亮度通道。</li><li>CLAHE 的 <code>clipLimit</code> 與 <code>tileGridSize</code> 需要依圖片調整，過大可能造成過度增強。</li><li>均衡化雖能改善對比度，但也可能放大雜訊，需搭配濾波或其他前處理。</li></ul><h2 id="🎯-結語"><a href="#🎯-結語" class="headerlink" title="🎯 結語"></a>🎯 結語</h2><p>本篇我們學會了 <strong>直方圖均衡化</strong>、<strong>CLAHE 自適應均衡化</strong> 以及 <strong>彩色圖片均衡化</strong>，能有效改善圖片對比度，讓細節更清晰。<br>這些技巧在圖片前處理中非常常見，尤其在 <strong>夜間圖片、醫學圖片、文件辨識</strong> 等場景中非常有用。</p><p>📖 如在學習過程中遇到疑問，或是想了解更多相關主題，建議回顧一下 <a href="/python-opencv-20260106-python-opencv-index"><strong>Python | OpenCV 系列導讀</strong></a>，掌握完整的章節目錄，方便快速找到你需要的內容。<br><br></p><blockquote><p>註：以上參考了<br><a href="https://docs.opencv.org/4.x/d9/df8/tutorial_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV Tutorials</a><br><a href="https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV-Python Tutorials</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;📚-前言&quot;&gt;&lt;a href=&quot;#📚-前言&quot; class=&quot;headerlink&quot; title=&quot;📚 前言&quot;&gt;&lt;/a&gt;📚 前言&lt;/h2&gt;&lt;p&gt;在前一篇我們學會了 &lt;strong&gt;直線與圓形偵測 (霍夫變換)&lt;/strong&gt;。&lt;br&gt;這一篇要介紹 &lt;stro
      
    
    </summary>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/categories/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/"/>
    
      <category term="05.特徵與進階篇" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/05-%E7%89%B9%E5%BE%B5%E8%88%87%E9%80%B2%E9%9A%8E%E7%AF%87/"/>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/tags/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/tags/OpenCV/"/>
    
  </entry>
  
  <entry>
    <title>Python | OpenCV 直線與圓形偵測 (霍夫變換)</title>
    <link href="https://morosedog.gitlab.io/python-opencv-20260205-python-opencv-hough/"/>
    <id>https://morosedog.gitlab.io/python-opencv-20260205-python-opencv-hough/</id>
    <published>2026-02-05T01:00:00.000Z</published>
    <updated>2026-03-02T01:23:05.767Z</updated>
    
    <content type="html"><![CDATA[<h2 id="📚-前言"><a href="#📚-前言" class="headerlink" title="📚 前言"></a>📚 前言</h2><p>在前一篇我們學會了 <strong>邊緣檢測與輪廓分析</strong>。<br>這一篇要介紹 <strong>霍夫變換 (Hough Transform)</strong>，它是一種常見的幾何特徵偵測方法，能有效找出圖片中的 <strong>直線</strong> 與 <strong>圓形</strong>。<br>這些技巧廣泛應用於車道線偵測、圓形物件辨識、工業檢測等場景。</p><h2 id="🎨-範例圖片"><a href="#🎨-範例圖片" class="headerlink" title="🎨 範例圖片"></a>🎨 範例圖片</h2><p><img loading="lazy" src="/images/python/opencv/hough/lines.png" alt><br><em>圖：範例圖片 lines.png — 含有多條直線</em></p><p><img loading="lazy" src="/images/python/opencv/hough/circles.png" alt><br><em>圖：範例圖片 circles.png — 含有多個圓形</em></p><p><img loading="lazy" src="/images/python/opencv/hough/suzuka.png" alt><br><em>圖：範例圖片 suzuka.png — 賽車道場景 (來源：<a href="https://www.gameapps.hk/news/41628/MARIOKART-LIVE-HOME-CIRCUIT" target="_blank" rel="external nofollow noopener noreferrer">GameApps</a>)</em></p><p><img loading="lazy" src="/images/python/opencv/hough/cans.png" alt><br><em>圖：範例圖片 cans.png — 罐頭俯瞰圖 (來源：<a href="https://www.techbang.com/posts/121069-dots-concentric-circles-can" target="_blank" rel="external nofollow noopener noreferrer">Techbang</a>)</em></p><h2 id="🔎-霍夫變換原理"><a href="#🔎-霍夫變換原理" class="headerlink" title="🔎 霍夫變換原理"></a>🔎 霍夫變換原理</h2><h3 id="原理說明"><a href="#原理說明" class="headerlink" title="原理說明"></a>原理說明</h3><ul><li><strong>直線霍夫變換</strong>：將圖片中的每個邊緣點轉換到參數空間 (ρ, θ)，找出符合直線方程的點群。</li><li><strong>圓形霍夫變換</strong>：利用邊緣點與圓心半徑的關係，在參數空間中找出可能的圓形。</li><li>OpenCV 提供了 <code>cv2.HoughLines()</code> 與 <code>cv2.HoughCircles()</code> 兩個函式。</li></ul><h2 id="🧠-函式與參數說明"><a href="#🧠-函式與參數說明" class="headerlink" title="🧠 函式與參數說明"></a>🧠 函式與參數說明</h2><h3 id="📌-cv2-HoughLines"><a href="#📌-cv2-HoughLines" class="headerlink" title="📌 cv2.HoughLines()"></a>📌 <code>cv2.HoughLines()</code></h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cv2.HoughLines(image, rho, theta, threshold)</span><br></pre></td></tr></table></figure><ul><li><strong>image</strong>：邊緣檢測後的二值化圖片 (通常用 Canny)。</li><li><strong>rho</strong>：距離解析度，單位像素，常用值 = 1。</li><li><strong>theta</strong>：角度解析度，單位弧度，常用值 = np.pi/180 (即 1 度)。</li><li><strong>threshold</strong>：累加器閾值，至少多少個點支持才能判定為直線。</li></ul><hr><h3 id="📌-cv2-HoughCircles"><a href="#📌-cv2-HoughCircles" class="headerlink" title="📌 cv2.HoughCircles()"></a>📌 <code>cv2.HoughCircles()</code></h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cv2.HoughCircles(image, method, dp, minDist, param1, param2, minRadius, maxRadius)</span><br></pre></td></tr></table></figure><ul><li><strong>image</strong>：灰階圖片，建議先模糊處理。</li><li><strong>method</strong>：偵測方法，常用 <code>cv2.HOUGH_GRADIENT</code>。</li><li><strong>dp</strong>：累加器解析度與圖片解析度的反比。dp=1 表示相同解析度。</li><li><strong>minDist</strong>：不同圓心之間的最小距離。</li><li><strong>param1</strong>：Canny 邊緣檢測的高閾值。</li><li><strong>param2</strong>：累加器閾值，數值越小偵測越敏感。</li><li><strong>minRadius</strong>：最小半徑。</li><li><strong>maxRadius</strong>：最大半徑。</li></ul><h2 id="💻-範例程式-—-直線偵測"><a href="#💻-範例程式-—-直線偵測" class="headerlink" title="💻 範例程式 — 直線偵測"></a>💻 範例程式 — 直線偵測</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line">img = cv2.imread(<span class="string">"lines.png"</span>)</span><br><span class="line">cv2.imshow(<span class="string">"Original"</span>, img)</span><br><span class="line"></span><br><span class="line">gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)</span><br><span class="line"></span><br><span class="line">edges = cv2.Canny(gray, <span class="number">50</span>, <span class="number">150</span>)</span><br><span class="line">lines = cv2.HoughLines(edges, <span class="number">1</span>, np.pi/<span class="number">180</span>, <span class="number">100</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> lines:</span><br><span class="line">    rho, theta = line[<span class="number">0</span>]</span><br><span class="line">    a = np.cos(theta)</span><br><span class="line">    b = np.sin(theta)</span><br><span class="line">    x0 = a * rho</span><br><span class="line">    y0 = b * rho</span><br><span class="line">    x1 = int(x0 + <span class="number">1000</span> * (-b))</span><br><span class="line">    y1 = int(y0 + <span class="number">1000</span> * (a))</span><br><span class="line">    x2 = int(x0 - <span class="number">1000</span> * (-b))</span><br><span class="line">    y2 = int(y0 - <span class="number">1000</span> * (a))</span><br><span class="line">    cv2.line(img, (x1,y1), (x2,y2), (<span class="number">0</span>,<span class="number">0</span>,<span class="number">255</span>), <span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">cv2.imshow(<span class="string">"Detected Lines"</span>, img)</span><br><span class="line">cv2.waitKey(<span class="number">0</span>)</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/hough/01.png" alt><br><em>圖：程式碼執行結果 — 偵測並標註圖片中的直線</em></p><h2 id="💻-範例程式-—-圓形偵測"><a href="#💻-範例程式-—-圓形偵測" class="headerlink" title="💻 範例程式 — 圓形偵測"></a>💻 範例程式 — 圓形偵測</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line">img = cv2.imread(<span class="string">"circles.png"</span>)</span><br><span class="line">cv2.imshow(<span class="string">"Original"</span>, img)</span><br><span class="line"></span><br><span class="line">gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)</span><br><span class="line">gray = cv2.medianBlur(gray, <span class="number">5</span>)</span><br><span class="line"></span><br><span class="line">circles = cv2.HoughCircles(</span><br><span class="line">    gray, cv2.HOUGH_GRADIENT, dp=<span class="number">1</span>, minDist=<span class="number">50</span>,</span><br><span class="line">    param1=<span class="number">100</span>, param2=<span class="number">30</span>, minRadius=<span class="number">20</span>, maxRadius=<span class="number">100</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> circles <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>:</span><br><span class="line">    circles = np.uint16(np.around(circles))</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> circles[<span class="number">0</span>,:]:</span><br><span class="line">        cv2.circle(img, (i[<span class="number">0</span>], i[<span class="number">1</span>]), i[<span class="number">2</span>], (<span class="number">0</span>,<span class="number">255</span>,<span class="number">0</span>), <span class="number">2</span>)</span><br><span class="line">        cv2.circle(img, (i[<span class="number">0</span>], i[<span class="number">1</span>]), <span class="number">2</span>, (<span class="number">0</span>,<span class="number">0</span>,<span class="number">255</span>), <span class="number">3</span>)</span><br><span class="line"></span><br><span class="line">cv2.imshow(<span class="string">"Detected Circles"</span>, img)</span><br><span class="line">cv2.waitKey(<span class="number">0</span>)</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/hough/02.png" alt><br><em>圖：程式碼執行結果 — 偵測並標註圖片中的圓形</em></p><h2 id="💻-範例程式-—-實際應用：賽車道直線偵測"><a href="#💻-範例程式-—-實際應用：賽車道直線偵測" class="headerlink" title="💻 範例程式 — 實際應用：賽車道直線偵測"></a>💻 範例程式 — 實際應用：賽車道直線偵測</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line">img = cv2.imread(<span class="string">"suzuka.png"</span>)  <span class="comment"># 賽車道圖片</span></span><br><span class="line">cv2.imshow(<span class="string">"Original"</span>, img)</span><br><span class="line"></span><br><span class="line">gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 邊緣檢測</span></span><br><span class="line">edges = cv2.Canny(gray, <span class="number">100</span>, <span class="number">200</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 霍夫直線偵測</span></span><br><span class="line">lines = cv2.HoughLines(edges, <span class="number">1</span>, np.pi/<span class="number">180</span>, <span class="number">120</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> lines <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>:</span><br><span class="line">    <span class="keyword">for</span> line <span class="keyword">in</span> lines:</span><br><span class="line">        rho, theta = line[<span class="number">0</span>]</span><br><span class="line">        a = np.cos(theta)</span><br><span class="line">        b = np.sin(theta)</span><br><span class="line">        x0 = a * rho</span><br><span class="line">        y0 = b * rho</span><br><span class="line">        x1 = int(x0 + <span class="number">1000</span> * (-b))</span><br><span class="line">        y1 = int(y0 + <span class="number">1000</span> * (a))</span><br><span class="line">        x2 = int(x0 - <span class="number">1000</span> * (-b))</span><br><span class="line">        y2 = int(y0 - <span class="number">1000</span> * (a))</span><br><span class="line">        cv2.line(img, (x1,y1), (x2,y2), (<span class="number">0</span>,<span class="number">0</span>,<span class="number">255</span>), <span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">cv2.imshow(<span class="string">"Suzuka Circuit - Lines"</span>, img)</span><br><span class="line">cv2.waitKey(<span class="number">0</span>)</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/hough/03.png" alt><br><em>圖：程式碼執行結果 — 偵測並標註賽車道中的直線</em></p><h2 id="💻-範例程式-—-實際應用：罐頭圓形偵測"><a href="#💻-範例程式-—-實際應用：罐頭圓形偵測" class="headerlink" title="💻 範例程式 — 實際應用：罐頭圓形偵測"></a>💻 範例程式 — 實際應用：罐頭圓形偵測</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line">img = cv2.imread(<span class="string">"cans.png"</span>)  <span class="comment"># 罐頭俯瞰圖</span></span><br><span class="line">cv2.imshow(<span class="string">"Original"</span>, img)</span><br><span class="line"></span><br><span class="line">gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)</span><br><span class="line">gray = cv2.medianBlur(gray, <span class="number">5</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 霍夫圓形偵測</span></span><br><span class="line">circles = cv2.HoughCircles(</span><br><span class="line">    gray, cv2.HOUGH_GRADIENT, dp=<span class="number">1</span>, minDist=<span class="number">100</span>,</span><br><span class="line">    param1=<span class="number">100</span>, param2=<span class="number">50</span>, minRadius=<span class="number">90</span>, maxRadius=<span class="number">110</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> circles <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>:</span><br><span class="line">    circles = np.uint16(np.around(circles))</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> circles[<span class="number">0</span>,:]:</span><br><span class="line">        cv2.circle(img, (i[<span class="number">0</span>], i[<span class="number">1</span>]), i[<span class="number">2</span>], (<span class="number">0</span>,<span class="number">255</span>,<span class="number">0</span>), <span class="number">2</span>)  <span class="comment"># 畫圓</span></span><br><span class="line">        cv2.circle(img, (i[<span class="number">0</span>], i[<span class="number">1</span>]), <span class="number">2</span>, (<span class="number">0</span>,<span class="number">0</span>,<span class="number">255</span>), <span class="number">3</span>)    <span class="comment"># 畫圓心</span></span><br><span class="line"></span><br><span class="line">cv2.imshow(<span class="string">"Cans - Circles"</span>, img)</span><br><span class="line">cv2.waitKey(<span class="number">0</span>)</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/hough/04.png" alt><br><em>圖：程式碼執行結果 — 偵測並標註罐頭的大圓形</em></p><h2 id="⚠️-注意事項"><a href="#⚠️-注意事項" class="headerlink" title="⚠️ 注意事項"></a>⚠️ 注意事項</h2><ul><li><strong>直線偵測</strong>：<code>threshold</code> 數值需依圖片調整，太小會產生雜訊，太大可能漏掉直線。</li><li><strong>圓形偵測</strong>：<code>param1</code> 與 <code>param2</code> 控制靈敏度，需依圖片特性調整。</li><li><strong>前處理</strong>：建議先做邊緣檢測或模糊處理，提升偵測效果。</li></ul><h2 id="📊-應用場景"><a href="#📊-應用場景" class="headerlink" title="📊 應用場景"></a>📊 應用場景</h2><ul><li><strong>車道線偵測</strong>：利用直線霍夫變換偵測道路標線。</li><li><strong>工業檢測</strong>：偵測零件上的圓孔或直線結構。</li><li><strong>醫學圖片</strong>：偵測血管或圓形結構。</li><li><strong>日常應用</strong>：找出圖片中的幾何形狀，輔助後續分析。</li></ul><h2 id="🎯-結語"><a href="#🎯-結語" class="headerlink" title="🎯 結語"></a>🎯 結語</h2><p>本篇我們學會了 <strong>霍夫變換</strong> 的直線與圓形偵測方法，並理解了 <strong>cv2.HoughLines() 與 cv2.HoughCircles()</strong> 的用法，成功在圖片中標註幾何特徵。<br>這些技巧在電腦視覺應用中非常常見，例如車道線偵測、工業檢測與醫學圖片分析。</p><p>📖 如在學習過程中遇到疑問，或是想了解更多相關主題，建議回顧一下 <a href="/python-opencv-20260106-python-opencv-index"><strong>Python | OpenCV 系列導讀</strong></a>，掌握完整的章節目錄，方便快速找到你需要的內容。<br><br></p><blockquote><p>註：以上參考了<br><a href="https://docs.opencv.org/4.x/d9/df8/tutorial_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV Tutorials</a><br><a href="https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV-Python Tutorials</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;📚-前言&quot;&gt;&lt;a href=&quot;#📚-前言&quot; class=&quot;headerlink&quot; title=&quot;📚 前言&quot;&gt;&lt;/a&gt;📚 前言&lt;/h2&gt;&lt;p&gt;在前一篇我們學會了 &lt;strong&gt;邊緣檢測與輪廓分析&lt;/strong&gt;。&lt;br&gt;這一篇要介紹 &lt;strong&gt;霍夫
      
    
    </summary>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/categories/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/"/>
    
      <category term="05.特徵與進階篇" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/05-%E7%89%B9%E5%BE%B5%E8%88%87%E9%80%B2%E9%9A%8E%E7%AF%87/"/>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/tags/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/tags/OpenCV/"/>
    
  </entry>
  
  <entry>
    <title>Python | OpenCV 邊緣檢測與輪廓分析</title>
    <link href="https://morosedog.gitlab.io/python-opencv-20260204-python-opencv-edge-detection/"/>
    <id>https://morosedog.gitlab.io/python-opencv-20260204-python-opencv-edge-detection/</id>
    <published>2026-02-04T01:00:00.000Z</published>
    <updated>2026-03-02T01:23:05.767Z</updated>
    
    <content type="html"><![CDATA[<h2 id="📚-前言"><a href="#📚-前言" class="headerlink" title="📚 前言"></a>📚 前言</h2><p>在前一篇我們學會了 <strong>魔術棒填充顏色</strong>。<br>這一篇要介紹 <strong>邊緣檢測 (Edge Detection)</strong> 與 <strong>輪廓分析 (Contour Analysis)</strong>，包含常見的 <strong>Laplacian、Sobel、Canny</strong> 三種方法，並展示如何利用輪廓分析找出圖片中的物件邊界。</p><h2 id="🔎-Laplacian-邊緣檢測"><a href="#🔎-Laplacian-邊緣檢測" class="headerlink" title="🔎 Laplacian 邊緣檢測"></a>🔎 Laplacian 邊緣檢測</h2><h3 id="原理說明"><a href="#原理說明" class="headerlink" title="原理說明"></a>原理說明</h3><ul><li>Laplacian 是 <strong>二階導數運算</strong>，偵測圖片中灰階值變化最劇烈的地方。</li><li>適合快速找出邊界，但容易受雜訊影響。</li></ul><h3 id="參數說明"><a href="#參數說明" class="headerlink" title="參數說明"></a>參數說明</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cv2.Laplacian(src, ddepth, ksize=<span class="number">1</span>, scale=<span class="number">1</span>, delta=<span class="number">0</span>, borderType=cv2.BORDER_DEFAULT)</span><br></pre></td></tr></table></figure><ul><li><strong>src</strong>：輸入圖片（通常是灰階）。</li><li><strong>ddepth</strong>：輸出圖片的深度，常用 <code>cv2.CV_64F</code> 避免溢位。</li><li><strong>ksize</strong>：卷積核大小，必須為正奇數，預設 1。數值越大，邊緣檢測越平滑。</li><li><strong>scale</strong>：縮放因子，調整計算後的梯度值。</li><li><strong>delta</strong>：加到結果上的偏移量。</li><li><strong>borderType</strong>：邊界處理方式，預設 <code>cv2.BORDER_DEFAULT</code>。</li></ul><h3 id="💻-範例程式"><a href="#💻-範例程式" class="headerlink" title="💻 範例程式"></a>💻 範例程式</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line">img = cv2.imread(<span class="string">"test.png"</span>)  <span class="comment"># 部落格頭像</span></span><br><span class="line">gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)</span><br><span class="line"></span><br><span class="line">laplacian = cv2.Laplacian(gray, cv2.CV_64F)</span><br><span class="line">laplacian = cv2.convertScaleAbs(laplacian)</span><br><span class="line"></span><br><span class="line">cv2.imshow(<span class="string">"Original"</span>, img)</span><br><span class="line">cv2.imshow(<span class="string">"Laplacian"</span>, laplacian)</span><br><span class="line">cv2.waitKey(<span class="number">0</span>)</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/edge-detection/01.png" alt><br><em>圖：程式碼執行結果 — Laplacian 邊緣檢測，顯示灰階邊界</em></p><h2 id="🔎-Sobel-邊緣檢測"><a href="#🔎-Sobel-邊緣檢測" class="headerlink" title="🔎 Sobel 邊緣檢測"></a>🔎 Sobel 邊緣檢測</h2><h3 id="原理說明-1"><a href="#原理說明-1" class="headerlink" title="原理說明"></a>原理說明</h3><ul><li>Sobel 是 <strong>一階導數運算</strong>，分別計算 <strong>水平 (x)</strong> 與 <strong>垂直 (y)</strong> 方向的梯度。</li><li>可以強調某一方向的邊緣，常用於圖片特徵提取。</li></ul><h3 id="參數說明-1"><a href="#參數說明-1" class="headerlink" title="參數說明"></a>參數說明</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cv2.Sobel(src, ddepth, dx, dy, ksize=<span class="number">3</span>, scale=<span class="number">1</span>, delta=<span class="number">0</span>, borderType=cv2.BORDER_DEFAULT)</span><br></pre></td></tr></table></figure><ul><li><strong>src</strong>：輸入圖片（通常是灰階）。</li><li><strong>ddepth</strong>：輸出圖片的深度，常用 <code>cv2.CV_64F</code>。</li><li><strong>dx</strong>：x 方向的導數階數，通常設 1。</li><li><strong>dy</strong>：y 方向的導數階數，通常設 1。</li><li><strong>ksize</strong>：Sobel 核大小，必須為 1、3、5、7。數值越大，邊緣越平滑。</li><li><strong>scale</strong>：縮放因子，調整梯度值。</li><li><strong>delta</strong>：加到結果上的偏移量。</li><li><strong>borderType</strong>：邊界處理方式。</li></ul><h3 id="💻-範例程式-1"><a href="#💻-範例程式-1" class="headerlink" title="💻 範例程式"></a>💻 範例程式</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line">img = cv2.imread(<span class="string">"test.png"</span>)  <span class="comment"># 部落格頭像</span></span><br><span class="line">gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)</span><br><span class="line"></span><br><span class="line">sobelx = cv2.Sobel(gray, cv2.CV_64F, <span class="number">1</span>, <span class="number">0</span>, ksize=<span class="number">3</span>)</span><br><span class="line">sobely = cv2.Sobel(gray, cv2.CV_64F, <span class="number">0</span>, <span class="number">1</span>, ksize=<span class="number">3</span>)</span><br><span class="line"></span><br><span class="line">sobelx = cv2.convertScaleAbs(sobelx)</span><br><span class="line">sobely = cv2.convertScaleAbs(sobely)</span><br><span class="line">sobel = cv2.addWeighted(sobelx, <span class="number">0.5</span>, sobely, <span class="number">0.5</span>, <span class="number">0</span>)</span><br><span class="line"></span><br><span class="line">cv2.imshow(<span class="string">"Sobel X"</span>, sobelx)</span><br><span class="line">cv2.imshow(<span class="string">"Sobel Y"</span>, sobely)</span><br><span class="line">cv2.imshow(<span class="string">"Sobel Combined"</span>, sobel)</span><br><span class="line">cv2.waitKey(<span class="number">0</span>)</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/edge-detection/02.png" alt><br><em>圖：程式碼執行結果 — Sobel 邊緣檢測，分別顯示水平、垂直與合成邊界</em></p><h2 id="🔎-Canny-邊緣檢測"><a href="#🔎-Canny-邊緣檢測" class="headerlink" title="🔎 Canny 邊緣檢測"></a>🔎 Canny 邊緣檢測</h2><h3 id="原理說明-2"><a href="#原理說明-2" class="headerlink" title="原理說明"></a>原理說明</h3><ul><li>Canny 是最常用的邊緣檢測演算法，步驟包含： <ol><li>高斯模糊去雜訊</li><li>計算梯度</li><li>非極大值抑制</li><li>雙閾值判斷</li><li>邊緣連接</li></ol></li><li>結果乾淨且穩定，適合後續輪廓分析。</li></ul><h3 id="參數說明-2"><a href="#參數說明-2" class="headerlink" title="參數說明"></a>參數說明</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cv2.Canny(image, threshold1, threshold2, apertureSize=<span class="number">3</span>, L2gradient=<span class="literal">False</span>)</span><br></pre></td></tr></table></figure><ul><li><strong>image</strong>：輸入圖片（必須是灰階）。</li><li><strong>threshold1</strong>：低閾值，用來判斷弱邊緣。</li><li><strong>threshold2</strong>：高閾值，用來判斷強邊緣。</li><li><strong>apertureSize</strong>：Sobel 核大小，預設 3。 </li><li><strong>L2gradient</strong>：布林值，若為 True 使用更精確的梯度計算 (L2 norm)，預設 False。</li></ul><h3 id="💻-範例程式-2"><a href="#💻-範例程式-2" class="headerlink" title="💻 範例程式"></a>💻 範例程式</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line">img = cv2.imread(<span class="string">"test.png"</span>)  <span class="comment"># 部落格頭像</span></span><br><span class="line">gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)</span><br><span class="line"></span><br><span class="line">canny = cv2.Canny(gray, <span class="number">100</span>, <span class="number">200</span>)</span><br><span class="line"></span><br><span class="line">cv2.imshow(<span class="string">"Original"</span>, img)</span><br><span class="line">cv2.imshow(<span class="string">"Canny"</span>, canny)</span><br><span class="line">cv2.waitKey(<span class="number">0</span>)</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/edge-detection/03.png" alt><br><em>圖：程式碼執行結果 — Canny 邊緣檢測，邊界清晰乾淨</em></p><h2 id="🧠-輪廓函式介紹"><a href="#🧠-輪廓函式介紹" class="headerlink" title="🧠 輪廓函式介紹"></a>🧠 輪廓函式介紹</h2><p>在 OpenCV 中，輪廓分析主要依靠兩個函式：</p><h3 id="cv2-findContours"><a href="#cv2-findContours" class="headerlink" title="cv2.findContours()"></a><code>cv2.findContours()</code></h3><ul><li>功能：從二值化圖片或邊緣檢測結果中，找出所有輪廓。</li><li>語法：<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">contours, hierarchy = cv2.findContours(image, mode, method)</span><br></pre></td></tr></table></figure></li><li>參數：<ul><li><code>image</code>：通常是二值化或邊緣檢測後的圖片。</li><li><code>mode</code>：輪廓檢索方式，例如 <code>cv2.RETR_EXTERNAL</code> (只取外層輪廓)、<code>cv2.RETR_TREE</code> (完整階層)。</li><li><code>method</code>：近似方式，例如 <code>cv2.CHAIN_APPROX_SIMPLE</code> (壓縮水平/垂直冗餘點)。</li></ul></li></ul><h3 id="cv2-drawContours"><a href="#cv2-drawContours" class="headerlink" title="cv2.drawContours()"></a><code>cv2.drawContours()</code></h3><ul><li>功能：將偵測到的輪廓繪製到圖片上。</li><li>語法：<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cv2.drawContours(image, contours, contourIdx, color, thickness)</span><br></pre></td></tr></table></figure></li><li>參數：<ul><li><code>image</code>：要繪製的圖片。</li><li><code>contours</code>：由 <code>findContours()</code> 找到的輪廓集合。</li><li><code>contourIdx</code>：指定要畫哪一個輪廓，<code>-1</code> 表示全部。</li><li><code>color</code>：繪製顏色，例如 <code>(0,255,0)</code>。</li><li><code>thickness</code>：線條粗細。</li></ul></li></ul><h2 id="✨-輪廓分析-Contours"><a href="#✨-輪廓分析-Contours" class="headerlink" title="✨ 輪廓分析 (Contours)"></a>✨ 輪廓分析 (Contours)</h2><h3 id="💻-範例程式-3"><a href="#💻-範例程式-3" class="headerlink" title="💻 範例程式"></a>💻 範例程式</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line">img = cv2.imread(<span class="string">"test.png"</span>)  <span class="comment"># 部落格頭像</span></span><br><span class="line">gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)</span><br><span class="line">canny = cv2.Canny(gray, <span class="number">100</span>, <span class="number">200</span>)</span><br><span class="line"></span><br><span class="line">contours, hierarchy = cv2.findContours(canny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)</span><br><span class="line"></span><br><span class="line">output = img.copy()</span><br><span class="line">cv2.drawContours(output, contours, <span class="number">-1</span>, (<span class="number">0</span>, <span class="number">255</span>, <span class="number">0</span>), <span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">cv2.imshow(<span class="string">"Original"</span>, img)</span><br><span class="line">cv2.imshow(<span class="string">"Contours"</span>, output)</span><br><span class="line">cv2.waitKey(<span class="number">0</span>)</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/edge-detection/04.png" alt><br><em>圖：程式碼執行結果 — 輪廓分析，綠色線條標示物件邊界</em></p><h2 id="📊-效果比較"><a href="#📊-效果比較" class="headerlink" title="📊 效果比較"></a>📊 效果比較</h2><table><thead><tr><th>方法</th><th>特點</th><th>優缺點</th></tr></thead><tbody><tr><td>Laplacian</td><td>二階導數，快速找邊界</td><td>容易受雜訊影響</td></tr><tr><td>Sobel</td><td>一階導數，分方向偵測</td><td>可強調水平/垂直邊緣</td></tr><tr><td>Canny</td><td>綜合演算法，結果乾淨</td><td>計算較複雜，但最常用</td></tr><tr><td>findContours + drawContours</td><td>輪廓偵測與繪製</td><td>可進一步分析形狀與面積</td></tr></tbody></table><h2 id="⚠️-注意事項"><a href="#⚠️-注意事項" class="headerlink" title="⚠️ 注意事項"></a>⚠️ 注意事項</h2><ul><li>邊緣檢測前建議先做 <strong>高斯模糊</strong>，避免雜訊造成誤判。</li><li><code>findContours()</code> 的回傳值在不同版本的 OpenCV 可能不同。</li><li>輪廓分析常搭配邊緣檢測，建議使用 Canny 作為前處理。</li></ul><h2 id="🎯-結語"><a href="#🎯-結語" class="headerlink" title="🎯 結語"></a>🎯 結語</h2><p>本篇我們學會了 <strong>Laplacian、Sobel、Canny 三種邊緣檢測方法</strong>，並理解了 <strong>cv2.findContours() 與 cv2.drawContours()</strong> 的用法，成功找出圖片中的物件邊界。<br>這些技巧在電腦視覺中非常常見，例如物件偵測、圖片分割、形狀辨識等。</p><p>📖 如在學習過程中遇到疑問，或是想了解更多相關主題，建議回顧一下 <a href="/python-opencv-20260106-python-opencv-index"><strong>Python | OpenCV 系列導讀</strong></a>，掌握完整的章節目錄，方便快速找到你需要的內容。<br><br></p><blockquote><p>註：以上參考了<br><a href="https://docs.opencv.org/4.x/d9/df8/tutorial_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV Tutorials</a><br><a href="https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV-Python Tutorials</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;📚-前言&quot;&gt;&lt;a href=&quot;#📚-前言&quot; class=&quot;headerlink&quot; title=&quot;📚 前言&quot;&gt;&lt;/a&gt;📚 前言&lt;/h2&gt;&lt;p&gt;在前一篇我們學會了 &lt;strong&gt;魔術棒填充顏色&lt;/strong&gt;。&lt;br&gt;這一篇要介紹 &lt;strong&gt;邊緣檢測
      
    
    </summary>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/categories/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/"/>
    
      <category term="05.特徵與進階篇" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/05-%E7%89%B9%E5%BE%B5%E8%88%87%E9%80%B2%E9%9A%8E%E7%AF%87/"/>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/tags/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/tags/OpenCV/"/>
    
  </entry>
  
  <entry>
    <title>Python | OpenCV 魔術棒填充顏色</title>
    <link href="https://morosedog.gitlab.io/python-opencv-20260203-python-opencv-floodfill/"/>
    <id>https://morosedog.gitlab.io/python-opencv-20260203-python-opencv-floodfill/</id>
    <published>2026-02-03T01:00:00.000Z</published>
    <updated>2026-03-02T01:23:05.767Z</updated>
    
    <content type="html"><![CDATA[<h2 id="📚-前言"><a href="#📚-前言" class="headerlink" title="📚 前言"></a>📚 前言</h2><p>在前一篇我們學會了 <strong>圖片合成、疊加與相減</strong>。<br>這一篇要介紹 <strong>魔術棒填充顏色</strong>，利用 OpenCV 的 <code>floodFill</code> 函式，實作類似 Photoshop 的魔術棒工具，快速選取顏色區域並填充。</p><h2 id="🎨-範例圖片"><a href="#🎨-範例圖片" class="headerlink" title="🎨 範例圖片"></a>🎨 範例圖片</h2><p><img loading="lazy" src="/images/python/opencv/floodfill/floodfill_sample.png" alt><br><em>圖：範例圖片 — 測試用彩色區塊</em></p><h2 id="🧠-floodFill-方法介紹"><a href="#🧠-floodFill-方法介紹" class="headerlink" title="🧠 floodFill 方法介紹"></a>🧠 floodFill 方法介紹</h2><p>OpenCV 的 <code>cv2.floodFill()</code> 是一個用來「填色」的函式，它會從一個指定的像素點（稱為 <strong>種子點 seed point</strong>）開始，向周圍擴展，找出顏色相近的區域並進行填色。</p><h3 id="🔍-基本語法"><a href="#🔍-基本語法" class="headerlink" title="🔍 基本語法"></a>🔍 基本語法</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cv2.floodFill(image, mask, seedPoint, newVal, loDiff, upDiff, flags)</span><br></pre></td></tr></table></figure><h3 id="📌-參數說明"><a href="#📌-參數說明" class="headerlink" title="📌 參數說明"></a>📌 參數說明</h3><table><thead><tr><th>參數</th><th>說明</th></tr></thead><tbody><tr><td><code>image</code></td><td>要填色的圖片（會被修改）</td></tr><tr><td><code>mask</code></td><td>遮罩，用來記錄哪些區域已被填色，大小需比原圖大 2 像素</td></tr><tr><td><code>seedPoint</code></td><td>起始點座標，例如 <code>(50, 50)</code></td></tr><tr><td><code>newVal</code></td><td>要填入的顏色，例如 <code>(0, 255, 0)</code></td></tr><tr><td><code>loDiff</code> / <code>upDiff</code></td><td>顏色容許差距，決定哪些像素算「相近」</td></tr><tr><td><code>flags</code></td><td>控制填色方式，例如 <code>FLOODFILL_FIXED_RANGE</code> 或 <code>FLOODFILL_MASK_ONLY</code></td></tr></tbody></table><h3 id="🎯-運作原理"><a href="#🎯-運作原理" class="headerlink" title="🎯 運作原理"></a>🎯 運作原理</h3><ol><li>從 <code>seedPoint</code> 開始，檢查周圍像素是否與起始點顏色「相近」。</li><li>若在 <code>loDiff</code> 與 <code>upDiff</code> 範圍內，就納入填色區域。</li><li>更新 <code>mask</code> 或 <code>image</code>，依照 <code>flags</code> 決定是否真的填色。</li></ol><h2 id="💻-範例程式"><a href="#💻-範例程式" class="headerlink" title="💻 範例程式"></a>💻 範例程式</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line"><span class="comment"># 讀取圖片</span></span><br><span class="line">img = cv2.imread(<span class="string">"floodfill_sample.png"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 顯示原圖</span></span><br><span class="line">cv2.imshow(<span class="string">"Original Image"</span>, img)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 建立遮罩 (比原圖大 2 像素)</span></span><br><span class="line">h, w = img.shape[:<span class="number">2</span>]</span><br><span class="line">mask = np.zeros((h+<span class="number">2</span>, w+<span class="number">2</span>), np.uint8)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 種子點 (假設使用者點選座標)</span></span><br><span class="line">seed_point = (<span class="number">15</span>, <span class="number">15</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 設定顏色範圍 (threshold)</span></span><br><span class="line">loDiff = (<span class="number">10</span>, <span class="number">10</span>, <span class="number">10</span>)</span><br><span class="line">upDiff = (<span class="number">10</span>, <span class="number">10</span>, <span class="number">10</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 執行 floodFill，直接在原圖上填色</span></span><br><span class="line">cv2.floodFill(img, mask, seed_point, (<span class="number">0</span>, <span class="number">255</span>, <span class="number">0</span>), loDiff, upDiff, flags=cv2.FLOODFILL_FIXED_RANGE)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 顯示填色後的結果</span></span><br><span class="line">cv2.imshow(<span class="string">"FloodFill Result"</span>, img)</span><br><span class="line"></span><br><span class="line">cv2.waitKey(<span class="number">0</span>)</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/floodfill/01.png" alt><br><em>圖：程式碼執行結果 — 原圖與填色結果</em></p><h2 id="🔧-loDiff-upDiff-效果比較"><a href="#🔧-loDiff-upDiff-效果比較" class="headerlink" title="🔧 loDiff / upDiff 效果比較"></a>🔧 loDiff / upDiff 效果比較</h2><p>在 <code>floodFill()</code> 中，<code>loDiff</code> 與 <code>upDiff</code> 控制顏色相似度範圍。<br>它們決定了「種子點顏色 ± 容許差距」的範圍，影響選取區域的大小。</p><h3 id="範例說明"><a href="#範例說明" class="headerlink" title="範例說明"></a>範例說明</h3><p>假設種子點顏色是 <code>(200, 200, 200)</code> (灰色)：</p><ul><li><p><code>loDiff = (10,10,10)</code>、<code>upDiff = (10,10,10)</code><br>→ 只會選取 <code>(190,190,190)</code> 到 <code>(210,210,210)</code> 的像素，範圍很精準。</p></li><li><p><code>loDiff = (50,50,50)</code>、<code>upDiff = (50,50,50)</code><br>→ 會選取 <code>(150,150,150)</code> 到 <code>(250,250,250)</code> 的像素，範圍更寬鬆。</p></li></ul><h3 id="📊-效果比較表"><a href="#📊-效果比較表" class="headerlink" title="📊 效果比較表"></a>📊 效果比較表</h3><table><thead><tr><th>設定值</th><th>選取範圍</th><th>特點</th></tr></thead><tbody><tr><td><code>loDiff=(10,10,10)</code> / <code>upDiff=(10,10,10)</code></td><td>種子點 ±10</td><td>精準選取，區域小</td></tr><tr><td><code>loDiff=(30,30,30)</code> / <code>upDiff=(30,30,30)</code></td><td>種子點 ±30</td><td>適中，常用設定</td></tr><tr><td><code>loDiff=(50,50,50)</code> / <code>upDiff=(50,50,50)</code></td><td>種子點 ±50</td><td>範圍大，容易包含更多顏色</td></tr></tbody></table><h2 id="⚠️-注意事項"><a href="#⚠️-注意事項" class="headerlink" title="⚠️ 注意事項"></a>⚠️ 注意事項</h2><ul><li>遮罩大小必須比原圖大 2 像素，否則會出錯。  </li><li><code>loDiff</code> 與 <code>upDiff</code> 的設定影響選取範圍，需依圖片調整。  </li><li><code>floodFill</code> 可以搭配 <code>FLOODFILL_MASK_ONLY</code> 或 <code>FLOODFILL_FIXED_RANGE</code> 使用，效果不同。</li></ul><h2 id="🎯-結語"><a href="#🎯-結語" class="headerlink" title="🎯 結語"></a>🎯 結語</h2><p>本篇我們學會了如何利用 OpenCV 實作 <strong>魔術棒顏色填充</strong>。<br>這個技巧能讓圖片編輯更直觀，並可延伸到互動式工具，例如滑鼠點選選取區域、即時顏色替換等。</p><p>📖 如在學習過程中遇到疑問，或是想了解更多相關主題，建議回顧一下 <a href="/python-opencv-20260106-python-opencv-index"><strong>Python | OpenCV 系列導讀</strong></a>，掌握完整的章節目錄，方便快速找到你需要的內容。<br><br></p><blockquote><p>註：以上參考了<br><a href="https://docs.opencv.org/4.x/d9/df8/tutorial_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV Tutorials</a><br><a href="https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV-Python Tutorials</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;📚-前言&quot;&gt;&lt;a href=&quot;#📚-前言&quot; class=&quot;headerlink&quot; title=&quot;📚 前言&quot;&gt;&lt;/a&gt;📚 前言&lt;/h2&gt;&lt;p&gt;在前一篇我們學會了 &lt;strong&gt;圖片合成、疊加與相減&lt;/strong&gt;。&lt;br&gt;這一篇要介紹 &lt;strong&gt;魔
      
    
    </summary>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/categories/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/"/>
    
      <category term="04.幾何與繪圖篇" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/04-%E5%B9%BE%E4%BD%95%E8%88%87%E7%B9%AA%E5%9C%96%E7%AF%87/"/>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/tags/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/tags/OpenCV/"/>
    
  </entry>
  
  <entry>
    <title>Python | OpenCV 圖片合成、疊加與相減</title>
    <link href="https://morosedog.gitlab.io/python-opencv-20260202-python-opencv-add/"/>
    <id>https://morosedog.gitlab.io/python-opencv-20260202-python-opencv-add/</id>
    <published>2026-02-02T01:00:00.000Z</published>
    <updated>2026-03-02T01:23:05.767Z</updated>
    
    <content type="html"><![CDATA[<h2 id="📚-前言"><a href="#📚-前言" class="headerlink" title="📚 前言"></a>📚 前言</h2><p>在前一篇我們學會了 <strong>建立遮罩與基本運算</strong>。<br>這一篇要介紹 <strong>圖片合成、疊加與相減</strong>，並展示如何利用遮罩完成 <strong>去背 + 合成</strong> 的應用。</p><h2 id="🎨-範例圖片"><a href="#🎨-範例圖片" class="headerlink" title="🎨 範例圖片"></a>🎨 範例圖片</h2><p><img loading="lazy" src="/images/python/opencv/add/red.png" alt> <img loading="lazy" src="/images/python/opencv/add/green.png" alt> <img loading="lazy" src="/images/python/opencv/add/blue.png" alt><br><em>圖：三原色範例圖片 — 紅色、綠色、藍色圓形</em><br><img loading="lazy" src="/images/python/opencv/add/OpenCV_logo_black.png" alt><br><em>圖：圖片來源 — <a href="https://github.com/opencv/opencv/wiki/opencvlogo?utm_source=copilot.com" target="_blank" rel="external nofollow noopener noreferrer">OpenCV 官方 Github</a></em><br><img loading="lazy" src="/images/python/opencv/add/yellow.png" alt> <img loading="lazy" src="/images/python/opencv/add/white_green.png" alt><br><em>圖：圖片相減範例圖片 — 黃色、白綠色圓形</em></p><h2 id="➕-圖片合成-add"><a href="#➕-圖片合成-add" class="headerlink" title="➕ 圖片合成 (add)"></a>➕ 圖片合成 (add)</h2><h3 id="原理說明"><a href="#原理說明" class="headerlink" title="原理說明"></a>原理說明</h3><ul><li>使用 <code>cv2.add(img1, img2)</code> 將兩張圖片相加。</li><li>像素值超過 255 時會被截斷為 255。</li><li>可以逐步相加，將三張圖片合成為一張「三原色比較圖」。</li></ul><h3 id="💻-範例程式"><a href="#💻-範例程式" class="headerlink" title="💻 範例程式"></a>💻 範例程式</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line"><span class="comment"># 讀取三張圖片</span></span><br><span class="line">img1 = cv2.imread(<span class="string">"red.png"</span>)</span><br><span class="line">img2 = cv2.imread(<span class="string">"green.png"</span>)</span><br><span class="line">img3 = cv2.imread(<span class="string">"blue.png"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 確保大小一致</span></span><br><span class="line">img2 = cv2.resize(img2, (img1.shape[<span class="number">1</span>], img1.shape[<span class="number">0</span>]))</span><br><span class="line">img3 = cv2.resize(img3, (img1.shape[<span class="number">1</span>], img1.shape[<span class="number">0</span>]))</span><br><span class="line"></span><br><span class="line"><span class="comment"># 先合成前兩張</span></span><br><span class="line">temp = cv2.add(img1, img2)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 再把結果與第三張合成</span></span><br><span class="line">output = cv2.add(temp, img3)</span><br><span class="line"></span><br><span class="line">cv2.imshow(<span class="string">"Red"</span>, img1)</span><br><span class="line">cv2.imshow(<span class="string">"Green"</span>, img2)</span><br><span class="line">cv2.imshow(<span class="string">"Blue"</span>, img3)</span><br><span class="line">cv2.imshow(<span class="string">"Add Result"</span>, output)</span><br><span class="line">cv2.waitKey(<span class="number">0</span>)</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/add/01.png" alt><br><em>圖：程式碼執行結果 — 三張圖片合成為三原色比較圖</em></p><h2 id="🌫️-圖片權重疊加-addWeighted"><a href="#🌫️-圖片權重疊加-addWeighted" class="headerlink" title="🌫️ 圖片權重疊加 (addWeighted)"></a>🌫️ 圖片權重疊加 (addWeighted)</h2><h3 id="原理說明-1"><a href="#原理說明-1" class="headerlink" title="原理說明"></a>原理說明</h3><ul><li>使用 <code>cv2.addWeighted(img1, alpha, img2, beta, gamma)</code>。</li><li>可模擬透明效果，常用於圖片融合。</li><li><code>alpha</code> 與 <code>beta</code> 為權重比例，<code>gamma</code> 為亮度調整。</li></ul><h3 id="💻-範例程式-1"><a href="#💻-範例程式-1" class="headerlink" title="💻 範例程式"></a>💻 範例程式</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line">img1 = cv2.imread(<span class="string">"test.png"</span>)  <span class="comment"># 部落格頭像</span></span><br><span class="line">img2 = cv2.imread(<span class="string">"OpenCV_logo_black.png"</span>)</span><br><span class="line"></span><br><span class="line">img2 = cv2.resize(img2, (img1.shape[<span class="number">1</span>], img1.shape[<span class="number">0</span>]))</span><br><span class="line"></span><br><span class="line">output = cv2.addWeighted(img1, <span class="number">0.8</span>, img2, <span class="number">0.2</span>, <span class="number">0</span>)</span><br><span class="line"></span><br><span class="line">cv2.imshow(<span class="string">"Weighted Blend"</span>, output)</span><br><span class="line">cv2.waitKey(<span class="number">0</span>)</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/add/02.png" alt><br><em>圖：程式碼執行結果 — 頭像與 OpenCV logo 的透明融合</em></p><h2 id="➖-圖片相減-subtract"><a href="#➖-圖片相減-subtract" class="headerlink" title="➖ 圖片相減 (subtract)"></a>➖ 圖片相減 (subtract)</h2><h3 id="原理說明-2"><a href="#原理說明-2" class="headerlink" title="原理說明"></a>原理說明</h3><ul><li>使用 <code>cv2.subtract(img1, img2)</code>，會逐像素相減。</li><li>若結果為負數，會被截斷成 0（黑色）；若大於 255，則截斷為 255。</li><li>常用於差異檢測，找出兩張圖片不同的部分。</li></ul><h3 id="💻-範例程式-2"><a href="#💻-範例程式-2" class="headerlink" title="💻 範例程式"></a>💻 範例程式</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line"><span class="comment"># 讀取圖片</span></span><br><span class="line">img1 = cv2.imread(<span class="string">"white_green.png"</span>)   <span class="comment"># 白色 + 綠色重疊的圖</span></span><br><span class="line">img2 = cv2.imread(<span class="string">"yellow.png"</span>)        <span class="comment"># 黃色圓形的圖</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 確保大小一致</span></span><br><span class="line">img2 = cv2.resize(img2, (img1.shape[<span class="number">1</span>], img1.shape[<span class="number">0</span>]))</span><br><span class="line"></span><br><span class="line"><span class="comment"># 相減</span></span><br><span class="line">output = cv2.subtract(img1, img2)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 顯示結果</span></span><br><span class="line">cv2.imshow(<span class="string">"Image A (White+Green)"</span>, img1)</span><br><span class="line">cv2.imshow(<span class="string">"Image B (Yellow)"</span>, img2)</span><br><span class="line">cv2.imshow(<span class="string">"Subtract Result"</span>, output)</span><br><span class="line">cv2.waitKey(<span class="number">0</span>)</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/add/03.png" alt><br><em>圖：程式碼執行結果 — 白色與綠色圖片減去黃色圖片</em></p><h3 id="🎨-顏色相減示例"><a href="#🎨-顏色相減示例" class="headerlink" title="🎨 顏色相減示例"></a>🎨 顏色相減示例</h3><table><thead><tr><th>圖片 A 像素</th><th>圖片 B 像素</th><th>結果像素</th><th>說明</th></tr></thead><tbody><tr><td>(255,255,255) 白色</td><td>(0,255,255) 黃色</td><td>(255,0,0) 藍色</td><td>白色減去黃色 → 只剩藍色</td></tr><tr><td>(0,255,0) 綠色</td><td>(0,255,255) 黃色</td><td>(0,0,0) 黑色</td><td>綠色減去黃色 → 負值截斷為黑色</td></tr><tr><td>(255,0,0) 紅色</td><td>(0,255,255) 黃色</td><td>(255,0,0) 紅色</td><td>紅色減去黃色 → 維持紅色</td></tr></tbody></table><h3 id="📊-小結"><a href="#📊-小結" class="headerlink" title="📊 小結"></a>📊 小結</h3><ul><li><code>subtract()</code> 的結果取決於兩張圖片的顏色差異。</li><li>白色減去黃色 → 只剩藍色分量。</li><li>綠色減去黃色 → 因為差值為負，結果變黑。</li><li>適合用來比較兩張相似圖片，找出差異區域。</li></ul><h2 id="🖼️-去背-合成"><a href="#🖼️-去背-合成" class="headerlink" title="🖼️ 去背 + 合成"></a>🖼️ 去背 + 合成</h2><h3 id="原理說明-3"><a href="#原理說明-3" class="headerlink" title="原理說明"></a>原理說明</h3><ul><li>利用遮罩 (<code>mask</code>) 去除 logo 背景。</li><li>再用 <code>add()</code> 或 <code>addWeighted()</code> 將去背後的圖片合成到背景。</li></ul><h3 id="💻-範例程式-3"><a href="#💻-範例程式-3" class="headerlink" title="💻 範例程式"></a>💻 範例程式</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line"><span class="comment"># 讀取 logo 與背景 (保持原始大小)</span></span><br><span class="line">logo = cv2.imread(<span class="string">"OpenCV_logo_black.png"</span>)</span><br><span class="line">bg   = cv2.imread(<span class="string">"test.png"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 建立灰階遮罩</span></span><br><span class="line">logo_gray = cv2.cvtColor(logo, cv2.COLOR_BGR2GRAY)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 建立兩個遮罩：一個保留 logo，一個保留背景</span></span><br><span class="line"><span class="comment"># 白色區域灰階值很高，設定閾值 240</span></span><br><span class="line">_, mask_logo = cv2.threshold(logo_gray, <span class="number">240</span>, <span class="number">255</span>, cv2.THRESH_BINARY_INV)  <span class="comment"># 保留非白色區域 (logo)</span></span><br><span class="line">_, mask_bg   = cv2.threshold(logo_gray, <span class="number">240</span>, <span class="number">255</span>, cv2.THRESH_BINARY)      <span class="comment"># 保留白色區域 (背景)</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 去背：只保留 logo 部分</span></span><br><span class="line">logo_no_bg = cv2.bitwise_and(logo, logo, mask=mask_logo)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 建立 ROI (放置 logo 的區域)</span></span><br><span class="line"><span class="comment"># 這裡假設 logo 要放在背景左上角 (0:logo.shape[0], 0:logo.shape[1])</span></span><br><span class="line">rows, cols = logo.shape[:<span class="number">2</span>]</span><br><span class="line">roi = bg[<span class="number">0</span>:rows, <span class="number">0</span>:cols]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 清空 ROI 中 logo 的位置</span></span><br><span class="line">bg_roi = cv2.bitwise_and(roi, roi, mask=mask_bg)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 合成：背景 ROI + logo</span></span><br><span class="line">dst = cv2.add(bg_roi, logo_no_bg)</span><br><span class="line">bg[<span class="number">0</span>:rows, <span class="number">0</span>:cols] = dst</span><br><span class="line"></span><br><span class="line"><span class="comment"># 顯示結果</span></span><br><span class="line">cv2.imshow(<span class="string">"Logo Merge Result"</span>, bg)</span><br><span class="line">cv2.waitKey(<span class="number">0</span>)</span><br><span class="line">cv2.destroyAllWindows()</span><br></pre></td></tr></table></figure><p><img loading="lazy" src="/images/python/opencv/add/04.png" alt><br><em>圖：利用遮罩去背後，將 OpenCV logo 合成到部落格頭像</em></p><h2 id="📊-效果比較"><a href="#📊-效果比較" class="headerlink" title="📊 效果比較"></a>📊 效果比較</h2><table><thead><tr><th>函式</th><th>功能</th><th>常見用途</th></tr></thead><tbody><tr><td><code>add()</code></td><td>圖片相加</td><td>合成圖片</td></tr><tr><td><code>addWeighted()</code></td><td>權重疊加</td><td>透明效果、圖片融合</td></tr><tr><td><code>subtract()</code></td><td>圖片相減</td><td>差異檢測</td></tr><tr><td>遮罩 + 合成</td><td>去背後合成</td><td>Logo 疊加、圖片合成</td></tr></tbody></table><h2 id="⚠️-注意事項"><a href="#⚠️-注意事項" class="headerlink" title="⚠️ 注意事項"></a>⚠️ 注意事項</h2><ul><li><strong>圖片大小</strong>：<code>add()</code>、<code>addWeighted()</code>、<code>subtract()</code> 都要求兩張圖片大小一致，否則需要先用 <code>cv2.resize()</code> 調整。</li><li><strong>像素值範圍</strong>：<code>add()</code> 與 <code>subtract()</code> 的結果會被截斷在 0–255 之間，超過 255 會變成 255，低於 0 會變成 0。</li><li><strong>透明效果</strong>：<code>addWeighted()</code> 可以模擬透明疊加，但 OpenCV 本身不支援 alpha 通道顯示，若要真正透明背景需轉成 BGRA 並輸出 PNG。</li><li><strong>遮罩合成</strong>：去背合成時，遮罩必須是灰階圖，且大小要與前景圖片一致。</li></ul><h2 id="🎯-結語"><a href="#🎯-結語" class="headerlink" title="🎯 結語"></a>🎯 結語</h2><p>本篇我們學會了 <strong>圖片合成、疊加與相減</strong>，並展示了遮罩去背後的合成應用。這些技巧在圖片處理、資料集製作、圖片融合時非常常見。</p><p>在下一篇，我們將進一步探索 <strong>魔術棒填充顏色</strong>，學習如何利用 OpenCV 的區域選取與顏色填充技巧，讓圖片編輯更直觀。</p><p>📖 如在學習過程中遇到疑問，或是想了解更多相關主題，建議回顧一下 <a href="/python-opencv-20260106-python-opencv-index"><strong>Python | OpenCV 系列導讀</strong></a>，掌握完整的章節目錄，方便快速找到你需要的內容。<br><br></p><blockquote><p>註：以上參考了<br><a href="https://docs.opencv.org/4.x/d9/df8/tutorial_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV Tutorials</a><br><a href="https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html" target="_blank" rel="external nofollow noopener noreferrer">OpenCV-Python Tutorials</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;📚-前言&quot;&gt;&lt;a href=&quot;#📚-前言&quot; class=&quot;headerlink&quot; title=&quot;📚 前言&quot;&gt;&lt;/a&gt;📚 前言&lt;/h2&gt;&lt;p&gt;在前一篇我們學會了 &lt;strong&gt;建立遮罩與基本運算&lt;/strong&gt;。&lt;br&gt;這一篇要介紹 &lt;strong&gt;圖片
      
    
    </summary>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/categories/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/"/>
    
      <category term="04.幾何與繪圖篇" scheme="https://morosedog.gitlab.io/categories/Python/OpenCV/04-%E5%B9%BE%E4%BD%95%E8%88%87%E7%B9%AA%E5%9C%96%E7%AF%87/"/>
    
    
      <category term="Python" scheme="https://morosedog.gitlab.io/tags/Python/"/>
    
      <category term="OpenCV" scheme="https://morosedog.gitlab.io/tags/OpenCV/"/>
    
  </entry>
  
</feed>
