文章目錄
- 高動態范圍成像
- 引言
- 曝光序列
- 源代碼
- 示例圖像
- 說明
- 結果
- 色調映射圖像
- 曝光融合
- 附加資源
- 高級圖像拼接 API(Stitcher 類)
- 目標
- 代碼
- 說明
- 相機模型
- 試用指南
- 圖像拼接詳解 (Python OpenCV >4.0.1)
- stitching_detailed
- 如何使用背景減除方法
- 目標
- 代碼
- 代碼解析
- 結果
- 參考文獻
- Meanshift 與 Camshift 算法
- 目標
- Meanshift算法
- OpenCV 中的 Meanshift 算法
- Camshift
- OpenCV 中的 Camshift 算法
- 其他資源
- 練習
- 光流
- 目標
- Lucas-Kanade 方法
- OpenCV中的Lucas-Kanade光流法
- OpenCV中的稠密光流
- 級聯分類器
- 目標
- 理論
- OpenCV 中的 Haar 級聯檢測
- 運行結果
- 附加資源
- 級聯分類器訓練
- 簡介
- 重要說明
- 訓練數據準備
- 負樣本
- 正樣本
- 額外說明
- 使用 OpenCV 的集成標注工具
- 級聯訓練
- 可視化級聯分類器
- 條形碼識別
- 目標
- 基礎概念
- 代碼示例
- 主類
- 初始化
- 檢測
- 解碼
- 檢測與解碼
- 結果
- 支持向量機簡介
- 目標
- 什么是支持向量機?
- 最優超平面是如何計算的?
- 源代碼
- 說明
- **設置訓練數據**
- **設置SVM參數**
- **SVM分類的區域**
- 支持向量
- 實驗結果
- 支持向量機處理非線性可分數據
- 目標
- 動機
- 優化問題的擴展
- 源代碼
- 說明
- 設置訓練數據
- 設置支持向量機參數
- 顯示決策區域
- 支持向量
- 結果
- 主成分分析(PCA)簡介
- 目標
- 什么是PCA?
- 如何計算特征向量和特征值?
- 源代碼
- 說明
- 讀取圖像并轉換為二值圖像
- 提取目標對象
- 提取方向
- 結果
高動態范圍成像
https://docs.opencv.org/4.x/d3/db7/tutorial_hdr_imaging.html
下一篇教程: 高級圖像拼接API (Stitcher類)
原作者 | Fedor Morozov |
兼容性 | OpenCV >= 3.0 |
引言
如今大多數數字圖像和成像設備每個通道使用8位,因此將設備的動態范圍限制在兩個數量級(實際為256級),而人眼能適應跨越十個數量級的光照條件變化。當我們拍攝真實場景時,亮部區域可能過曝,暗部區域可能欠曝,因此無法通過單次曝光捕捉所有細節。高動態范圍(HDR)成像使用每個通道超過8位(通常為32位浮點值)的圖像,從而支持更廣的動態范圍。
獲取HDR圖像有多種方法,最常見的是對同一場景拍攝不同曝光值的照片。要合成這些曝光圖像,了解相機的響應函數很有幫助,現有算法可對其進行估算。HDR圖像合成后需轉換回8位才能在普通顯示器上查看,這一過程稱為色調映射。當場景中的物體或相機在拍攝間移動時會產生額外復雜性,因為不同曝光的圖像需要配準和對齊。
本教程展示如何通過曝光序列生成并顯示HDR圖像。本案例中圖像已預先對齊且無移動物體。我們還將演示一種稱為曝光融合的替代方法,該方法能生成低動態范圍圖像。HDR流程的每個步驟可采用不同算法實現,具體可查閱參考手冊了解全部選項。
曝光序列
源代碼
以下展示本教程的代碼行。你也可以從C++下載。
Python
以下展示本教程的代碼行。你也可以從這里下載。
from __future__ import print_function
from __future__ import division
import cv2 as cv
import numpy as np
import argparse
import osdef loadExposureSeq(path):images = []times = []with open(os.path.join(path, 'list.txt')) as f:content = f.readlines()for line in content:tokens = line.split()images.append(cv.imread(os.path.join(path, tokens[0])))times.append(1 / float(tokens[1]))return images, np.asarray(times, dtype=np.float32)parser = argparse.ArgumentParser(description='Code for High Dynamic Range Imaging tutorial.')
parser.add_argument('--input', type=str, help='Path to the directory that contains images and exposure times.')
args = parser.parse_args()if not args.input:parser.print_help()exit(0)images, times = loadExposureSeq(args.input)calibrate = cv.createCalibrateDebevec()
response = calibrate.process(images, times)merge_debevec = cv.createMergeDebevec()
hdr = merge_debevec.process(images, times, response)tonemap = cv.createTonemapDrago(2.2)
ldr = tonemap.process(hdr)merge_mertens = cv.createMergeMertens()
fusion = merge_mertens.process(images)cv.imwrite('fusion.png', fusion * 255)
cv.imwrite('ldr.png', ldr * 255)
cv.imwrite('hdr.hdr', hdr)
示例圖像
包含圖像、曝光時間和list.txt
文件的數據目錄可從此處下載。
說明
- 加載圖像和曝光時間
images, times = loadExposureSeq(args.input)
首先我們從用戶定義的文件夾加載輸入圖像和曝光時間。該文件夾應包含圖像和list.txt文件,其中記錄了文件名及對應的逆曝光時間。
針對我們的圖像序列,列表內容如下:
memorial00.png 0.03125memorial01.png 0.0625...memorial15.png 1024
- 估計相機響應
calibrate = [cv.createCalibrateDebevec`](https://docs.opencv.org/4.x/d6/df5/group__photo__hdr.html#ga670bbeecf0aac14abf386083a57b7958)() response = calibrate.process(images, times)
許多HDR構建算法都需要了解相機響應函數(CRF)。我們采用一種校準算法來估算所有256個像素值的逆CRF。
- 生成HDR圖像
merge_debevec = [cv.createMergeDebevec`](https://docs.opencv.org/4.x/d6/df5/group__photo__hdr.html#gab2c9fc25252aee0915733ff8ea987190)()hdr = merge_debevec.process(images, times, response)
我們使用Debevec的權重方案,利用前一項計算出的響應來構建HDR圖像。
- 對HDR圖像進行色調映射
tonemap = cv.createTonemapDrago(2.2)ldr = tonemap.process(hdr)
由于我們需要在普通LDR顯示器上查看結果,因此必須將HDR圖像映射到8位范圍,同時保留大部分細節。這正是色調映射方法的主要目標。我們采用了帶雙邊濾波的色調映射器,并將伽馬校正值設為2.2。
- 執行曝光融合
merge_mertens = cv.createMergeMertens()fusion = merge_mertens.process(images)
當不需要HDR圖像時,還有一種替代方法可以合并我們的曝光。這個過程稱為曝光融合,生成的LDR圖像不需要進行伽馬校正。它也不使用照片的曝光值。
- 寫入結果
cv.imwrite('fusion.png', fusion * 255)cv.imwrite('ldr.png', ldr * 255)cv.imwrite('hdr.hdr', hdr)
現在來看看結果。需要注意的是,HDR圖像無法以常見圖像格式存儲,因此我們將其保存為Radiance圖像(.hdr)。此外,所有HDR成像函數的返回結果范圍都在[0,1]之間,因此我們需要將結果乘以255。
你可以嘗試其他色調映射算法:cv::TonemapDrago
、cv::TonemapMantiuk
和cv::TonemapReinhard
。你也可以根據自己照片的需求,調整HDR校準和色調映射方法中的參數。
結果
色調映射圖像
曝光融合
附加資源
1、Paul E Debevec 和 Jitendra Malik。從照片中恢復高動態范圍輻射圖。收錄于 ACM SIGGRAPH 2008 課程,第 31 頁。ACM,2008 年。[68]](https://docs.opencv.org/4.x/d0/de3/citelist.html#CITEREF_dm97) 2、Mark A Robertson、Sean Borman 和 Robert L Stevenson。通過多重曝光改善動態范圍。收錄于《圖像處理,1999 年國際會議論文集》,第 3 卷,第 159–163 頁。IEEE,1999 年。[[228]
3、Tom Mertens、Jan Kautz 和 Frank Van Reeth。曝光融合。收錄于《計算機圖形學與應用,2007 年太平洋會議論文集》,第 382–390 頁。IEEE,2007 年。[189]`
4、維基百科-HDR
5、從照片中恢復高動態范圍輻射圖(網頁)
生成于 2025 年 4 月 30 日星期三 23:08:42,由 doxygen 1.12.0 為 OpenCV 創建
高級圖像拼接 API(Stitcher 類)
https://docs.opencv.org/4.x/d8/d19/tutorial_stitcher.html
上一教程: 高動態范圍成像
下一教程: 如何使用背景減除方法
原作者 | Jiri Horner |
兼容性 | OpenCV >= 3.2 |
目標
在本教程中,您將學習如何:
- 使用由
cv::Stitcher
提供的高級圖像拼接 API - 學習如何使用預配置的 Stitcher 配置,通過不同的相機模型進行圖像拼接
代碼
C++ 本教程的代碼展示在下方。你可以從這里下載。
注意:C++版本包含一些額外功能選項,如圖像分割(–d3)和更詳細的錯誤處理機制,這些在Python示例中并未提供。
#!/usr/bin/env python'''
Stitching sample
================Show how to use Stitcher API from python in a simple way to stitch panoramas
or scans.
'''# Python 2/3 compatibility
from __future__ import print_functionimport numpy as np
import cv2 as cvimport argparse
import sysmodes = (cv.Stitcher_PANORAMA, cv.Stitcher_SCANS)parser = argparse.ArgumentParser(prog='stitching.py', description='Stitching sample.')
parser.add_argument('--mode',type = int, choices = modes, default = cv.Stitcher_PANORAMA,help = 'Determines configuration of stitcher. The default is `PANORAMA` (%d), ''mode suitable for creating photo panoramas. Option `SCANS` (%d) is suitable ''for stitching materials under affine transformation, such as scans.' % modes)
parser.add_argument('--output', default = 'result.jpg',help = 'Resulting image. The default is `result.jpg`.')
parser.add_argument('img', nargs='+', help = 'input images')__doc__ += '\n' + parser.format_help()def main():args = parser.parse_args()# read input imagesimgs = []for img_name in args.img:img = cv.imread(cv.samples.findFile(img_name))if img is None:print("can't read image " + img_name)sys.exit(-1)imgs.append(img)#![stitching]stitcher = cv.Stitcher.create(args.mode)status, pano = stitcher.stitch(imgs)if status != cv.Stitcher_OK:print("Can't stitch images, error code = %d" % status)sys.exit(-1)#![stitching]cv.imwrite(args.output, pano)print("stitching completed successfully. %s saved!" % args.output)print('Done')if __name__ == '__main__':print(__doc__)main()cv.destroyAllWindows()
說明
最重要的代碼部分是:
stitcher = cv.Stitcher.create(args.mode)status, pano = stitcher.stitch(imgs)if status != cv.Stitcher_OK:print("Can't stitch images, error code = %d" % status)sys.exit(-1)
創建了一個新的stitcher實例,cv::Stitcher::stitch
將完成所有繁重的工作。
cv::Stitcher::create
可以用預定義的配置模式(參數 mode
)創建stitcher。詳情請參閱 cv::Stitcher::Mode
。這些配置會設置多個stitcher屬性,使其在預定義的場景下運行。在以預定義配置創建stitcher后,您可以通過設置任意stitcher屬性來調整拼接效果。
如果擁有CUDA設備,可以配置 cv::Stitcher
將某些操作卸載到GPU上執行。若需此配置,請將 try_use_gpu
設為true。無論此標志如何設置,OpenCL加速都會基于OpenCV的全局設置透明地啟用。
拼接可能因多種原因失敗,您應始終檢查是否一切正常,并確認生成的全景圖已存入 pano
。可能的錯誤代碼請參閱 cv::Stitcher::Status
文檔。
相機模型
目前拼接流水線中實現了兩種相機模型:
-
單應性模型:適用于圖像間存在透視變換的場景,相關實現類包括
cv::detail::BestOf2NearestMatcher
、cv::detail::HomographyBasedEstimator
、cv::detail::BundleAdjusterReproj
和cv::detail::BundleAdjusterRay
-
仿射模型:支持6自由度或4自由度的仿射變換,相關實現類包括
cv::detail::AffineBestOf2NearestMatcher
、cv::detail::AffineBasedEstimator
、cv::detail::BundleAdjusterAffine
、cv::detail::BundleAdjusterAffinePartial
以及cv::AffineWarper
單應性模型適用于創建普通相機拍攝的照片全景圖,而基于仿射的模型可用于拼接專業設備獲取的掃描件和物體圖像。
注意:cv::Stitcher
的某些詳細設置可能不適用。特別要注意不應混用實現仿射模型的類和實現單應性模型的類,因為它們處理的是不同類型的變換。
試用指南
如果啟用了示例構建,你可以在 build/bin/cpp-example-stitching
目錄下找到可執行文件。該示例是一個控制臺應用程序,不帶參數運行即可查看幫助信息。opencv_extra
提供了一些測試所有可用配置的樣本數據。
要嘗試全景模式,請運行:
./cpp-example-stitching --mode panorama <path to opencv_extra>/testdata/stitching/boat*
或者(來自專業書籍掃描儀的數據集):
./cpp-example-stitching --mode scans <path to opencv_extra>/testdata/stitching/budapest*
注意:上述示例基于POSIX平臺,在Windows系統中需顯式提供所有文件名(如boat1.jpg
boat2.jpg
…),因為Windows命令行不支持*
通配符擴展。
圖像拼接詳解 (Python OpenCV >4.0.1)
如果你想研究拼接流程的內部實現,或者希望進行詳細配置的實驗,可以使用C++或Python版本的stitching_detailed源代碼。
stitching_detailed
C++ stitching_detailed.cpp
Python stitching_detailed.py
stitching_detailed 程序通過命令行獲取圖像拼接參數。該程序支持眾多參數配置,上述示例展示了部分可用的命令行參數:
boat5.jpg boat2.jpg boat3.jpg boat4.jpg boat1.jpg boat6.jpg –work_megapix 0.6 –features orb –matcher homography –estimator homography –match_conf 0.3 –conf_thresh 0.3 –ba ray –ba_refine_mask xxxxx –save_graph test.txt –wave_correct no –warp fisheye –blend multiband –expos_comp no –seam gc_colorgrad
配對圖像通過單應性矩陣進行匹配:
- 使用單應性匹配器
–matcher homography
- 變換估計也采用單應性估計器
–estimator homography
特征匹配步驟的置信度閾值為 0.3:–match_conf 0.3
。若圖像匹配困難,可適當降低該值
判斷兩圖是否屬于同一全景圖的置信度閾值為 0.3:–conf_thresh 0.3
。若匹配困難,可降低此值
光束法平差(Bundle Adjustment)采用射線成本函數:–ba ray
光束法平差的優化掩碼格式為 xxxxx(–ba_refine_mask xxxxx
):
- ‘x’ 表示優化對應參數
- ‘_’ 表示不優化
格式說明:fx(焦距x), skew(傾斜系數), ppx(主點x), aspect(縱橫比), ppy(主點y)
將匹配關系圖以DOT語言格式保存至test.txt(–save_graph test.txt
):
- 標簽說明:Nm表示匹配數,Ni表示內點數,C表示置信度
波形校正功能關閉(–wave_correct no
)
曲面變形類型為魚眼(–warp fisheye
)
融合方法采用多頻段混合(–blend multiband
)
曝光補償功能未啟用(–expos_comp no
)
接縫估計算法基于最小圖割色彩梯度(–seam gc_colorgrad
)
這些參數也可直接在命令行中使用:
boat5.jpg boat2.jpg boat3.jpg boat4.jpg boat1.jpg boat6.jpg –work_megapix 0.6 –features orb –matcher homography –estimator homography –match_conf 0.3 –conf_thresh 0.3 –ba ray –ba_refine_mask xxxxx –wave_correct horiz –warp compressedPlaneA2B1 –blend multiband –expos_comp channels_blocks –seam gc_colorgrad
您將獲得:
對于使用掃描儀或無人機(仿射運動)捕獲的圖像,您可以在命令行中使用以下參數:
newspaper1.jpg newspaper2.jpg –work_megapix 0.6 –features surf –matcher affine –estimator affine –match_conf 0.3 –conf_thresh 0.3 –ba affine –ba_refine_mask xxxxx –wave_correct no –warp affine
你可以在 https://github.com/opencv/opencv_extra/tree/4.x/testdata/stitching 找到所有圖像
生成于 2025年4月30日 星期三 23:08:42 為 OpenCV 由 doxygen 1.12.0 創建
如何使用背景減除方法
https://docs.opencv.org/4.x/d1/dc5/tutorial_background_subtraction.html
上一教程: 高級圖像拼接API(Stitcher類)
下一教程: 均值漂移與連續自適應均值漂移
原作者 | Domenico Daniele Bloisi |
兼容性 | OpenCV >= 3.0 |
- 背景減除(BS)是一種通過靜態攝像頭生成前景掩模(即包含場景中運動物體像素的二值圖像)的常用技術。
- 顧名思義,BS通過計算當前幀與背景模型之間的差值來獲取前景掩模,背景模型包含場景的靜態部分,或者更廣義地說,包含根據觀察場景特性可被視為背景的所有內容。
- 背景建模包含兩個主要步驟:
- 背景初始化;
- 背景更新。第一步計算背景的初始模型,第二步則更新該模型以適應場景中可能發生的變化。
- 本教程將學習如何使用OpenCV執行背景減除。
目標
在本教程中,您將學習如何:
1、使用 cv::VideoCapture
從視頻或圖像序列中讀取數據;
2、通過 cv::BackgroundSubtractor
類創建并更新背景模型;
3、利用 cv::imshow
獲取并顯示前景掩膜;
代碼
以下提供源代碼示例。我們將允許用戶選擇處理視頻文件或圖像序列。
本示例將使用 cv::BackgroundSubtractorMOG2
來生成前景掩模。
處理結果和輸入數據都會實時顯示在屏幕上。
可下載代碼:C++ | Java | Python
代碼概覽:
from __future__ import print_function
import cv2 as cv
import argparseparser = argparse.ArgumentParser(description='This program shows how to use background subtraction methods provided by \OpenCV. You can process both videos and images.')
parser.add_argument('--input', type=str, help='Path to a video or a sequence of image.', default='vtest.avi')
parser.add_argument('--algo', type=str, help='Background subtraction method (KNN, MOG2).', default='MOG2')
args = parser.parse_args()if args.algo == 'MOG2':backSub = cv.createBackgroundSubtractorMOG2()
else:backSub = cv.createBackgroundSubtractorKNN()capture = cv.VideoCapture(cv.samples.findFileOrKeep(args.input))
if not capture.isOpened():print('Unable to open: ' + args.input)exit(0)while True:ret, frame = capture.read()if frame is None:breakfgMask = backSub.apply(frame)cv.rectangle(frame, (10, 2), (100,20), (255,255,255), -1)cv.putText(frame, str(capture.get(cv.CAP_PROP_POS_FRAMES)), (15, 15),cv.FONT_HERSHEY_SIMPLEX, 0.5 , (0,0,0))cv.imshow('Frame', frame)cv.imshow('FG Mask', fgMask)keyboard = cv.waitKey(30)if keyboard == 'q' or keyboard == 27:break
代碼解析
我們將討論上述代碼的主要部分:
- 使用
cv::BackgroundSubtractor
對象來生成前景掩膜。本示例中使用的是默認參數,但也可以通過 create 函數聲明特定參數。
if args.algo == 'MOG2':backSub = cv.createBackgroundSubtractorMOG2()
else:backSub = cv.createBackgroundSubtractorKNN()
- 使用
cv::VideoCapture
對象來讀取輸入視頻或輸入圖像序列。
capture = cv.VideoCapture(cv.samples.findFileOrKeep(args.input))
if not capture.isOpened():print('Unable to open: ' + args.input)exit(0)
- 每一幀都同時用于計算前景掩膜和更新背景模型。若需調整更新背景模型時的學習率,可通過向
apply
方法傳遞參數來設定特定學習率。
fgMask = backSub.apply(frame)
- 可以從
cv::VideoCapture
對象中提取當前幀號,并將其標記在當前幀的左上角。使用白色矩形框突出顯示黑色的幀號。
cv.rectangle(frame, (10, 2), (100,20), (255,255,255), -1)cv.putText(frame, str(capture.get(cv.CAP_PROP_POS_FRAMES)), (15, 15),cv.FONT_HERSHEY_SIMPLEX, 0.5 , (0,0,0))
- 我們準備展示當前輸入幀及處理結果。
cv.imshow('Frame', frame)cv.imshow('FG Mask', fgMask)
結果
對于 vtest.avi
視頻中的以下幀:
程序使用 MOG2 方法輸出的結果如下(灰色區域為檢測到的陰影):
程序使用 KNN 方法輸出的結果如下(灰色區域為檢測到的陰影):
參考文獻
- 背景模型挑戰賽(BMC)官網
- 前景/背景提取基準數據集 [281]`
本文檔由 doxygen 1.12.0 生成于 2025年4月30日 星期三 23:08:42,針對 OpenCV 項目
Meanshift 與 Camshift 算法
https://docs.opencv.org/4.x/d7/d00/tutorial_meanshift.html
上一教程: 如何使用背景減除方法
下一教程: 光流法
目標
在本章節中,
- 我們將學習用于視頻中目標追蹤的Meanshift和Camshift算法。
Meanshift算法
Meanshift背后的原理很簡單。假設你有一組點集(可以是像直方圖反向投影這樣的像素分布)。給你一個小窗口(可能是個圓形),你需要將這個窗口移動到像素密度最大(或點數最多)的區域。下面這張簡單的示意圖說明了這個過程:
初始窗口顯示為藍色圓圈,標注為"C1"。其原始中心用藍色矩形標記為"C1_o"。但如果計算窗口內所有點的質心,你會得到標記為"C1_r"的點(用小藍圈表示),這才是窗口的真實質心。顯然兩者不重合。因此移動窗口,使新窗口的圓形與之前的質心重合。再次計算新質心,很可能還是不匹配。于是繼續移動窗口,如此迭代直到窗口中心與其質心落在同一位置(或誤差足夠小)。最終得到的就是像素分布最密集的窗口,圖中用綠色圓圈標注為"C2"。可以看到,這個區域包含了最多的點。整個過程在下方的靜態圖像演示中更為直觀:
通常我們會傳入直方圖反向投影圖像和初始目標位置。當物體移動時,這種運動會在反向投影圖像中體現出來。Meanshift算法因此會將窗口移動到新的密度最大區域。
OpenCV 中的 Meanshift 算法
要在 OpenCV 中使用 meanshift 算法,首先需要設置目標區域并計算其直方圖,這樣我們才能在每一幀上通過反向投影目標區域來計算 meanshift。同時還需要提供窗口的初始位置。對于直方圖,這里僅考慮色調(Hue)分量。此外,為了避免低光照導致的錯誤值,可以使用 cv.inRange()
函數來剔除低光值。
可下載代碼: C++ | Java | Python
代碼概覽:
import numpy as np
import cv2 as cv
import argparseparser = argparse.ArgumentParser(description='This sample demonstrates the meanshift algorithm. \The example file can be downloaded from: \https://www.bogotobogo.com/python/OpenCV_Python/images/mean_shift_tracking/slow_traffic_small.mp4')
parser.add_argument('image', type=str, help='path to image file')
args = parser.parse_args()cap = cv.VideoCapture(args.image)# take first frame of the video
ret,frame = cap.read()# setup initial location of window
x, y, w, h = 300, 200, 100, 50 # simply hardcoded the values
track_window = (x, y, w, h)# set up the ROI for tracking
roi = frame[y:y+h, x:x+w]
hsv_roi = cv.cvtColor(roi, cv.COLOR_BGR2HSV)
mask = cv.inRange(hsv_roi, np.array((0., 60.,32.)), np.array((180.,255.,255.)))
roi_hist = cv.calcHist([hsv_roi],[0],mask,[180],[0,180])
cv.normalize(roi_hist,roi_hist,0,255,cv.NORM_MINMAX)# Setup the termination criteria, either 10 iteration or move by at least 1 pt
term_crit = ( cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 1 )while(1):ret, frame = cap.read()if ret == True:hsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV)dst = cv.calcBackProject([hsv],[0],roi_hist,[0,180],1)# apply meanshift to get the new locationret, track_window = cv.meanShift(dst, track_window, term_crit)# Draw it on imagex,y,w,h = track_windowimg2 = cv.rectangle(frame, (x,y), (x+w,y+h), 255,2)cv.imshow('img2',img2)k = cv.waitKey(30) & 0xffif k == 27:breakelse:break
以下是我使用的視頻中的三幀畫面:
圖片
Camshift
你仔細觀察過上一個結果嗎?這里有個問題。無論汽車離攝像頭很遠還是很近,我們的窗口始終保持相同大小。這顯然不夠理想。我們需要根據目標物體的大小和旋轉來自適應調整窗口尺寸。這個解決方案再次來自"OpenCV實驗室",由Gary Bradsky在其1998年發表的論文《用于感知用戶界面的計算機視覺人臉追蹤》中提出,稱為CAMshift(持續自適應均值漂移)算法[40]。
該算法首先應用均值漂移。當均值漂移收斂后,它會按照公式更新窗口尺寸,同時計算最佳擬合橢圓的方向。接著,算法會使用新的縮放搜索窗口和先前窗口位置再次應用均值漂移。這個過程會持續循環,直到達到所需的精度要求。
OpenCV 中的 Camshift 算法
- 可下載代碼:C++ | Java | Python
import numpy as np
import cv2 as cv
import argparseparser = argparse.ArgumentParser(description='This sample demonstrates the camshift algorithm. \The example file can be downloaded from: \https://www.bogotobogo.com/python/OpenCV_Python/images/mean_shift_tracking/slow_traffic_small.mp4')
parser.add_argument('image', type=str, help='path to image file')
args = parser.parse_args()cap = cv.VideoCapture(args.image)# take first frame of the video
ret,frame = cap.read()# setup initial location of window
x, y, w, h = 300, 200, 100, 50 # simply hardcoded the values
track_window = (x, y, w, h)# set up the ROI for tracking
roi = frame[y:y+h, x:x+w]
hsv_roi = cv.cvtColor(roi, cv.COLOR_BGR2HSV)
mask = cv.inRange(hsv_roi, np.array((0., 60.,32.)), np.array((180.,255.,255.)))
roi_hist = cv.calcHist([hsv_roi],[0],mask,[180],[0,180])
cv.normalize(roi_hist,roi_hist,0,255,cv.NORM_MINMAX)# Setup the termination criteria, either 10 iteration or move by at least 1 pt
term_crit = ( cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 1 )while(1):ret, frame = cap.read()if ret == True:hsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV)dst = cv.calcBackProject([hsv],[0],roi_hist,[0,180],1)# apply camshift to get the new locationret, track_window = cv.CamShift(dst, track_window, term_crit)# Draw it on imagepts = cv.boxPoints(ret)pts = np.int0(pts)img2 = cv.polylines(frame,[pts],True, 255,2)cv.imshow('img2',img2)k = cv.waitKey(30) & 0xffif k == 27:breakelse:break
結果顯示的三幀如下:
其他資源
- 法語維基百科關于Camshift的頁面(其中的兩個動畫取自該頁面)
- Bradski, G.R. 發表的論文《實時人臉與物體跟蹤作為感知用戶界面的組成部分》,收錄于1998年10月19-21日第四屆IEEE應用計算機視覺研討會(WACV '98)論文集,第214-219頁
練習
- OpenCV 附帶了一個 Python 示例,用于演示 camshift 的交互式功能。請使用它、修改它并理解它。
光流
https://docs.opencv.org/4.x/d4/dee/tutorial_optical_flow.html
上一教程: Meanshift 和 Camshift
下一教程: 級聯分類器
目標
在本章中,
- 我們將理解光流的概念及其使用Lucas-Kanade方法的估計原理。
- 我們將使用
cv.calcOpticalFlowPyrLK()
等函數來追蹤視頻中的特征點。 - 我們將通過
cv.calcOpticalFlowFarneback()
方法創建稠密光流場。
光流是指由于物體或相機運動導致圖像對象在連續兩幀之間產生的表觀運動模式。它是一個二維向量場,每個向量表示點從第一幀到第二幀的位移向量。請看下圖(圖片來源:維基百科光流條目):
圖中展示了一個球在連續5幀中的運動軌跡,箭頭表示其位移向量。光流在以下領域有廣泛應用:
- 運動恢復結構
- 視頻壓縮
- 視頻穩定…
光流計算基于以下假設:
- 物體的像素強度在連續幀之間不會改變
- 相鄰像素具有相似的運動
假設第一幀中某像素為 \(I(x,y,t)\)(注意這里新增了時間維度,之前我們只處理靜態圖像,所以不需要時間參數)。該像素在 \(dt\) 時間后的下一幀中移動了 \((dx,dy)\) 距離。由于像素相同且強度不變,可以得到:
\[I(x,y,t) = I(x+dx, y+dy, t+dt)\]
對右邊進行泰勒級數展開,消去相同項后除以 \(dt\),得到光流方程:
\[f_x u + f_y v + f_t = 0 \;\]
其中:
\[f_x = \frac{\partial f}{\partial x} \; ; \; f_y = \frac{\partial f}{\partial y}\]
\[u = \frac{dx}{dt} \; ; \; v = \frac{dy}{dt}\]
該方程中,\(f_x\) 和 \(f_y\) 是圖像梯度,\(f_t\) 是時間梯度。但 \((u,v)\) 未知,一個方程無法求解兩個未知數,因此發展出多種解決方法,Lucas-Kanade 就是其中之一。
Lucas-Kanade 方法
我們之前提到過一個假設:相鄰像素具有相似的運動。Lucas-Kanade 方法會在目標點周圍取一個 3x3 的像素塊,因此這 9 個點具有相同的運動。我們可以求出這 9 個點的 \((f_x, f_y, f_t)\)。于是問題轉化為用 9 個方程求解兩個未知數的超定方程組。通過最小二乘法可以獲得更好的解。以下是最終解的形式——這是一個二元方程組,求解后即可得到結果:
\[\begin{bmatrix} u \\ v \end{bmatrix} =
\begin{bmatrix}
\sum_{i}{f_{x_i}}^2 & \sum_{i}{f_{x_i} f_{y_i} } \\
\sum_{i}{f_{x_i} f_{y_i}} & \sum_{i}{f_{y_i}}^2
\end{bmatrix}^{-1}
\begin{bmatrix}
- \sum_{i}{f_{x_i} f_{t_i}} \\
- \sum_{i}{f_{y_i} f_{t_i}}
\end{bmatrix}\]
(注意逆矩陣與 Harris 角點檢測器的相似性。這說明角點更適合被追蹤。)
從用戶的角度來看,原理很簡單:我們提供一些待追蹤的點,就能獲得這些點的光流向量。但這種方法仍存在一些問題。目前我們處理的都是小幅度運動,當運動幅度較大時就會失效。為了解決這個問題,我們使用圖像金字塔結構。當我們在金字塔上層處理時,小幅運動被忽略,而大幅運動轉化為小幅運動。因此在金字塔上應用 Lucas-Kanade 方法,我們就能獲得帶有尺度信息的光流。
OpenCV中的Lucas-Kanade光流法
OpenCV通過單一函數 cv.calcOpticalFlowPyrLK()
提供了完整實現。我們將創建一個簡單的視頻點追蹤應用,首先使用 cv.goodFeaturesToTrack()
在第一幀中識別Shi-Tomasi角點,然后通過Lucas-Kanade光流法迭代追蹤這些點。
調用 cv.calcOpticalFlowPyrLK()
時需要傳入前一幀、前一幀的特征點以及當前幀。函數會返回當前幀的特征點坐標和狀態碼(找到點時狀態為1,否則為0)。在迭代過程中,我們將當前幀的特征點作為下一幀的"前一幀特征點"輸入。具體實現如下:
示例代碼下載:C++ | Java | Python
核心代碼概覽:
import numpy as np
import cv2 as cv
import argparseparser = argparse.ArgumentParser(description='This sample demonstrates Lucas-Kanade Optical Flow calculation. \The example file can be downloaded from: \https://www.bogotobogo.com/python/OpenCV_Python/images/mean_shift_tracking/slow_traffic_small.mp4')
parser.add_argument('image', type=str, help='path to image file')
args = parser.parse_args()cap = cv.VideoCapture(args.image)# params for ShiTomasi corner detection
feature_params = dict( maxCorners = 100,qualityLevel = 0.3,minDistance = 7,blockSize = 7 )# Parameters for lucas kanade optical flow
lk_params = dict( winSize = (15, 15),maxLevel = 2,criteria = (cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 0.03))# Create some random colors
color = np.random.randint(0, 255, (100, 3))# Take first frame and find corners in it
ret, old_frame = cap.read()
old_gray = cv.cvtColor(old_frame, cv.COLOR_BGR2GRAY)
p0 = cv.goodFeaturesToTrack(old_gray, mask = None, **feature_params)# Create a mask image for drawing purposes
mask = np.zeros_like(old_frame)while(1):ret, frame = cap.read()if not ret:print('No frames grabbed!')breakframe_gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)# calculate optical flowp1, st, err = cv.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)# Select good pointsif p1 is not None:good_new = p1[st==1]good_old = p0[st==1]# draw the tracksfor i, (new, old) in enumerate(zip(good_new, good_old)):a, b = new.ravel()c, d = old.ravel()mask = cv.line(mask, (int(a), int(b)), (int(c), int(d)), color[i].tolist(), 2)frame = cv.circle(frame, (int(a), int(b)), 5, color[i].tolist(), -1)img = cv.add(frame, mask)cv.imshow('frame', img)k = cv.waitKey(30) & 0xffif k == 27:break# Now update the previous frame and previous pointsold_gray = frame_gray.copy()p0 = good_new.reshape(-1, 1, 2)cv.destroyAllWindows()
這段代碼不會檢查下一個關鍵點的準確性。因此,即使某個特征點在圖像中消失,光流仍有可能找到一個看起來接近它的新點。為了實現更穩健的跟蹤,應該定期重新檢測角點。OpenCV示例中提供了一個相關樣例,它會每5幀檢測一次特征點,并對獲得的光流點進行反向檢查以篩選優質點。具體請查看 samples/python/lk_track.py。
以下是運行結果:
OpenCV中的稠密光流
Lucas-Kanade方法計算的是稀疏特征集的光流(在我們的例子中,是使用Shi-Tomasi算法檢測到的角點)。OpenCV提供了另一種算法來尋找稠密光流,它會計算幀中所有點的光流。該算法基于Gunnar Farneback在2003年發表的論文《基于多項式展開的雙幀運動估計》中提出的方法。
以下示例展示了如何使用上述算法查找稠密光流。我們會得到一個包含光流向量的2通道數組,然后計算它們的幅度和方向。為了更直觀地展示結果,我們對結果進行了顏色編碼:方向對應圖像的色調(Hue)值,幅度對應明度(Value)平面。具體代碼如下:
可下載代碼:C++ | Java | Python
import numpy as np
import cv2 as cv
cap = cv.VideoCapture(cv.samples.findFile("vtest.avi"))
ret, frame1 = cap.read()
prvs = cv.cvtColor(frame1, cv.COLOR_BGR2GRAY)
hsv = np.zeros_like(frame1)
hsv[..., 1] = 255
while(1):ret, frame2 = cap.read()if not ret:print('No frames grabbed!')breaknext = cv.cvtColor(frame2, cv.COLOR_BGR2GRAY)flow = cv.calcOpticalFlowFarneback(prvs, next, None, 0.5, 3, 15, 3, 5, 1.2, 0)mag, ang = cv.cartToPolar(flow[..., 0], flow[..., 1])hsv[..., 0] = ang*180/np.pi/2hsv[..., 2] = cv.normalize(mag, None, 0, 255, cv.NORM_MINMAX)bgr = cv.cvtColor(hsv, cv.COLOR_HSV2BGR)cv.imshow('frame2', bgr)k = cv.waitKey(30) & 0xffif k == 27:breakelif k == ord('s'):cv.imwrite('opticalfb.png', frame2)cv.imwrite('opticalhsv.png', bgr)prvs = nextcv.destroyAllWindows()
查看下方結果:
級聯分類器
https://docs.opencv.org/4.x/db/d28/tutorial_cascade_classifier.html
上一教程: 光流
下一教程: 級聯分類器訓練
原作者 | Ana Huamán |
兼容性 | OpenCV >= 3.0 |
目標
在本教程中:
- 我們將學習 Haar 級聯對象檢測的工作原理。
- 了解基于 Haar 特征級聯分類器進行人臉檢測和眼睛檢測的基礎知識。
- 使用
cv::CascadeClassifier
類來檢測視頻流中的對象。具體將使用以下函數:cv::CascadeClassifier::load
加載 .xml 分類器文件(支持 Haar 或 LBP 分類器)cv::CascadeClassifier::detectMultiScale
執行檢測任務。
理論
基于Haar特征的級聯分類器進行目標檢測是由Paul Viola和Michael Jones在2001年發表的論文《使用簡單特征增強級聯的快速目標檢測》中提出的一種有效方法。這是一種基于機器學習的方法,通過大量正樣本(包含人臉的圖像)和負樣本(不包含人臉的圖像)訓練級聯函數,進而用于檢測其他圖像中的目標。
我們將以人臉檢測為例進行說明。算法首先需要大量正樣本(人臉圖像)和負樣本(非人臉圖像)來訓練分類器。接著需要從中提取特征,這里使用下圖展示的Haar特征。這些特征類似于卷積核,每個特征值是通過計算黑色矩形區域像素和與白色矩形區域像素和的差值得到的。
算法會使用所有可能的核尺寸和位置來計算大量特征(想象一下計算量有多大?即使24x24的窗口也會產生超過16萬個特征)。為了高效計算每個特征中矩形區域的像素和,研究者引入了積分圖技術。無論圖像多大,積分圖都能將任意矩形區域的像素和計算簡化為僅涉及四個像素的操作,這極大地提升了計算效率。
但計算得到的特征中大部分是無關的。例如下圖所示,第一行展示的兩個有效特征:第一個特征捕捉到眼睛區域通常比鼻梁和臉頰區域更暗的特性,第二個特征利用了眼睛比鼻梁更暗的特性。而相同的檢測窗口應用于臉頰等其他區域則無效。那么如何從16萬+特征中篩選最優特征呢?這通過Adaboost算法實現。
具體實現時,所有特征會在訓練圖像上進行測試。每個特征會找到一個最佳閾值來區分人臉和非人臉。顯然會出現誤分類情況,我們選擇錯誤率最低的特征(即最能準確區分人臉與非人臉的特征)。這個過程并非簡單的一次篩選:初始時所有圖像權重相同,每次分類后增加誤分類圖像的權重,迭代進行直到達到預定精度或特征數量(最終分類器是這些弱分類器的加權組合,單個弱分類器效果有限,但組合后形成強分類器)。論文指出,僅200個特征就能達到95%的檢測準確率,最終系統使用了約6000個特征(從16萬+到6000,這是巨大的優化)。
現在檢測流程是:對圖像的每個24x24窗口應用6000個特征判斷是否為人臉。這顯然效率低下,為此研究者提出了創新方案——由于圖像中大部分區域是非人臉區域,他們設計了級聯分類器:將特征分組到不同階段逐級應用(前幾階段特征數很少),若窗口未通過某階段則立即丟棄,僅對可能包含人臉的窗口進行后續檢測。例如作者的檢測器包含38個階段共6000+特征,前五個階段分別有1、10、25、25和50個特征,平均每個子窗口只需評估10個特征。
這就是Viola-Jones人臉檢測算法的工作原理簡介。如需更多細節請參閱原論文或查看附加資源中的參考文獻。
OpenCV 中的 Haar 級聯檢測
OpenCV 提供了訓練方法(參見 Cascade Classifier Training
)或預訓練模型,這些模型可以通過 cv::CascadeClassifier::load
方法讀取。預訓練模型位于 OpenCV 安裝目錄的 data 文件夾中,也可以從這里獲取。
以下代碼示例將使用預訓練的 Haar 級聯模型來檢測圖像中的面部和眼睛。首先,創建一個 cv::CascadeClassifier
,并通過 cv::CascadeClassifier::load
方法加載所需的 XML 文件。隨后,使用 cv::CascadeClassifier::detectMultiScale
方法進行檢測,該方法會返回檢測到的面部或眼睛的邊界矩形。
本教程的代碼如下所示。你也可以從以下鏈接下載:C++ | Java | Python。
from __future__ import print_function
import cv2 as cv
import argparsedef detectAndDisplay(frame):frame_gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)frame_gray = cv.equalizeHist(frame_gray)#-- Detect facesfaces = face_cascade.detectMultiScale(frame_gray)for (x,y,w,h) in faces:center = (x + w//2, y + h//2)frame = cv.ellipse(frame, center, (w//2, h//2), 0, 0, 360, (255, 0, 255), 4)faceROI = frame_gray[y:y+h,x:x+w]#-- In each face, detect eyeseyes = eyes_cascade.detectMultiScale(faceROI)for (x2,y2,w2,h2) in eyes:eye_center = (x + x2 + w2//2, y + y2 + h2//2)radius = int(round((w2 + h2)*0.25))frame = cv.circle(frame, eye_center, radius, (255, 0, 0 ), 4)cv.imshow('Capture - Face detection', frame)parser = argparse.ArgumentParser(description='Code for Cascade Classifier tutorial.')
parser.add_argument('--face_cascade', help='Path to face cascade.', default='data/haarcascades/haarcascade_frontalface_alt.xml')
parser.add_argument('--eyes_cascade', help='Path to eyes cascade.', default='data/haarcascades/haarcascade_eye_tree_eyeglasses.xml')
parser.add_argument('--camera', help='Camera divide number.', type=int, default=0)
args = parser.parse_args()face_cascade_name = args.face_cascade
eyes_cascade_name = args.eyes_cascadeface_cascade = cv.CascadeClassifier()
eyes_cascade = cv.CascadeClassifier()#-- 1. Load the cascades
if not face_cascade.load(cv.samples.findFile(face_cascade_name)):print('--(!)Error loading face cascade')exit(0)
if not eyes_cascade.load(cv.samples.findFile(eyes_cascade_name)):print('--(!)Error loading eyes cascade')exit(0)camera_device = args.camera
#-- 2. Read the video stream
cap = cv.VideoCapture(camera_device)
if not cap.isOpened:print('--(!)Error opening video capture')exit(0)while True:ret, frame = cap.read()if frame is None:print('--(!) No captured frame -- Break!')breakdetectAndDisplay(frame)if cv.waitKey(10) == 27:break
運行結果
1、以下是運行上述代碼并使用內置攝像頭視頻流作為輸入的檢測效果:
請確保程序能正確找到以下文件的路徑:
haarcascade_frontalface_alt.xml
haarcascade_eye_tree_eyeglasses.xml
這些文件位于opencv/data/haarcascades
目錄下
2、這是使用LBP訓練文件lbpcascade_frontalface.xml
進行人臉檢測的效果(眼部檢測仍沿用教程中的原文件):
附加資源
1、Paul Viola 和 Michael J. Jones。《魯棒的實時人臉檢測》。國際計算機視覺雜志,57(2):137–154,2004年。[287]`
2、Rainer Lienhart 和 Jochen Maydt。《用于快速目標檢測的擴展Haar-like特征集》。收錄于《圖像處理。2002年國際會議論文集》,第1卷,I–900頁。IEEE,2002年。[168]`
3、視頻講座:人臉檢測與追蹤
4、關于人臉檢測的趣味訪談,受訪者:Adam Harvey
5、Adam Harvey在Vimeo上的作品:OpenCV人臉檢測:可視化解析
生成于2025年4月30日星期三 23:08:42,由 doxygen 1.12.0 為 OpenCV 生成
級聯分類器訓練
https://docs.opencv.org/4.x/dc/d88/tutorial_traincascade.html
上一篇教程: 級聯分類器
下一篇教程: 條形碼識別
簡介
使用弱分類器級聯增強模型主要包含兩個階段:訓練階段和檢測階段。基于HAAR或LBP模型的檢測階段已在目標檢測教程中詳細說明。本文檔將重點介紹如何訓練自定義弱分類器級聯模型的功能概述。本指南將逐步講解所有不同階段:收集訓練數據、準備訓練數據以及執行實際的模型訓練。
為配合本教程,我們將使用以下官方OpenCV應用程序:opencv_createsamples、opencv_annotation、opencv_traincascade和opencv_visualisation。
注意:自OpenCV 4.0起,Createsamples和traincascade功能已被禁用。如需訓練級聯分類器,建議使用3.4分支中的這些應用程序。3.4版本與4.x版本間的模型格式保持一致。
重要說明
-
如果你遇到任何提到舊版 opencv_haartraining 工具的教程(該工具已棄用且仍使用 OpenCV1.x 接口),請忽略該教程并堅持使用 opencv_traincascade 工具。此工具是新版本,采用 C++ 編寫,符合 OpenCV 2.x 和 OpenCV 3.x API。opencv_traincascade 同時支持類似 HAAR 的小波特征 [
286]
和 LBP(局部二值模式)[165
] 特征。與 HAAR 特征的浮點精度相比,LBP 特征提供整數精度,因此 LBP 的訓練和檢測速度比 HAAR 快數倍。關于 LBP 和 HAAR 的檢測質量,主要取決于使用的訓練數據和選擇的訓練參數。有可能訓練出一個基于 LBP 的分類器,其質量幾乎與基于 HAAR 的分類器相當,而訓練時間僅需后者的幾分之一。 -
OpenCV 2.x 和 OpenCV 3.x 中較新的級聯分類器檢測接口 (
cv::CascadeClassifier
) 支持新舊模型格式。如果出于某些原因你仍需使用舊接口,opencv_traincascade 甚至可以以舊格式保存(導出)訓練好的級聯分類器。至少模型的訓練可以在最穩定的接口中完成。 -
opencv_traincascade 應用程序可以使用 TBB 進行多線程處理。要在多核模式下使用此功能,必須啟用 TBB 支持來構建 OpenCV。
訓練數據準備
為了訓練一個弱分類器的增強級聯模型,我們需要準備兩組樣本數據:
- 正樣本集(包含您想要檢測的實際目標對象)
- 負樣本集(包含所有不希望檢測的內容)
負樣本集需要手動準備,而正樣本集可通過 opencv_createsamples 應用程序生成。
負樣本
負樣本取自任意圖像,這些圖像不包含你想要檢測的目標物體。生成負樣本的原始背景圖像需要列在一個專門的負樣本圖像文件中,每行包含一個圖像路徑(可以是絕對路徑或相對路徑)。需要注意的是,負樣本和樣本圖像也被稱為背景樣本或背景圖像,本文檔中這些術語可互換使用。
所描述的圖像尺寸可以各不相同。但每張圖像的尺寸應等于或大于預期的訓練窗口尺寸(通常對應模型維度,即目標物體的平均大小),因為這些圖像會被進一步分割成多個具有該訓練窗口尺寸的圖像樣本。
以下是一個負樣本描述文件的示例:
目錄結構:
/imgimg1.jpgimg2.jpg
bg.txt
文件 bg.txt:
img/img1.jpg
img/img2.jpg
你的負窗口樣本集將用于告訴機器學習步驟(這里指boosting算法),在尋找目標對象時應該忽略哪些內容。
正樣本
正樣本由 opencv_createsamples 應用程序創建。它們被用于 boosting 過程,以定義模型在尋找目標對象時應該關注的特征。該應用程序支持兩種生成正樣本數據集的方式:
- 可以從單個正樣本圖像生成大量正樣本。
- 可以自行提供所有正樣本,僅使用該工具進行裁剪、調整大小并將其轉換為 OpenCV 所需的二進制格式。
雖然第一種方法對于固定對象(如非常剛性的標志)效果尚可,但對于不太剛性的對象,它往往會很快失效。在這種情況下,我們建議使用第二種方法。許多網絡教程甚至指出,使用 opencv_createsamples 應用程序時,100 張真實對象圖像生成的模型可能比 1000 張人工生成的正樣本效果更好。但如果決定采用第一種方法,請注意以下幾點:
- 請注意,在將樣本提供給該應用程序之前,需要準備多個正樣本,因為它僅應用透視變換。
- 如果需要構建一個魯棒的模型,應確保樣本覆蓋對象類別中可能出現的各種變化。例如,在人臉檢測的情況下,應考慮不同種族、年齡組、情緒以及可能的胡須樣式。這一點在采用第二種方法時同樣適用。
第一種方法以一個對象圖像(例如公司標志)為輸入,通過隨機旋轉對象、改變圖像強度以及在任意背景上放置圖像,從給定的對象圖像生成大量正樣本。隨機性的程度和范圍可以通過 opencv_createsamples 應用程序的命令行參數控制。
命令行參數:
-vec <vec_file_name>
:包含訓練正樣本的輸出文件名。-img <image_file_name>
:源對象圖像(例如公司標志)。-bg <background_file_name>
:背景描述文件;包含用作對象隨機變形版本背景的圖像列表。-num <number_of_samples>
:要生成的正樣本數量。-bgcolor <background_color>
:背景顏色(當前假設為灰度圖像);背景顏色表示透明色。由于可能存在壓縮偽影,可以通過-bgthresh
指定顏色容差范圍。所有在bgcolor-bgthresh
和bgcolor+bgthresh
范圍內的像素均被視為透明。-bgthresh <background_color_threshold>
-inv
:如果指定,顏色將被反轉。-randinv
:如果指定,顏色將隨機反轉。-maxidev <max_intensity_deviation>
:前景樣本中像素的最大強度偏差。-maxxangle <max_x_rotation_angle>
:繞 x 軸的最大旋轉角度(以弧度為單位)。-maxyangle <max_y_rotation_angle>
:繞 y 軸的最大旋轉角度(以弧度為單位)。-maxzangle <max_z_rotation_angle>
:繞 z 軸的最大旋轉角度(以弧度為單位)。-show
:有用的調試選項。如果指定,將顯示每個樣本。按 Esc 鍵將繼續樣本創建過程而不顯示每個樣本。-w <sample_width>
:輸出樣本的寬度(以像素為單位)。-h <sample_height>
:輸出樣本的高度(以像素為單位)。
當以這種方式運行 opencv_createsamples 時,創建樣本對象實例的過程如下:給定的源圖像會繞所有三個軸隨機旋轉,旋轉角度受 -maxxangle
、-maxyangle
和 -maxzangle
限制。然后,強度在 [bg_color-bg_color_threshold; bg_color+bg_color_threshold]
范圍內的像素被視為透明。前景的強度會添加白噪聲。如果指定了 -inv
參數,則前景像素強度會被反轉。如果指定了 -randinv
參數,則算法會隨機決定是否對該樣本應用反轉。最后,生成的圖像會被放置在背景描述文件中的任意背景上,調整為由 -w
和 -h
指定的目標大小,并存儲到由 -vec
命令行選項指定的 vec 文件中。
正樣本也可以從一組預先標記的圖像中獲取,這是構建魯棒對象模型時的理想方式。該集合由一個類似于背景描述文件的文本文件描述。文件的每一行對應一個圖像,行的第一個元素是文件名,后跟對象標注的數量,然后是描述對象邊界矩形坐標的數字(x、y、寬度、高度)。
描述文件示例:
目錄結構:
/imgimg1.jpgimg2.jpg
info.dat
文件 info.dat:
img/img1.jpg 1 140 100 45 45
img/img2.jpg 2 100 200 50 50 50 30 25 25
圖片 img1.jpg 包含單個物體實例,其邊界矩形坐標為 (140, 100, 45, 45)。圖片 img2.jpg 包含兩個物體實例。
要從這類集合中創建正樣本,應使用 -info
參數替代 -img
:
-info <collection_file_name>
: 標注圖像集合的描述文件。
注意:此時諸如 -bg, -bgcolor, -bgthreshold, -inv, -randinv, -maxxangle, -maxyangle, -maxzangle
等參數將被忽略且不再使用。此情況下的樣本創建流程如下:從給定圖像中提取提供的邊界框區域作為物體實例,隨后將其縮放至目標樣本尺寸(由 -w
和 -h
定義),并存儲到 -vec
參數指定的輸出 vec 文件中。不應用任何形變處理,因此僅受影響的參數為 -w
、-h
、-show
和 -num
。
創建 -info
文件的手動過程也可通過 opencv_annotation 工具完成。該開源工具可直觀地在任意圖像中選取物體實例的感興趣區域。下一小節將詳細討論如何使用此應用程序。
額外說明
- 可以使用 opencv_createsamples 工具來檢查存儲在任意給定正樣本文件中的樣本。為此只需指定
-vec
、-w
和-h
參數即可。 - 這里提供了一個 vec 文件的示例
opencv/data/vec_files/trainingfaces_24-24.vec
。該文件可用于訓練窗口大小為-w 24 -h 24
的人臉檢測器。
使用 OpenCV 的集成標注工具
自 OpenCV 3.x 版本起,社區提供并維護了一款開源標注工具,用于生成 -info
文件。如果已構建 OpenCV 應用程序,可通過命令 opencv_annotation
調用該工具。
該工具使用起來非常簡單,它接受幾個必需參數和一些可選參數:
--annotations
(必需):指定存儲標注結果的 txt 文件路徑,該文件后續會傳遞給-info
參數 [示例 - /data/annotations.txt]--images
(必需):包含待標注對象圖片的文件夾路徑 [示例 - /data/testimages/]--maxWindowHeight
(可選):如果輸入圖像高度超過此處設定的分辨率,則通過--resizeFactor
縮放圖像以便于標注--resizeFactor
(可選):與--maxWindowHeight
參數配合使用時,指定圖像縮放比例因子
注意:可選參數必須搭配使用。下方展示了一個典型的使用命令示例
opencv_annotation --annotations=/path/to/annotations/file.txt --images=/path/to/image/folder/
該命令將啟動一個窗口,顯示第一張圖片和您的鼠標光標,用于進行標注操作。關于如何使用標注工具的視頻可參考此處。基本操作是通過幾個按鍵觸發動作:首先用鼠標左鍵選擇對象的第一個角點,持續繪制直到滿意為止,當再次點擊鼠標左鍵時停止繪制。每次選擇后您有以下選項:
- 按下
c
鍵:確認標注,標注線將變為綠色并確認已存儲 - 按下
d
鍵:從標注列表中刪除最后一個標注(便于移除錯誤標注) - 按下
n
鍵:繼續處理下一張圖片 - 按下
ESC
鍵:退出標注軟件
最終您將獲得一個可用的標注文件,該文件可傳遞給opencv_createsamples的-info
參數使用。
級聯訓練
下一步是基于預先準備好的正負樣本數據集,對弱分類器的增強級聯進行實際訓練。
opencv_traincascade應用程序的命令行參數按用途分組如下:
-
通用參數:
-data <cascade_dir_name>
:訓練好的分類器存儲路徑。該目錄需提前手動創建。-vec <vec_file_name>
:包含正樣本的vec文件(由opencv_createsamples工具生成)。-bg <background_file_name>
:背景描述文件,包含負樣本圖像。-numPos <number_of_positive_samples>
:每個分類器階段訓練使用的正樣本數量。-numNeg <number_of_negative_samples>
:每個分類器階段訓練使用的負樣本數量。-numStages <number_of_stages>
:需要訓練的級聯階段數。-precalcValBufSize <precalculated_vals_buffer_size_in_Mb>
:預計算特征值緩沖區大小(MB)。分配內存越大訓練越快,但需注意-precalcValBufSize
和-precalcIdxBufSize
總和不應超過可用系統內存。-precalcIdxBufSize <precalculated_idxs_buffer_size_in_Mb>
:預計算特征索引緩沖區大小(MB)。分配內存越大訓練越快,但需注意-precalcValBufSize
和-precalcIdxBufSize
總和不應超過可用系統內存。-baseFormatSave
:僅適用于Haar特征。若指定,級聯將以舊格式保存。此參數僅為向后兼容而保留,使依賴舊接口的用戶至少能通過新接口訓練模型。-numThreads <max_number_of_threads>
:訓練時使用的最大線程數。實際線程數可能更低,取決于設備和編譯選項。默認情況下,若OpenCV編譯時支持TBB(此優化的必要條件),將選擇最大可用線程數。-acceptanceRatioBreakValue <break_value>
:用于控制模型學習精度和停止條件。建議訓練不超過10e-5,防止模型在訓練數據上過擬合。默認值-1表示禁用此功能。
-
級聯參數:
-stageType <BOOST(default)>
:階段類型。目前僅支持增強分類器作為階段類型。-featureType<{HAAR(default), LBP}>
:特征類型:HAAR表示類Haar特征,LBP表示局部二值模式。-w <sampleWidth>
:訓練樣本寬度(像素)。必須與創建訓練樣本時(opencv_createsamples工具)使用的值嚴格一致。-h <sampleHeight>
:訓練樣本高度(像素)。必須與創建訓練樣本時(opencv_createsamples工具)使用的值嚴格一致。
-
增強分類器參數:
-bt <{DAB, RAB, LB, GAB(default)}>
:增強分類器類型:DAB-離散AdaBoost,RAB-實數AdaBoost,LB-LogitBoost,GAB-溫和AdaBoost。-minHitRate <min_hit_rate>
:每個分類器階段的最小期望命中率。總命中率可估算為(min_hit_rate^number_of_stages),參見[287]§4.1。-maxFalseAlarmRate <max_false_alarm_rate>
:每個分類器階段的最大允許誤檢率。總誤檢率可估算為(max_false_alarm_rate^number_of_stages),參見[287]§4.1。-weightTrimRate <weight_trim_rate>
:指定是否使用權重修剪及其比率。推薦值為0.95。-maxDepth <max_depth_of_weak_tree>
:弱樹的最大深度。推薦值為1(即樹樁結構)。-maxWeakCount <max_weak_tree_count>
:每個級聯階段的弱樹最大數量。增強分類器(階段)將包含足夠多的弱樹(≤maxWeakCount)以滿足給定的-maxFalseAlarmRate
。
-
類Haar特征參數:
-mode <BASIC (default) | CORE | ALL>
:選擇訓練使用的Haar特征集類型。BASIC僅使用直立特征,ALL使用包含直立和45度旋轉特征的完整集合。詳見[168]。
-
局部二值模式參數:無特定參數。
當opencv_traincascade應用程序完成工作后,訓練好的級聯將保存在-data
文件夾的cascade.xml
文件中。該文件夾中的其他文件是為訓練中斷情況創建的,訓練完成后可刪除。
訓練完成后,即可測試您的級聯分類器!
可視化級聯分類器
有時可視化訓練好的級聯分類器會很有幫助,可以查看它選擇了哪些特征以及各階段的復雜度。為此,OpenCV 提供了一個 opencv_visualisation
應用程序。該應用程序包含以下命令:
--image
(必選):對象模型的參考圖像路徑。這應該是一個標注圖像,其尺寸 [-w
,-h
] 需與傳遞給opencv_createsamples
和opencv_traincascade
應用程序的參數一致。--model
(必選):訓練好的模型路徑,該模型應位于opencv_traincascade
應用程序-data
參數指定的文件夾中。--data
(可選):如果提供了數據文件夾(需事先手動創建),將存儲階段輸出和特征視頻。
下面是一個示例命令:
opencv_visualisation --image=/data/object.png --model=/data/model.xml --data=/data/result/
當前可視化工具的一些限制
-
僅支持使用opencv_traincascade工具訓練的級聯分類器模型,且決策樹必須為樁決策樹[默認設置]。
-
提供的圖像必須是原始模型尺寸的樣本窗口,需通過
--image
參數傳入。
以安吉麗娜·朱莉的特定窗口運行HAAR/LBP人臉模型的示例(該窗口與級聯分類器文件采用相同預處理流程)–>24x24像素圖像、灰度轉換及直方圖均衡化:
為每個階段的可視化特征制作了視頻:
每個階段的特征被存儲為圖像以便后續驗證:
本作品由StevenPuttemans為OpenCV 3 Blueprints創建,經Packt Publishing同意整合至OpenCV。
由doxygen 1.12.0生成于2025年4月30日周三 23:08:42,適用于OpenCV
條形碼識別
https://docs.opencv.org/4.x/d6/d25/tutorial_barcode_detect_and_decode.html
上一教程: 級聯分類器訓練
下一教程: 支持向量機簡介
兼容性 | OpenCV >= 4.8 |
目標
在本章中,我們將熟悉 OpenCV 中可用的條形碼檢測和解碼方法。
基礎概念
條形碼是現實生活中識別商品的主要技術手段。常見的條形碼由反射率差異顯著的黑白條平行排列組成。條形碼識別的原理是:通過水平方向掃描條形碼,獲取由不同寬度和顏色的條組成的二進制碼串,即條形碼的編碼信息。通過與多種條形碼編碼規則匹配,可以解碼出條形碼的內容。目前我們支持 EAN-8、EAN-13、UPC-A 和 UPC-E 標準。
參考鏈接:https://en.wikipedia.org/wiki/Universal_Product_Code 和 https://en.wikipedia.org/wiki/International_Article_Number
相關論文:[304]](https://docs.opencv.org/4.x/d0/de3/citelist.html#CITEREF_xiangmin2015research) 、[[144]
、[21]`
代碼示例
主類
本文介紹了多種用于條形碼識別的算法。
在編寫代碼時,我們首先需要創建一個 cv::barcode::BarcodeDetector
對象。該對象主要包含三個成員函數,我們將在下文進行詳細介紹。
初始化
用戶可選擇性地構建帶有超分辨率模型的條碼檢測器,該模型需從 https://github.com/WeChatCV/opencv_3rdparty/tree/wechat_qrcode 下載(sr.caffemodel
、sr.prototxt
)。
try{app.bardet = makePtr<barcode::BarcodeDetector>(sr_prototxt, sr_model);}catch (const std::exception& e){cout <<"\n---------------------------------------------------------------\n""Failed to initialize super resolution.\n""Please, download 'sr.*' from\n""https://github.com/WeChatCV/opencv_3rdparty/tree/wechat_qrcode\n""and put them into the current directory.\n""Or you can leave sr_prototxt and sr_model unspecified.\n""---------------------------------------------------------------\n";cout << e.what() << endl;return -1;}
我們需要創建變量來存儲輸出結果。
vector<Point> corners;vector<string> decode_info;vector<string> decode_type;
檢測
cv::barcode::BarcodeDetector::detect
方法采用基于方向一致性的算法。首先,計算每個像素的平均平方梯度[21]`](https://docs.opencv.org/4.x/d0/de3/citelist.html#CITEREF_bazen2002systematic)。接著,將圖像劃分為方形區塊,分別計算各區塊的梯度方向一致性和平均梯度方向。然后,連接所有具有高梯度方向一致性且梯度方向相似的區塊。在此階段,采用多尺度區塊來捕捉不同尺寸條形碼的梯度分布,并應用非極大值抑制來過濾重復的候選區域。最后,使用[`cv::minAreaRect`框定感興趣區域,并輸出矩形的角點坐標。
在輸入圖像中檢測編碼,并輸出檢測到的矩形角點:
bardet->detectMulti(frame, corners);
解碼
cv::barcode::BarcodeDetector::decode
方法首先會對圖像進行超分辨率放大(可選操作,當圖像尺寸小于閾值時),然后銳化圖像并通過OTSU或局部二值化處理進行二值化。接著,該方法通過匹配指定條形碼模式的相似度來讀取條形碼內容。
檢測與解碼
cv::barcode::BarcodeDetector::detectAndDecode
將 detect
和 decode
功能整合在單次調用中。以下簡單示例展示了如何使用該函數:
bardet->detectAndDecodeWithType(frame, decode_info, decode_type, corners);
可視化結果:
for (size_t i = 0; i < corners.size(); i += 4){const size_t idx = i / 4;const bool isDecodable = idx < decode_info.size()&& idx < decode_type.size()&& !decode_type[idx].empty();const Scalar lineColor = isDecodable ? greenColor : redColor;// draw barcode rectanglevector<Point> contour(corners.begin() + i, corners.begin() + i + 4);const vector< vector<Point> > contours {contour};drawContours(frame, contours, 0, lineColor, 1);// draw verticesfor (size_t j = 0; j < 4; j++)circle(frame, contour[j], 2, randColor(), -1);// write decoded textif (isDecodable){ostringstream buf;buf << "[" << decode_type[idx] << "] " << decode_info[idx];putText(frame, buf.str(), contour[1], FONT_HERSHEY_COMPLEX, 0.8, yellowColor, 1);}}
結果
原始圖像:
檢測后:
由 doxygen 1.12.0 生成于 2025年4月30日 星期三 23:08:42 用于 OpenCV
支持向量機簡介
https://docs.opencv.org/4.x/d1/d73/tutorial_introduction_to_svm.html
上一教程: 條形碼識別
下一教程: 非線性可分數據的支持向量機
原作者 | Fernando Iglesias García |
兼容性 | OpenCV >= 3.0 |
目標
在本教程中,您將學習如何:
- 使用OpenCV函數
cv::ml::SVM::train
基于支持向量機(SVM)構建分類器 - 使用
cv::ml::SVM::predict
函數測試分類器性能
什么是支持向量機?
支持向量機(SVM)是一種通過分離超平面定義的判別式分類器。簡而言之,給定帶有標簽的訓練數據(監督學習),該算法會輸出一個最優超平面,用于對新樣本進行分類。
這個超平面在什么意義下是最優的呢?讓我們考慮以下簡單問題:
對于線性可分的二維點集,這些點屬于兩個類別之一,找到一條分隔直線。
注意:本例中我們處理的是笛卡爾平面中的直線和點,而非高維空間中的超平面和向量。這是問題的簡化形式。需要理解的是,這樣做僅因為從易于想象的例子出發更符合直覺。但相同的概念適用于樣本分類空間維度高于二維的任務。
從上圖可見,存在多條直線都能解決問題。其中是否有某條直線比其他更好?我們可以直觀地定義一個標準來評估這些直線的優劣:若某條直線過于接近某些點,則其表現不佳,因為它會對噪聲敏感且泛化能力差。 因此,我們的目標是找到一條盡可能遠離所有點的直線。
于是,SVM算法的核心在于尋找那個使訓練樣本最小距離最大化的超平面。這個距離的兩倍在SVM理論中有一個重要名稱——間隔。因此,最優分隔超平面就是能夠最大化訓練數據間隔的那個。
最優超平面是如何計算的?
讓我們先介紹用于形式化定義超平面的符號表示:
\[f(x) = \beta_{0} + \beta^{T} x,\]
其中 \(\beta\) 稱為權重向量,\(\beta_{0}\) 稱為偏置項。
注:關于超平面更深入的描述,可參考 T. Hastie、R. Tibshirani 和 J. H. Friedman 所著《統計學習基礎》第 4.5 節(分離超平面)([274]`)。
最優超平面可以通過縮放 \(\beta\) 和 \(\beta_{0}\) 以無限多種方式表示。按照慣例,我們選擇以下特定表示形式:
\[|\beta_{0} + \beta^{T} x| = 1\]
其中 \(x\) 代表距離超平面最近的訓練樣本。通常,這些最接近超平面的訓練樣本被稱為支持向量。這種表示形式稱為規范超平面。
根據幾何學結論,點 \(x\) 到超平面 \((\beta, \beta_{0})\) 的距離公式為:
\[\mathrm{distance} = \frac{|\beta_{0} + \beta^{T} x|}{||\beta||}.\]
特別地,對于規范超平面,分子等于 1,因此支持向量的距離為:
\[\mathrm{distance}{\text{支持向量}} = \frac{|\beta{0} + \beta^{T} x|}{||\beta||} = \frac{1}{||\beta||}.\]
前文提到的間隔(此處記作 \(M\))是最接近樣本距離的兩倍:
\[M = \frac{2}{||\beta||}\]
最終,最大化 \(M\) 的問題等價于在特定約束條件下最小化函數 \(L(\beta)\)。這些約束條件要求超平面必須正確分類所有訓練樣本 \(x_{i}\)。形式化表示為:
\[\min_{\beta, \beta_{0}} L(\beta) = \frac{1}{2}||\beta||^{2} \text{ 約束條件為 } y_{i}(\beta^{T} x_{i} + \beta_{0}) \geq 1 \text{ } \forall i,\]
其中 \(y_{i}\) 表示各訓練樣本的標簽。
這是一個拉格朗日優化問題,可通過拉格朗日乘數法求解,從而得到最優超平面的權重向量 \(\beta\) 和偏置項 \(\beta_{0}\)。
源代碼
可下載代碼:C++ | Java | Python
代碼概覽:
import cv2 as cv
import numpy as np# Set up training datalabels = np.array([1, -1, -1, -1])
trainingData = np.matrix([[501, 10], [255, 10], [501, 255], [10, 501]], dtype=np.float32)# Train the SVMsvm = cv.ml.SVM_create()
svm.setType(cv.ml.SVM_C_SVC)
svm.setKernel(cv.ml.SVM_LINEAR)
svm.setTermCriteria((cv.TERM_CRITERIA_MAX_ITER, 100, 1e-6))svm.train(trainingData, cv.ml.ROW_SAMPLE, labels)# Data for visual representation
width = 512
height = 512
image = np.zeros((height, width, 3), dtype=np.uint8)# Show the decision regions given by the SVMgreen = (0,255,0)
blue = (255,0,0)
for i in range(image.shape[0]):for j in range(image.shape[1]):sampleMat = np.matrix([[j,i]], dtype=np.float32)response = svm.predict(sampleMat)[1]if response == 1:image[i,j] = greenelif response == -1:image[i,j] = blue# Show the training datathickness = -1
cv.circle(image, (501, 10), 5, ( 0, 0, 0), thickness)
cv.circle(image, (255, 10), 5, (255, 255, 255), thickness)
cv.circle(image, (501, 255), 5, (255, 255, 255), thickness)
cv.circle(image, ( 10, 501), 5, (255, 255, 255), thickness)# Show support vectorsthickness = 2
sv = svm.getUncompressedSupportVectors()for i in range(sv.shape[0]):cv.circle(image, (int(sv[i,0]), int(sv[i,1])), 6, (128, 128, 128), thickness)cv.imwrite('result.png', image) # save the imagecv.imshow('SVM Simple Example', image) # show it to the user
cv.waitKey()
說明
設置訓練數據
本練習的訓練數據由一組標記的二維點組成,這些點屬于兩個不同類別之一;其中一個類別包含一個點,另一個類別包含三個點。
labels = np.array([1, -1, -1, -1])
trainingData = np.matrix([[501, 10], [255, 10], [501, 255], [10, 501]], dtype=np.float32)
后續將使用的 cv::ml::SVM::train
函數要求訓練數據以浮點型 cv::Mat
對象的形式存儲。因此,我們需要根據上面定義的數組來創建這些對象:
labels = np.array([1, -1, -1, -1])
trainingData = np.matrix([[501, 10], [255, 10], [501, 255], [10, 501]], dtype=np.float32)
設置SVM參數
在本教程中,我們介紹了最簡單情況下SVM的理論,即訓練樣本被分為線性可分的兩個類別。然而,SVM可以應用于各種不同的問題(例如非線性可分數據的問題,使用核函數提升樣本維度的SVM等)。因此,在訓練SVM之前,我們需要定義一些參數。這些參數存儲在類cv::ml::SVM
的對象中。
svm = cv.ml.SVM_create()
svm.setType(cv.ml.SVM_C_SVC)
svm.setKernel(cv.ml.SVM_LINEAR)
svm.setTermCriteria((cv.TERM_CRITERIA_MAX_ITER, 100, 1e-6))
以下是翻譯后的中文內容:
-
SVM類型選擇。這里我們選用C_SVC`類型,該類型適用于多類分類(n≥2)。其重要特性是能處理類別不完全分離的情況(即訓練數據非線性可分)。雖然當前數據是線性可分的,這個特性并不關鍵,但我們仍選擇該類型,因為它是實際應用中最常用的SVM類型。
-
核函數類型。由于當前訓練數據特性,我們尚未討論核函數。不過簡要說明其核心思想:核函數通過對訓練數據進行映射,增強其與線性可分數據的相似性。這種映射通過提升數據維度實現,并借助核函數高效完成。此處我們選擇LINEAR`](https://docs.opencv.org/4.x/d1/d2d/classcv_1_1ml_1_1SVM.html#aad7f1aaccced3c33bb256640910a0e56ab92a19ab0c193735c3fd71f938dd87e7)類型,表示不進行任何映射。該參數通過[`cv::ml::SVM::setKernel`方法設置。
-
算法終止條件。SVM訓練過程通過迭代方式求解約束二次優化問題實現。這里我們指定最大迭代次數和容錯閾值,允許算法在未計算出最優超平面時提前終止。該參數通過
cv::TermCriteria
結構體定義。 -
訓練SVM模型。調用
cv::ml::SVM::train
方法構建SVM模型。
svm.train(trainingData, cv.ml.ROW_SAMPLE, labels)
SVM分類的區域
方法 cv::ml::SVM::predict
用于通過訓練好的SVM對輸入樣本進行分類。在本例中,我們使用該方法根據SVM的預測結果對空間進行著色。換句話說,我們將圖像遍歷,將其像素解釋為笛卡爾平面上的點。每個點根據SVM預測的類別進行著色:如果屬于標簽為1的類別則顯示為綠色,如果屬于標簽為-1的類別則顯示為藍色。
green = (0,255,0)
blue = (255,0,0)
for i in range(image.shape[0]):for j in range(image.shape[1]):sampleMat = np.matrix([[j,i]], dtype=np.float32)response = svm.predict(sampleMat)[1]if response == 1:image[i,j] = greenelif response == -1:image[i,j] = blue
支持向量
這里我們采用幾種方法來獲取支持向量的相關信息。方法 cv::ml::SVM::getSupportVectors
可獲取所有的支持向量。我們運用該方法來識別作為支持向量的訓練樣本,并將其突出顯示。
thickness = 2
sv = svm.getUncompressedSupportVectors()for i in range(sv.shape[0]):cv.circle(image, (int(sv[i,0]), int(sv[i,1])), 6, (128, 128, 128), thickness)
實驗結果
- 代碼打開一張圖片,展示兩個類別的訓練樣本。其中一個類別的樣本點用白色圓圈表示,另一個類別則用黑色圓圈表示。
- 訓練支持向量機(SVM)模型后,對圖像所有像素進行分類。結果顯示圖像被劃分為藍色區域和綠色區域,兩個區域之間的分界線即為最優分隔超平面。
- 最后通過在訓練樣本周圍顯示灰色圓環來展示支持向量。
本文檔由 doxygen 1.12.0 生成于 2025年4月30日 星期三 23:08:42,針對 OpenCV 庫
支持向量機處理非線性可分數據
https://docs.opencv.org/4.x/d0/dcc/tutorial_non_linear_svms.html
上一教程: 支持向量機簡介
下一教程: 主成分分析(PCA)簡介
原作者 | Fernando Iglesias García |
兼容性 | OpenCV >= 3.0 |
目標
在本教程中,您將學習:
- 當訓練數據無法線性分離時,如何為支持向量機(SVM)定義優化問題。
- 如何配置參數以使您的SVM適應此類問題。
動機
為什么擴展SVM優化問題以處理非線性可分的訓練數據是有意義的?在計算機視覺中使用SVM的大多數應用場景中,我們需要的工具比簡單的線性分類器更強大。這是因為在這些任務中,訓練數據很少能通過超平面實現完全分離。
以人臉檢測任務為例。這類任務的訓練數據由兩部分組成:包含人臉的圖像集和不含人臉的圖像集(即除人臉外的所有其他事物)。這類訓練數據過于復雜,我們很難找到一種樣本表示方式(即特征向量),使得所有人臉樣本能與非人臉樣本實現線性可分。
優化問題的擴展
使用支持向量機(SVM)時,我們會得到一個分隔超平面。由于訓練數據現在是非線性可分的,我們必須承認找到的超平面會錯誤分類部分樣本。這種誤分類成為優化問題中必須考慮的新變量。新模型需要同時滿足兩個要求:既要找到能提供最大邊界的超平面,又要通過限制過多分類錯誤來正確泛化訓練數據。
我們從尋找最大化邊界的超平面優化問題出發(這在之前的教程《支持向量機簡介》中已說明):
\[\min_{\beta, \beta_{0}} L(\beta) = \frac{1}{2}||\beta||^{2} \text{ 約束條件 } y_{i}(\beta^{T} x_{i} + \beta_{0}) \geq 1 \text{ } \forall i\]
有多種方法可以修改此模型以考慮誤分類錯誤。例如,可以嘗試最小化相同量加上一個常數乘以訓練數據中的誤分類錯誤數,即:
\[\min ||\beta||^{2} + C \text{(誤分類錯誤數)}\]
然而,這并不是一個很好的解決方案,原因之一是我們沒有區分那些距離其正確決策區域較近的誤分類樣本和那些距離較遠的樣本。因此,更好的解決方案應考慮誤分類樣本到其正確決策區域的距離,即:
\[\min ||\beta||^{2} + C \text{(誤分類樣本到其正確區域的距離)}\]
對于訓練數據中的每個樣本,定義一個新參數\(\xi_{i}\)。每個參數表示對應訓練樣本到其正確決策區域的距離。下圖展示了兩類非線性可分的訓練數據、一個分隔超平面以及誤分類樣本到其正確區域的距離。
注意:圖中僅顯示了誤分類樣本的距離。其余樣本的距離為零,因為它們已位于正確的決策區域內。圖中出現的紅線和藍線是每個決策區域的邊界。非常重要的是要認識到,每個\(\xi_{i}\)表示一個誤分類訓練樣本到其適當區域邊界的距離。
最終,優化問題的新表述為:
\[\min_{\beta, \beta_{0}} L(\beta) = ||\beta||^{2} + C \sum_{i} {\xi_{i}} \text{ 約束條件 } y_{i}(\beta^{T} x_{i} + \beta_{0}) \geq 1 - \xi_{i} \text{ 且 } \xi_{i} \geq 0 \text{ } \forall i\]
如何選擇參數C?顯然,這個問題的答案取決于訓練數據的分布。雖然沒有通用的答案,但以下規則很有幫助:
- 較大的C值會給出較少誤分類錯誤但邊界較小的解。這種情況下,誤分類錯誤的代價較高。由于優化的目標是最小化參數,因此允許的誤分類錯誤較少。
- 較小的C值會給出邊界較大但分類錯誤較多的解。此時,最小化過程不太關注求和項,而更側重于尋找具有較大邊界的超平面。
源代碼
你可以在OpenCV源碼庫的samples/cpp/tutorial_code/ml/non_linear_svms
目錄下找到源代碼,或者從這里下載。
可下載代碼: C++ | Java | Python
代碼概覽:
from __future__ import print_function
import cv2 as cv
import numpy as np
import random as rngNTRAINING_SAMPLES = 100 # Number of training samples per class
FRAC_LINEAR_SEP = 0.9 # Fraction of samples which compose the linear separable part# Data for visual representation
WIDTH = 512
HEIGHT = 512
I = np.zeros((HEIGHT, WIDTH, 3), dtype=np.uint8)# --------------------- 1. Set up training data randomly ---------------------------------------
trainData = np.empty((2*NTRAINING_SAMPLES, 2), dtype=np.float32)
labels = np.empty((2*NTRAINING_SAMPLES, 1), dtype=np.int32)rng.seed(100) # Random value generation class# Set up the linearly separable part of the training data
nLinearSamples = int(FRAC_LINEAR_SEP * NTRAINING_SAMPLES)trainClass = trainData[0:nLinearSamples,:]
# The x coordinate of the points is in [0, 0.4)
c = trainClass[:,0:1]
c[:] = np.random.uniform(0.0, 0.4 * WIDTH, c.shape)
# The y coordinate of the points is in [0, 1)
c = trainClass[:,1:2]
c[:] = np.random.uniform(0.0, HEIGHT, c.shape)# Generate random points for the class 2
trainClass = trainData[2*NTRAINING_SAMPLES-nLinearSamples:2*NTRAINING_SAMPLES,:]
# The x coordinate of the points is in [0.6, 1]
c = trainClass[:,0:1]
c[:] = np.random.uniform(0.6*WIDTH, WIDTH, c.shape)
# The y coordinate of the points is in [0, 1)
c = trainClass[:,1:2]
c[:] = np.random.uniform(0.0, HEIGHT, c.shape)#------------------ Set up the non-linearly separable part of the training data ---------------trainClass = trainData[nLinearSamples:2*NTRAINING_SAMPLES-nLinearSamples,:]
# The x coordinate of the points is in [0.4, 0.6)
c = trainClass[:,0:1]
c[:] = np.random.uniform(0.4*WIDTH, 0.6*WIDTH, c.shape)
# The y coordinate of the points is in [0, 1)
c = trainClass[:,1:2]
c[:] = np.random.uniform(0.0, HEIGHT, c.shape)#------------------------- Set up the labels for the classes ---------------------------------
labels[0:NTRAINING_SAMPLES,:] = 1 # Class 1
labels[NTRAINING_SAMPLES:2*NTRAINING_SAMPLES,:] = 2 # Class 2#------------------------ 2. Set up the support vector machines parameters --------------------
print('Starting training process')svm = cv.ml.SVM_create()
svm.setType(cv.ml.SVM_C_SVC)
svm.setC(0.1)
svm.setKernel(cv.ml.SVM_LINEAR)
svm.setTermCriteria((cv.TERM_CRITERIA_MAX_ITER, int(1e7), 1e-6))#------------------------ 3. Train the svm ----------------------------------------------------svm.train(trainData, cv.ml.ROW_SAMPLE, labels)print('Finished training process')#------------------------ 4. Show the decision regions ----------------------------------------green = (0,100,0)
blue = (100,0,0)
for i in range(I.shape[0]):for j in range(I.shape[1]):sampleMat = np.matrix([[j,i]], dtype=np.float32)response = svm.predict(sampleMat)[1]if response == 1:I[i,j] = greenelif response == 2:I[i,j] = blue#----------------------- 5. Show the training data --------------------------------------------thick = -1
# Class 1
for i in range(NTRAINING_SAMPLES):px = trainData[i,0]py = trainData[i,1]cv.circle(I, (int(px), int(py)), 3, (0, 255, 0), thick)# Class 2
for i in range(NTRAINING_SAMPLES, 2*NTRAINING_SAMPLES):px = trainData[i,0]py = trainData[i,1]cv.circle(I, (int(px), int(py)), 3, (255, 0, 0), thick)#------------------------- 6. Show support vectors --------------------------------------------thick = 2
sv = svm.getUncompressedSupportVectors()for i in range(sv.shape[0]):cv.circle(I, (int(sv[i,0]), int(sv[i,1])), 6, (128, 128, 128), thick)cv.imwrite('result.png', I) # save the Image
cv.imshow('SVM for Non-Linear Training Data', I) # show it to the user
cv.waitKey()
說明
設置訓練數據
本練習的訓練數據由一組標記的二維點組成,這些點屬于兩個不同類別之一。為了使練習更具吸引力,訓練數據是使用均勻概率密度函數(PDF)隨機生成的。
我們將訓練數據的生成分為兩個主要部分。
在第一部分中,我們為兩個類別生成線性可分的數據。
trainClass = trainData[0:nLinearSamples,:]
# The x coordinate of the points is in [0, 0.4)
c = trainClass[:,0:1]
c[:] = np.random.uniform(0.0, 0.4 * WIDTH, c.shape)
# The y coordinate of the points is in [0, 1)
c = trainClass[:,1:2]
c[:] = np.random.uniform(0.0, HEIGHT, c.shape)# Generate random points for the class 2
trainClass = trainData[2*NTRAINING_SAMPLES-nLinearSamples:2*NTRAINING_SAMPLES,:]
# The x coordinate of the points is in [0.6, 1]
c = trainClass[:,0:1]
c[:] = np.random.uniform(0.6*WIDTH, WIDTH, c.shape)
# The y coordinate of the points is in [0, 1)
c = trainClass[:,1:2]
c[:] = np.random.uniform(0.0, HEIGHT, c.shape)
設置支持向量機參數
注意:在前一篇教程支持向量機簡介中,已經對cv::ml::SVM
類的屬性進行了說明。在訓練SVM之前,我們需要先配置這些參數。
svm = cv.ml.SVM_create()
svm.setType(cv.ml.SVM_C_SVC)
svm.setC(0.1)
svm.setKernel(cv.ml.SVM_LINEAR)
svm.setTermCriteria((cv.TERM_CRITERIA_MAX_ITER, int(1e7), 1e-6))
我們在此處的配置與參考教程(支持向量機入門)僅存在兩點差異:
- C參數。這里選擇較小的參數值以避免在優化過程中對分類錯誤施加過重懲罰。這種做法的初衷是為了獲得更接近直覺預期的解。但建議通過調整該參數來深入理解問題本質。
注意:當前類間重疊區域中的樣本點極少。通過減小FRAC_LINEAR_SEP值可以增加點密度,從而深入探究C參數的影響效果。
-
算法終止條件。為正確解決非線性可分訓練數據問題,必須大幅增加最大迭代次數。具體而言,我們將該值提高了五個數量級。
-
訓練SVM模型
調用方法cv::ml::SVM::train
構建SVM模型。請注意訓練過程可能耗時較長,運行程序時請保持耐心。
Python
svm.train(trainData, cv.ml.ROW_SAMPLE, labels)
顯示決策區域
方法 cv::ml::SVM::predict
用于通過訓練好的 SVM 對輸入樣本進行分類。在本示例中,我們利用該方法根據 SVM 的預測結果對空間進行著色。換句話說,程序會遍歷圖像,將其像素解釋為笛卡爾平面上的點。每個點根據 SVM 預測的類別進行著色:若屬于標簽為 1 的類別則顯示深綠色,若屬于標簽為 2 的類別則顯示深藍色。
green = (0,100,0)
blue = (100,0,0)
for i in range(I.shape[0]):for j in range(I.shape[1]):sampleMat = np.matrix([[j,i]], dtype=np.float32)response = svm.predict(sampleMat)[1]if response == 1:I[i,j] = greenelif response == 2:I[i,j] = blue
方法 cv::circle
用于顯示構成訓練數據的樣本。標記為類別1的樣本顯示為淺綠色,標記為類別2的樣本顯示為淺藍色。
thick = -1
# Class 1
for i in range(NTRAINING_SAMPLES):px = trainData[i,0]py = trainData[i,1]cv.circle(I, (int(px), int(py)), 3, (0, 255, 0), thick)# Class 2
for i in range(NTRAINING_SAMPLES, 2*NTRAINING_SAMPLES):px = trainData[i,0]py = trainData[i,1]cv.circle(I, (int(px), int(py)), 3, (255, 0, 0), thick)
支持向量
這里我們采用幾種方法來獲取有關支持向量的信息。方法 cv::ml::SVM::getSupportVectors
可獲取所有支持向量。我們已使用該方法找出作為支持向量的訓練樣本,并將其突出顯示。
thick = 2
sv = svm.getUncompressedSupportVectors()for i in range(sv.shape[0]):cv.circle(I, (int(sv[i,0]), int(sv[i,1])), 6, (128, 128, 128), thick)
結果
- 代碼打開一張圖片并展示兩個類別的訓練樣本。其中一個類別的點用淺綠色表示,另一個類別則用淺藍色表示。
- 訓練支持向量機(SVM)并用它對圖像所有像素進行分類。這導致圖像被劃分為藍色區域和綠色區域。兩個區域之間的邊界就是分離超平面。由于訓練數據是非線性可分的,可以看到兩個類別都有部分樣本被誤分類:一些綠點位于藍色區域,而一些藍點位于綠色區域。
- 最后通過在訓練樣本周圍顯示灰色圓環來展示支持向量。
您可以在YouTube上觀看此程序的運行實例:https://www.youtube.com/watch?v=vFv2yPcSo-Q。
主成分分析(PCA)簡介
https://docs.opencv.org/4.x/d1/dee/tutorial_introduction_to_pca.html
上一教程: 非線性可分數據的支持向量機
原作者 | Theodore Tsesmelis |
兼容性 | OpenCV >= 3.0 |
目標
在本教程中,您將學習如何:
- 使用 OpenCV 類
cv::PCA
計算物體的方向。
什么是PCA?
主成分分析(PCA)是一種統計方法,用于提取數據集中最重要的特征。
假設你有一組如上圖所示的二維數據點,每個維度對應你感興趣的特征。乍看之下,這些點似乎是隨機分布的。但仔細觀察會發現,存在一個難以忽視的線性模式(如藍線所示)。PCA的核心在于降維——即減少數據集特征數量的過程。例如在上例中,我們可以將這些點近似為一條直線,從而將數據維度從2D降至1D。
此外,你會注意到這些點沿藍線方向的變異程度,遠大于沿特征1軸或特征2軸的變異。這意味著,相比僅知道點在特征1軸或特征2軸上的位置,了解點沿藍線的位置能提供更多信息。
因此,PCA能幫助我們找到數據變異最大的方向。實際上,對圖中點集運行PCA會得到兩個稱為特征向量的結果向量,它們就是數據集的主成分。
每個特征向量的大小由其對應的特征值編碼,表示數據沿該主成分的變異程度。特征向量的起點是數據集中所有點的中心點。將PCA應用于N維數據集會產生:N個N維特征向量、N個特征值和1個N維中心點。理論介紹到此為止,下面我們來看看如何用代碼實現這些概念。
如何計算特征向量和特征值?
目標是將維度為p的給定數據集X轉換為維度更小(L)的替代數據集Y。等價地說,我們需要找到矩陣Y,其中Y是矩陣X的Karhunen–Loève變換(KLT):
\[ \mathbf{Y} = \mathbb{K} \mathbb{L} \mathbb{T} \{\mathbf{X}\} \]
組織數據集
假設你有一組包含p個變量的觀測數據,希望將數據降維,使得每個觀測值僅用L個變量描述(L < p)。進一步假設數據由n個數據向量\( x_1…x_n \)組成,每個\( x_i \)表示p個變量的單次分組觀測。
- 將\( x_1…x_n \)寫作行向量,每個行向量包含p列
- 將這些行向量組合成維度為\( n\times p \)的矩陣X
計算經驗均值
- 對每個維度\( j = 1, …, p \)計算經驗均值
- 將計算結果存入維度為\( p\times 1 \)的經驗均值向量u
\[ \mathbf{u[j]} = \frac{1}{n}\sum_{i=1}^{n}\mathbf{X[i,j]} \]
計算均值偏差
均值減法是尋找主成分基的關鍵步驟,該基能最小化數據逼近的均方誤差。因此我們按以下方式中心化數據:
- 從數據矩陣X的每一行減去經驗均值向量u
- 將均值中心化后的數據存儲在\( n\times p \)矩陣B中
\[ \mathbf{B} = \mathbf{X} - \mathbf{h}\mathbf{u^{T}} \]
其中h是\( n\times 1 \)的全1列向量:
\[ h[i] = 1, i = 1, …, n \]
求協方差矩陣
- 通過矩陣B與其自身的外積,計算\( p\times p \)經驗協方差矩陣C:
\[ \mathbf{C} = \frac{1}{n-1} \mathbf{B^{*}} \cdot \mathbf{B} \]
其中*表示共軛轉置算子。注意如果B完全由實數組成(多數應用場景如此),"共軛轉置"等同于常規轉置。
求協方差矩陣的特征向量和特征值
- 計算對角化協方差矩陣C的特征向量矩陣V:
\[ \mathbf{V^{-1}} \mathbf{C} \mathbf{V} = \mathbf{D} \]
其中D是C的特征值對角矩陣
- 矩陣D將呈現\( p \times p \)對角矩陣形式:
\[ D[k,l] = \left\{\begin{matrix} \lambda_k, k = l \\ 0, k \neq l \end{matrix}\right. \]
這里\( \lambda_j \)是協方差矩陣C的第j個特征值
- 同樣維度為p x p的矩陣V包含p個列向量,每個長度為p,代表協方差矩陣C的p個特征向量
- 特征值和特征向量按順序配對,第j個特征值對應第j個特征向量
注:參考文獻[1]、[2],特別感謝Svetlin Penkov的原始教程。
源代碼
可下載代碼: C++ | Java | Python
from __future__ import print_function
from __future__ import division
import cv2 as cv
import numpy as np
import argparse
from math import atan2, cos, sin, sqrt, pidef drawAxis(img, p_, q_, colour, scale):p = list(p_)q = list(q_)angle = atan2(p[1] - q[1], p[0] - q[0]) # angle in radianshypotenuse = sqrt((p[1] - q[1]) * (p[1] - q[1]) + (p[0] - q[0]) * (p[0] - q[0]))# Here we lengthen the arrow by a factor of scaleq[0] = p[0] - scale * hypotenuse * cos(angle)q[1] = p[1] - scale * hypotenuse * sin(angle)cv.line(img, (int(p[0]), int(p[1])), (int(q[0]), int(q[1])), colour, 1, cv.LINE_AA)# create the arrow hooksp[0] = q[0] + 9 * cos(angle + pi / 4)p[1] = q[1] + 9 * sin(angle + pi / 4)cv.line(img, (int(p[0]), int(p[1])), (int(q[0]), int(q[1])), colour, 1, cv.LINE_AA)p[0] = q[0] + 9 * cos(angle - pi / 4)p[1] = q[1] + 9 * sin(angle - pi / 4)cv.line(img, (int(p[0]), int(p[1])), (int(q[0]), int(q[1])), colour, 1, cv.LINE_AA)def getOrientation(pts, img):sz = len(pts)data_pts = np.empty((sz, 2), dtype=np.float64)for i in range(data_pts.shape[0]):data_pts[i,0] = pts[i,0,0]data_pts[i,1] = pts[i,0,1]# Perform PCA analysismean = np.empty((0))mean, eigenvectors, eigenvalues = cv.PCACompute2(data_pts, mean)# Store the center of the objectcntr = (int(mean[0,0]), int(mean[0,1]))cv.circle(img, cntr, 3, (255, 0, 255), 2)p1 = (cntr[0] + 0.02 * eigenvectors[0,0] * eigenvalues[0,0], cntr[1] + 0.02 * eigenvectors[0,1] * eigenvalues[0,0])p2 = (cntr[0] - 0.02 * eigenvectors[1,0] * eigenvalues[1,0], cntr[1] - 0.02 * eigenvectors[1,1] * eigenvalues[1,0])drawAxis(img, cntr, p1, (0, 255, 0), 1)drawAxis(img, cntr, p2, (255, 255, 0), 5)angle = atan2(eigenvectors[0,1], eigenvectors[0,0]) # orientation in radiansreturn angleparser = argparse.ArgumentParser(description='Code for Introduction to Principal Component Analysis (PCA) tutorial.\This program demonstrates how to use OpenCV PCA to extract the orientation of an object.')
parser.add_argument('--input', help='Path to input image.', default='pca_test1.jpg')
args = parser.parse_args()src = cv.imread(cv.samples.findFile(args.input))
# Check if image is loaded successfully
if src is None:print('Could not open or find the image: ', args.input)exit(0)cv.imshow('src', src)# Convert image to grayscale
gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)# Convert image to binary
_, bw = cv.threshold(gray, 50, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)contours, _ = cv.findContours(bw, cv.RETR_LIST, cv.CHAIN_APPROX_NONE)for i, c in enumerate(contours):# Calculate the area of each contourarea = cv.contourArea(c)# Ignore contours that are too small or too largeif area < 1e2 or 1e5 < area:continue# Draw each contour only for visualisation purposescv.drawContours(src, contours, i, (0, 0, 255), 2)# Find the orientation of each shapegetOrientation(c, src)cv.imshow('output', src)
cv.waitKey()
注意:另一個使用PCA進行降維同時保持一定方差的示例可在opencv_source_code/samples/cpp/pca.cpp找到。
說明
讀取圖像并轉換為二值圖像
這里我們應用必要的預處理步驟,以便能夠檢測出感興趣的目標對象。
parser = argparse.ArgumentParser(description='Code for Introduction to Principal Component Analysis (PCA) tutorial.\This program demonstrates how to use OpenCV PCA to extract the orientation of an object.')
parser.add_argument('--input', help='Path to input image.', default='pca_test1.jpg')
args = parser.parse_args()src = cv.imread(cv.samples.findFile(args.input))
# Check if image is loaded successfully
if src is None:print('Could not open or find the image: ', args.input)exit(0)cv.imshow('src', src)# Convert image to grayscale
gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)# Convert image to binary
_, bw = cv.threshold(gray, 50, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
提取目標對象
接著根據尺寸查找并篩選輪廓,獲取剩余輪廓的方向信息。
contours, _ = cv.findContours(bw, cv.RETR_LIST, cv.CHAIN_APPROX_NONE)for i, c in enumerate(contours):# Calculate the area of each contourarea = cv.contourArea(c)# Ignore contours that are too small or too largeif area < 1e2 or 1e5 < area:continue# Draw each contour only for visualisation purposescv.drawContours(src, contours, i, (0, 0, 255), 2)# Find the orientation of each shapegetOrientation(c, src)
提取方向
方向信息通過調用 getOrientation()
函數提取,該函數執行完整的 PCA 處理流程。
sz = len(pts)data_pts = np.empty((sz, 2), dtype=np.float64)for i in range(data_pts.shape[0]):data_pts[i,0] = pts[i,0,0]data_pts[i,1] = pts[i,0,1]# Perform PCA analysismean = np.empty((0))mean, eigenvectors, eigenvalues = cv.PCACompute2(data_pts, mean)# Store the center of the objectcntr = (int(mean[0,0]), int(mean[0,1]))
首先需要將數據排列成大小為n×2的矩陣,其中n代表數據點的數量。隨后即可執行PCA分析。計算得到的均值(即質心位置)存儲在cntr變量中,特征向量和特征值則分別存入對應的std::vector容器。
- 結果可視化
最終結果通過drawAxis()函數進行可視化:主成分以線段形式繪制,每個特征向量會乘以對應的特征值并平移到均值位置。
cv.circle(img, cntr, 3, (255, 0, 255), 2)p1 = (cntr[0] + 0.02 * eigenvectors[0,0] * eigenvalues[0,0], cntr[1] + 0.02 * eigenvectors[0,1] * eigenvalues[0,0])p2 = (cntr[0] - 0.02 * eigenvectors[1,0] * eigenvalues[1,0], cntr[1] - 0.02 * eigenvectors[1,1] * eigenvalues[1,0])drawAxis(img, cntr, p1, (0, 255, 0), 1)drawAxis(img, cntr, p2, (255, 255, 0), 5)angle = atan2(eigenvectors[0,1], eigenvectors[0,0]) # orientation in radians
angle = atan2(p[1] - q[1], p[0] - q[0]) # angle in radianshypotenuse = sqrt((p[1] - q[1]) * (p[1] - q[1]) + (p[0] - q[0]) * (p[0] - q[0]))# Here we lengthen the arrow by a factor of scaleq[0] = p[0] - scale * hypotenuse * cos(angle)q[1] = p[1] - scale * hypotenuse * sin(angle)cv.line(img, (int(p[0]), int(p[1])), (int(q[0]), int(q[1])), colour, 1, cv.LINE_AA)# create the arrow hooksp[0] = q[0] + 9 * cos(angle + pi / 4)p[1] = q[1] + 9 * sin(angle + pi / 4)cv.line(img, (int(p[0]), int(p[1])), (int(q[0]), int(q[1])), colour, 1, cv.LINE_AA)p[0] = q[0] + 9 * cos(angle - pi / 4)p[1] = q[1] + 9 * sin(angle - pi / 4)cv.line(img, (int(p[0]), int(p[1])), (int(q[0]), int(q[1])), colour, 1, cv.LINE_AA)
結果
該代碼會打開一張圖像,檢測出感興趣物體的方向,然后通過繪制以下內容來可視化結果:檢測到的感興趣物體輪廓、中心點,以及根據提取方向確定的x軸和y軸。