生成OpenCV正樣本標(biāo)注描述文件)
簡介這套資源提供 objectmarker 工具的完整源碼與可執(zhí)行程序面向需要自制物體識別數(shù)據(jù)集的計(jì)算機(jī)視覺初、中級開發(fā)者。它基于 OpenCV 實(shí)現(xiàn)交互式正樣本標(biāo)注用戶通過鼠標(biāo)在圖像上框選目標(biāo)區(qū)域程序會將多個(gè)邊界框信息寫入 info.txt方便后續(xù)訓(xùn)練檢測或分類模型。壓縮包共 10 個(gè)文件包含 cpp 源碼、exe 可執(zhí)行文件、dll 運(yùn)行庫、txt 說明文檔及示例位圖整體體積僅 1.01MB結(jié)構(gòu)緊湊、便于直接運(yùn)行或二次修改。已有 291 人學(xué)習(xí)下載。借助該工具讀者可快速理解正樣本描述格式圖像名加邊界框坐標(biāo)并掌握用 OpenCV 處理鼠標(biāo)事件、顯示圖像和保存標(biāo)注結(jié)果的實(shí)現(xiàn)思路對于公開數(shù)據(jù)集難以覆蓋的小眾類別也能自行批量標(biāo)注圖像構(gòu)建貼合業(yè)務(wù)需求的訓(xùn)練集。附帶的 readme 與 info 示例文件有助于快速上手適合用于物體識別項(xiàng)目的前期數(shù)據(jù)準(zhǔn)備和算法驗(yàn)證。1. ObjectMarker給 OpenCV 識別批量生成正樣本描述的工具提到 OpenCV 識別從零訓(xùn)練級聯(lián)分類器是最常見的一條路而這條路的第一步就是準(zhǔn)備正樣本。正樣本不是把圖片丟進(jìn)文件夾就完事OpenCV 要求一個(gè)記錄目標(biāo)位置的描述文件格式稍有偏差后面訓(xùn)練直接崩。很多項(xiàng)目卡在圖片有幾百張、目標(biāo)又小又多手工數(shù)坐標(biāo)能讓心態(tài)崩掉。ObjectMarker附源碼就是用來解決這個(gè)問題的打開圖片鼠標(biāo)框出目標(biāo)自動(dòng)生成 OpenCV 訓(xùn)練要用的正樣本描述文本。適合訓(xùn)練人臉、車牌、缺陷檢測這類目標(biāo)區(qū)域明確的識別場景也適合第一次接觸 opencv_traincascade 的初學(xué)者。2. 正樣本描述文件的格式邊界先看懂 info.txt 再開始標(biāo)用級聯(lián)分類器訓(xùn)練前OpenCV 要求把所有正樣本的標(biāo)注寫進(jìn)一個(gè)文本。這個(gè)文件的行格式直接決定后續(xù) opencv_createsamples 能否正確裁出正樣本。很多人第一次訓(xùn)練失敗不是標(biāo)注得不準(zhǔn)而是格式不對。所以先用一節(jié)把它掰開揉碎。2.1 每種 OpenCV 訓(xùn)練工具吃的格式一行一個(gè)邏輯樣本opencv_traincascade 的 -info 參數(shù)接受的文件每一行由三部分組成圖像路徑、目標(biāo)個(gè)數(shù)、目標(biāo)矩形列表。標(biāo)準(zhǔn)寫法是relative/path/to/image.jpg 1 x y w h其中 x、y 是矩形左上角坐標(biāo)w、h 是寬度和高度單位是像素。所有坐標(biāo)必須以圖片左上角為原點(diǎn)向右向下為正。一張圖里有多個(gè)目標(biāo)就把數(shù)量改成 2后面跟兩組 x y w hrelative/path/to/image.jpg 3 10 15 80 90 120 130 60 70 200 40 55 65這里有個(gè)新手常踩的誤區(qū)以為 w、h 是右下角坐標(biāo)。實(shí)際上 opencv_createsamples 在讀取時(shí)是按寬度和高度來理解你填成右下角坐標(biāo)它會把樣本裁成一個(gè)寬高等于右下角減左上角的巨大區(qū)域后面訓(xùn)練直接報(bào)廢。我一般會寫一個(gè)小腳本先校驗(yàn) w、h 都大于 0 且不超過圖像尺寸。這里還要區(qū)分路徑的寫法。OpenCV 按相對路徑讀取圖片而相對路徑是相對于你執(zhí)行訓(xùn)練命令時(shí)的當(dāng)前目錄。如果 info.txt 寫在 train_data 目錄下圖片在 train_data/positive_images 里路徑就應(yīng)該寫成positive_images/001.jpg而不是帶一串絕對路徑。否則換一臺機(jī)器跑或者從別的目錄啟動(dòng)訓(xùn)練全部路徑找不到。2.2 為什么手動(dòng)維護(hù)坐標(biāo)清單容易出低級錯(cuò)誤當(dāng)圖片數(shù)量上百張每張里有兩三個(gè)小目標(biāo)時(shí)手動(dòng)把坐標(biāo)敲進(jìn)文本很難保證不出錯(cuò)。我把常見錯(cuò)誤按出現(xiàn)頻率排了個(gè)序框沒有完整框住目標(biāo)目標(biāo)邊緣被切掉一部分。一張圖里多個(gè)目標(biāo)眼睛數(shù)漏了數(shù)量字段和實(shí)際矩形個(gè)數(shù)對不上。復(fù)制粘貼路徑時(shí)大小寫不一致Windows 下沒問題Linux 下直接找不到文件。坐標(biāo)重疊或者負(fù)數(shù)訓(xùn)練中途才彈 assert failed。這些錯(cuò)誤不會在訓(xùn)練剛開始就暴露往往在跑了一兩個(gè)小時(shí)后彈出異?;蛘哂?xùn)練出的模型誤檢率高得離譜。因此用工具生成描述文件本質(zhì)上是在把“人肉計(jì)算坐標(biāo)”換成“鼠標(biāo)拖框 自動(dòng)換算”從源頭減少數(shù)字出錯(cuò)。2.3 ObjectMarker 的標(biāo)注核心鼠標(biāo)回調(diào)與矩形框管理ObjectMarker 的源碼核心其實(shí)不復(fù)雜基于 OpenCV 的窗口鼠標(biāo)回調(diào)來記錄矩形。我拆過類似的工具最關(guān)鍵是三塊鼠標(biāo)按下開始記錄鼠標(biāo)移動(dòng)時(shí)更新臨時(shí)矩形鼠標(biāo)松開時(shí)把合法矩形存進(jìn)列表。下面這段代碼就是核心循環(huán)import cv2 boxes [] # 已確認(rèn)的矩形列表 start None # 按下鼠標(biāo)時(shí)的坐標(biāo) temp None # 移動(dòng)中的臨時(shí)矩形 def on_mouse(event, x, y, flags, param): global start, temp if event cv2.EVENT_LBUTTONDOWN: start (x, y) temp (x, y, x, y) elif event cv2.EVENT_MOUSEMOVE: if start: temp (start[0], start[1], x, y) elif event cv2.EVENT_LBUTTONUP: if start and x start[0] and y start[1]: boxes.append((start[0], start[1], x, y)) start None cv2.namedWindow(marker) cv2.setMouseCallback(marker, on_mouse)邏輯說明回調(diào)函數(shù)根據(jù)鼠標(biāo)事件維護(hù)三個(gè)變量boxes存放已確認(rèn)的矩形start記錄按下瞬間坐標(biāo)temp用于實(shí)時(shí)顯示。當(dāng)左鍵松開時(shí)判斷x start[0]且y start[1]作用是排除反向拖拽產(chǎn)生的負(fù)寬高矩形。存進(jìn)boxes的是圖片顯示坐標(biāo)系下的絕對坐標(biāo)寫入文件前不需要再換算。參數(shù)說明cv2.setMouseCallback里的窗口名必須和namedWindow保持一致?;卣{(diào)里的x, y是窗口顯示坐標(biāo)系的像素值。如果圖片在窗口中被縮放顯示那么原圖坐標(biāo)需要做一次除法原圖坐標(biāo) 顯示坐標(biāo) / 縮放比例。大多數(shù)復(fù)刻源碼里會保留這個(gè)縮放因子否則框出來的位置會偏移。這塊也是你拿到源碼后最值得改的地方。我建議在顯示窗口上方加一個(gè)類似狀態(tài)欄的區(qū)域?qū)崟r(shí)顯示當(dāng)前圖片路徑、已框目標(biāo)數(shù)量、當(dāng)前矩形寬高。這個(gè)小改動(dòng)在標(biāo)注大量小目標(biāo)時(shí)非常救命每框一個(gè)目標(biāo)就 1減少漏標(biāo)率。如果你的目標(biāo)物體邊緣不明顯先用圖像增強(qiáng)把對比度拉開再標(biāo)避免框太小或者框偏移。2.4 程序里如何管理圖像目錄與自動(dòng)保存ObjectMarker 一般有個(gè)簡單的文件隊(duì)列加載目錄下所有 jpg/png按文件名排序逐張展示。標(biāo)注完一張切到下一張前把該圖所有矩形寫入輸出文件再清空boxes。這樣即使中途關(guān)掉程序已標(biāo)注的圖片也不會丟。你如果拿到源碼想二次開發(fā)主要改這幾處文件后綴過濾目前只認(rèn).jpg和.png如果你的數(shù)據(jù)集是.bmp要手動(dòng)加后綴。輸出路徑默認(rèn)生成在運(yùn)行目錄下建議改成絕對路徑避免不同終端啟動(dòng)時(shí)寫入位置不一致。圖片縮放顯示原圖分辨率很大時(shí)窗口可能放不下需要按比例縮小顯示但坐標(biāo)要還原回原圖尺度。3. 從圖片目錄到輸出文件ObjectMarker 標(biāo)注實(shí)操流程3.1 環(huán)境準(zhǔn)備與源碼運(yùn)行方式拿到 ObjectMarker 源碼后先確認(rèn) OpenCV 環(huán)境。如果源碼是 Python 版本安裝 opencv-python 就夠了如果源碼是 C 版本需要編譯 OpenCV 并配置 CMake。一個(gè)比較省心的做法是先建一個(gè)干凈的虛擬環(huán)境跑通腳本再逐步加依賴。python -m venv venv source venv/bin/activate pip install opencv-python numpy參數(shù)說明虛擬環(huán)境避免把全局 Python 環(huán)境搞亂。opencv-python 自帶常用圖像處理模塊numpy 用于圖像數(shù)組操作。如果你的源碼還用到了 tqdm 做進(jìn)度條也一并裝上。邏輯說明先激活環(huán)境再裝依賴避免裝到系統(tǒng)目錄里。后面的運(yùn)行指令都在這個(gè)虛擬環(huán)境下執(zhí)行退出后不影響其他項(xiàng)目。如果源碼是 C 版本核心依賴是 opencv 庫和 CMake。編譯過程相比 Python 會多一步但運(yùn)行效率更高適合大批量圖片。不過大多數(shù)場景下Python 版本已經(jīng)夠用畢竟標(biāo)注操作是交互式的瓶頸在人的手速不在程序速度。3.2 命令行參數(shù)與操作流程很多復(fù)刻的 ObjectMarker 工具會提供三個(gè)參數(shù)圖片目錄、輸出文件路徑、顯示窗口尺寸。常見調(diào)用如下python objectmarker.py --img-dir ./positive_images --output info.txt --win-width 800 --win-height 600這里--img-dir指定存放正樣本圖片的目錄--output指定生成的描述文件名--win-width和--win-height是顯示窗口的尺寸不是訓(xùn)練尺寸。這兩個(gè)參數(shù)只影響畫框時(shí)的窗口縮放比例不影響最終坐標(biāo)精度。如果你把顯示窗口設(shè)得比原圖小內(nèi)部會按比例縮放坐標(biāo)換算到原圖時(shí)依然是準(zhǔn)確像素。然后按下面的步驟操作運(yùn)行命令后窗口出現(xiàn)第一張圖。按住鼠標(biāo)左鍵拖出矩形框住目標(biāo)松開左鍵確認(rèn)一個(gè)框。同一張圖有多個(gè)目標(biāo)就繼續(xù)框直到框完所有目標(biāo)。按空格鍵保存本圖所有矩形到輸出文件并跳到下一張。全部完成后按 q 退出。需要特別提醒的是這里的“保存”是增量寫入也就是說跳下一張之前把當(dāng)前圖片的矩形寫進(jìn)文件而不是等全部標(biāo)注完統(tǒng)一寫。這樣程序意外崩潰時(shí)已標(biāo)注的圖片不會白標(biāo)。3.3 目錄組織建議與輸出文件檢查建議把正樣本圖片放在一個(gè)獨(dú)立目錄生成文件放在上一級。比如train_data/ positive_images/ 001.jpg 002.jpg info.txt這樣在 info.txt 里寫positive_images/001.jpg訓(xùn)練時(shí)只要在 train_data 目錄下啟動(dòng)命令就能找到所有圖片。避免寫絕對路徑否則換一臺機(jī)器路徑全變。保存后打開 info.txt 檢查前三行。如果看到類似這樣的內(nèi)容positive_images/001.jpg 2 10 15 80 90 120 130 60 70 positive_images/002.jpg 1 30 40 110 95說明每行格式基本對。再檢查矩形是否超出圖像邊界以及數(shù)量字段和實(shí)際坐標(biāo)組數(shù)是否一致。我見過最陰間的錯(cuò)誤是某一行數(shù)量寫成 2但后面跟了 3 組坐標(biāo)OpenCV 讀取時(shí)按數(shù)量截取第三組被當(dāng)成下一行的開頭后面全部錯(cuò)位。所以這個(gè)檢查不能省。3.4 批量預(yù)處理把圖片裁好再標(biāo)省得訓(xùn)練時(shí)踩坑標(biāo)注之前建議對圖片做一次統(tǒng)一預(yù)處理。常見做法是用腳本把圖片統(tǒng)一命名成 6 位數(shù)字比如000001.jpg再按需縮放。這樣 info.txt 里的文件名是整齊的后續(xù)寫數(shù)據(jù)增強(qiáng)腳本也方便。下面是一個(gè)簡單的前置處理腳本把一張大圖按滑動(dòng)窗口切成小圖并跳過面積小于閾值的子圖import cv2 import os def preprocess(image_path, output_dir, patch_size256, overlap64): img cv2.imread(image_path) if img is None: print(ffail to read: {image_path}) return h, w img.shape[:2] idx 0 for y in range(0, h - patch_size 1, patch_size - overlap): for x in range(0, w - patch_size 1, patch_size - overlap): patch img[y:y patch_size, x:x patch_size] out_path os.path.join(output_dir, f{idx:06d}.jpg) cv2.imwrite(out_path, patch) idx 1 preprocess(raw.jpg, positive_images)參數(shù)說明patch_size是滑窗尺寸overlap是相鄰窗口重疊區(qū)域這兩個(gè)值決定你從一張大圖能得到多少張子圖。如果你的目標(biāo)物體不會正好跨邊界可以不用重疊減少重復(fù)樣本。邏輯說明腳本按patch_size - overlap作為步長滑動(dòng)切割避免目標(biāo)剛好被切碎。所有子圖統(tǒng)一命名生成的正樣本描述文件里路徑也會整齊很多。這里有個(gè)取舍滑窗切出來的子圖可能大量不包含目標(biāo)你需要后續(xù)用 ObjectMarker 把包含目標(biāo)的子圖挑出來其余刪掉否則負(fù)樣本混進(jìn)正樣本里模型會學(xué)得很困惑。4. 標(biāo)注與訓(xùn)練銜接的常見問題排查五個(gè)最容易翻車的坑4.1 訓(xùn)練時(shí)提示找不到圖片文件現(xiàn)象opencv_traincascade 啟動(dòng)后報(bào)錯(cuò)Can not open file或者Failed to read image。原因info.txt 里的路徑是相對路徑但命令運(yùn)行的當(dāng)前目錄不在 train_data 下或者路徑里的子目錄名寫錯(cuò)。解決先確認(rèn)工作目錄cd到 train_data 再執(zhí)行訓(xùn)練命令。在 info.txt 中統(tǒng)一使用斜杠/不要混用 Windows 的反斜杠。如果你用 Windows建議在標(biāo)注工具里直接把路徑替換成標(biāo)準(zhǔn)斜杠避免跨平臺時(shí)出問題。4.2 生成的樣本全是全黑或全白現(xiàn)象訓(xùn)練出的 .vec 里樣本無法看標(biāo)記的物體沒有出現(xiàn)在裁片中。原因坐標(biāo) w、h 被誤填成右下角坐標(biāo)導(dǎo)致目標(biāo)偏移或裁到背景或者圖像通道處理錯(cuò)誤BGR 和 RGB 混用。解決在源碼輸出前強(qiáng)制檢查 w、h 為正數(shù)并且x w、y h不超過圖像寬高??杉哟a把坐標(biāo)限制在合法范圍內(nèi)def clamp_rect(x, y, w, h, img_w, img_h): x max(0, min(x, img_w - 1)) y max(0, min(y, img_h - 1)) w min(w, img_w - x) h min(h, img_h - y) return x, y, w, h參數(shù)說明img_w和img_h是原圖寬高傳入前從cv2.imread讀取。clamp_rect的作用是把越界坐標(biāo)拉回圖像范圍內(nèi)避免生成無效裁剪區(qū)域。邏輯說明先 clamp 左上角坐標(biāo)再根據(jù)剩余空間收縮寬高。這里不直接修改原圖只是保證寫入 info.txt 的坐標(biāo)合法。建議在保存前對每一條矩形都調(diào)用一次。4.3 多目標(biāo)圖只檢測出來一部分現(xiàn)象訓(xùn)練完成后檢測率很低特別是那些包含多個(gè)目標(biāo)的圖片。原因標(biāo)注的時(shí)候漏框某些目標(biāo)被當(dāng)成背景模型學(xué)到的特征不完整。解決保存前顯示當(dāng)前圖片上已經(jīng)畫好的所有框確認(rèn)數(shù)量一致再跳下一張。如果圖片數(shù)量大可以每 20 張停一次把本輪標(biāo)注結(jié)果打印到終端。批量標(biāo)注最容易出現(xiàn)“標(biāo)著標(biāo)著忘了這張圖有幾個(gè)目標(biāo)”的情況所以工具里顯示已框數(shù)量不是小事。4.4 坐標(biāo)正常但訓(xùn)練一直報(bào)錯(cuò)現(xiàn)象提示Invalid rectangle或者訓(xùn)練中途崩潰。原因某些圖片尺寸不一致而框坐標(biāo)是從上一張圖片繼承下來沒有清空。解決每次切換圖片時(shí)強(qiáng)制清空boxes數(shù)組。如果圖片尺寸相差大最好在標(biāo)注前統(tǒng)一縮放或填充保證所有圖片的寬高范圍一致。否則即使坐標(biāo)合法不同尺寸的樣本混在一起訓(xùn)練也會導(dǎo)致歸一化時(shí)出現(xiàn)問題。4.5 負(fù)樣本數(shù)量足夠但正樣本描述文件行首數(shù)字是 0現(xiàn)象info.txt 里某行是path.jpg 0opencv_createsamples 直接跳過這行。原因按下空格時(shí)boxes列表為空說明沒有框任何目標(biāo)。解決工具里加判斷如果boxes為空就不寫入該行同時(shí)提示當(dāng)前圖片沒有標(biāo)記讓你決定是重新標(biāo)還是跳過。不要寫個(gè) 0 混過去這一行不會報(bào)錯(cuò)但會浪費(fèi)一次樣本機(jī)會并且讓你以為這批數(shù)據(jù)沒問題。5. 接進(jìn) opencv_createsamples用一次可視化校驗(yàn)收尾描述文件生成后最終要喂給 opencv_createsamples 生成 .vec 文件這一步是訓(xùn)練前最后一道關(guān)卡。命令格式如下opencv_createsamples -info info.txt -num 2000 -w 24 -h 24 -vec positives.vec參數(shù)說明-info指定 ObjectMarker 生成的正樣本描述文件-num是抽取的樣本數(shù)不要超過描述文件中的目標(biāo)總數(shù)否則會報(bào)錯(cuò)-w和-h是統(tǒng)一縮放尺寸建議保持 24 或 20不要設(shè)太大否則訓(xùn)練時(shí)間劇增-vec是輸出的樣本向量文件。邏輯說明-num的值代表最終參與訓(xùn)練的正樣本數(shù)量。如果你的描述文件里一共有 1500 個(gè)目標(biāo)就填 1500。填多了會報(bào)錯(cuò)填少了浪費(fèi)已有數(shù)據(jù)。生成 vec 后我習(xí)慣做一次可視化校驗(yàn)不急著開訓(xùn)。最直接的方式是用 opencv_createsamples 自帶的展示功能opencv_createsamples -vec positives.vec -show 10這個(gè)命令會彈出窗口一屏展示前 10 個(gè)正樣本。你可以按空格翻頁按 ESC 退出。只需要花兩分鐘就能確認(rèn)每個(gè)樣本里目標(biāo)是否完整、有無被裁掉一半的情況。我以前跳過這步直接開訓(xùn)結(jié)果訓(xùn)練了兩小時(shí)模型識別不到目標(biāo)回頭查才發(fā)現(xiàn)框選時(shí)所有框都偏了半個(gè)身位。從那以后我每次標(biāo)注完都強(qiáng)制走一遍-show檢查確認(rèn)框內(nèi)是完整目標(biāo)再啟動(dòng)訓(xùn)練。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取