OpenCV探索之路(二十五):制作簡易的圖像標注小工具

搞圖像深度學習的童鞋一定碰過圖像數據標注的東西,當我們訓練網絡時需要訓練集數據,但在網上又沒有找到自己想要的數據集,這時候就考慮自己制作自己的數據集了,這時就需要對圖像進行標注。圖像標注是件很枯燥又很費人力物力的一件事情,但是又不能回避,畢竟搞深度學習如果沒有數據集那一切都是瞎搞。最近我在參加一個有關圖像深度學習的比賽,因為命題方沒有給出訓練集,所以需要隊伍自己去標注訓練集,所以我花點時間開發了一些圖像標注小工具給我的團隊使用,以減輕標注的難度,加快標注的速度。

這篇文章我將分享三個標注小工具,分別用于圖像分類、目標檢測以及語義分割的圖像標注任務。

圖像分類標注小工具

實現圖像分類的小工具太好開發了,因為它功能很簡單,無非是對一個文件夾內的所有圖片進行分類,生成每張圖片所對應的類別標簽,用txt文件存儲起來,當然也可以把每一類圖片放在對應的該類的文件夾下。

我實現的這個圖像分類小工具的功能就是,循環彈出一個文件夾內所有的圖片,標注人員對這張圖片進行分類,屬于1類就按1,屬于2類就按2,如此類推,按完相應號碼后圖片自動跳到下一張,直至文件夾內的圖片都被標注完畢。

我們以下面的圖庫為例,將其分為3類。

1093303-20170930001506637-988022432.png

首先我們需要創建相應的文件夾來存儲每個類的圖片

1093303-20170930001519465-2019322231.png

圖像分類標注小工具代碼:

#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>#define  DATA_DIR ".\\dataset\\"
#define  IMG_MAX_NUM  20using namespace cv;
using namespace std;int main()
{FILE* fp;FILE* fp_result;fp = fopen("start.txt", "r");  //讀取開始的圖片名字,方便從某一圖片開始標注int start_i = 0;fscanf(fp, "%d", &start_i);fclose(fp);fp_result = fopen("classify_record.txt", "a+");   //用于記錄每張圖每個框的標注信息printf("start_i: %d\n", start_i);/*循環讀取圖片來標注*/for (int i = start_i; i < IMG_MAX_NUM; i++){stringstream ss1,ss2,ss3;ss1 << DATA_DIR <<"data\\"<< i << ".jpg";ss3 << i << ".jpg";Mat src = imread(ss1.str());if (src.empty()){continue;}printf("正在操作的圖像: %s\n", string(ss1.str()).c_str());imshow("標注", src);char c = 0;c = waitKey(0);while ( c != '1' && c != '2' && c != '3')  {c = waitKey(0);printf("invaid input!\n");}ss2 << DATA_DIR << c << "\\" << i << ".jpg";char type = c - '0';printf("分類為: %d\n", c - '0');  imwrite(ss2.str(), src);   //copy一份到對應類別的文件夾fprintf(fp_result, "%s %d\n", string(ss3.str()).c_str(), type);}fclose(fp_result);return 0;
}

利用工具進行標注

1093303-20170930001539372-462477385.png

每一類圖片被分到相應的文件夾內

1093303-20170930001601434-885127407.png

同時也生成標簽文件,每行以圖片路徑+對應的類別的方式呈現。

1093303-20170930001612872-802210818.png

目標檢測圖像標注小工具

在目標檢測相關的網絡訓練中,我們需要有帶有以下標簽的數據集:

1093303-20170930001625731-94770740.png

我們做標注時不僅僅要把我們想要識別的物體用矩形框將其框出來,還需要記錄這個框的相關信息,比如這個框的左頂點坐標、寬度高度等(x,y,w,h)。為了能實現這個標注任務,這個標注小工具必須具備框圖和自動記錄(x,y,w,h)信息的功能。

利用opencv我們可以快速實現用矩形框框出對應物體的功能,再加上將每個矩形框的信息有序記錄在txt文件的功能,一個用于檢測圖像標注小工具就算開發好了。

目標檢測圖像標注小工具代碼:

#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>#define  DATA_DIR ".\\cut256\\"
#define  IM_ROWS  5106
#define  IM_COLS  15106
#define  ROI_SIZE 256using namespace cv;
using namespace std;Point ptL, ptR; //鼠標畫出矩形框的起點和終點,矩形的左下角和右下角
Mat imageSource, imageSourceCopy;
FILE* fp_result;struct UserData
{Mat src;vector<Rect> rect;
};void OnMouse(int event, int x, int y, int flag, void *dp)
{UserData *d = (UserData *)dp;imageSourceCopy = imageSource.clone();if (event == CV_EVENT_LBUTTONDOWN)  //按下鼠標右鍵,即拖動開始{ptL = Point(x, y);ptR = Point(x, y);}if (flag == CV_EVENT_FLAG_LBUTTON)   //拖拽鼠標右鍵,即拖動進行{ptR = Point(x, y);imageSourceCopy = imageSource.clone();rectangle(imageSourceCopy, ptL, ptR, Scalar(0, 255, 0));imshow("標注", imageSourceCopy);}if (event == CV_EVENT_LBUTTONUP)  //拖動結束{if (ptL != ptR){rectangle(imageSourceCopy, ptL, ptR, Scalar(0, 255, 0));imshow("標注", imageSourceCopy);int h = ptR.y - ptL.y;int w = ptR.x - ptL.x;printf("選擇的信息區域是:x:%d  y:%d  w:%d  h:%d\n", ptL.x, ptL.y, w, h);d->rect.push_back(Rect(ptL.x, ptL.y, w, h));//d->src(imageSourceCopy);}}//點擊右鍵刪除一個矩形if (event == CV_EVENT_RBUTTONDOWN){if (d->rect.size() > 0){Rect temp = d->rect.back();printf("刪除的信息區域是:x:%d  y:%d  w:%d  h:%d\n", temp.x, temp.y, temp.width, temp.height);d->rect.pop_back();for (int i = 0; i < d->rect.size(); i++){rectangle(imageSourceCopy, d->rect[i], Scalar(0, 255, 0), 1);}}}}void DrawArea(Mat& src, string img_name, string path_name)
{Mat img = src.clone();char c = 'x';UserData d;d.src = img.clone();while (c != 'n'){Mat backup = src.clone();imageSource = img.clone();namedWindow("標注", 1);imshow("標注", imageSource);setMouseCallback("標注", OnMouse, &d);c = waitKey(0);if (c == 'a'){printf("rect size: %d\n", d.rect.size());for (int i = 0; i < d.rect.size(); i++){rectangle(backup, d.rect[i], Scalar(0, 255, 0), 1);}img = backup.clone();}}fprintf(fp_result, "%s\n", img_name.c_str());fprintf(fp_result, "%d\n", d.rect.size());for (int i = 0; i < d.rect.size(); i++){Rect t = d.rect[i];fprintf(fp_result, "%d %d %d %d\n", t.x, t.y, t.width, t.height);}imwrite(path_name, img);}
int main()
{FILE* fp;fp = fopen("start.txt", "r");int start_i = 0;int start_j = 0;fscanf(fp, "%d %d", &start_i, &start_j);fclose(fp);fp_result = fopen("record.txt", "a+");printf("start_i: %d, start_j: %d\n", start_i, start_j);/*循環讀取圖片來標注*/for (int i = start_i; i< IM_ROWS / ROI_SIZE + 1; i++){for (int j = start_j; j<IM_COLS / ROI_SIZE; j++){stringstream ss1, ss2;ss1 << DATA_DIR << "2017\\" << i << "_" << j << "_" << ROI_SIZE << "_.jpg";ss2 << DATA_DIR << "label_img\\" << i << "_" << j << "_" << ROI_SIZE << "_.jpg";cout << ss1.str() << endl;string str(ss1.str());string str2(ss2.str());cv::Mat src = cv::imread(ss1.str());DrawArea(src, str,str2);}}fclose(fp_result);return 0;
}

以標注建筑物為例子吧!

1093303-20170930001642669-315578557.png

1093303-20170930001657762-1289600726.png

然后在txt文件中可以看到我們標記的矩形信息記錄,第一行是圖片路徑+框的個數,第二行開始是每個矩形的x,y,w,h。

1093303-20170930001710981-1620963112.png

語義分割圖像標注小工具

語義分割的標注相比上面的標注要復雜得多,所以標注工具開發起來也略難一點。

比如有這么一個任務,我們需要把圖像中的建筑物給標注出來,生成一個mask圖。

比如這樣子

1093303-20170930001727325-1694179410.png

然后我們以后就可以根據這些mask圖作為label來進行語義分割網絡的訓練了。

實現這么一個工具還是不算太復雜,主要功能的實現就在于使用了opencv的多邊形的生成與填充函數。標注人員只需要在要標注的物體邊緣打點,然后工具就會自動填充該區域,進而生成黑白mask圖。

#include <iostream>
#include <sstream>
#include <vector>
#include <opencv2/opencv.hpp>
using namespace std;#define  DATA_DIR ".\\cut256\\"#define  IM_ROWS  5106
#define  IM_COLS  15106
#define  ROI_SIZE 256
struct UserData
{cv::Mat src;vector<cv::Point> pts;
};FILE* fpts_set;void on_mouse(int event, int x, int y, int flags, void *dp)
{UserData *d = (UserData *)dp;if (event == CV_EVENT_LBUTTONDOWN){d->pts.push_back(cv::Point(x, y));}if (event == CV_EVENT_RBUTTONDOWN){if (d->pts.size()>0)d->pts.pop_back();}cv::Mat temp = d->src.clone();if (d->pts.size()>2){const cv::Point* ppt[1] = { &d->pts[0] };int npt[] = { static_cast<int>(d->pts.size()) };cv::fillPoly(temp, ppt, npt, 1, cv::Scalar(0, 0, 255), 16);}for (int i = 0; i<d->pts.size(); i++){cv::circle(temp, d->pts[i], 1, cv::Scalar(0, 0, 255), 1, 16);}cv::circle(temp, cv::Point(x, y), 1, cv::Scalar(0, 255, 0), 1, 16);cv::imshow("2017", temp);}void WriteTxT(vector<cv::Point>& pst)
{for (int i = 0; i < pst.size(); i++){fprintf(fpts_set, "%d %d", pst[i].x, pst[i].y);if (i == pst.size() - 1){fprintf(fpts_set, "\n");}else{fprintf(fpts_set, " ");}}
}int label_img(cv::Mat &src, cv::Mat &mask, string& name)
{char c = 'x';vector<vector<cv::Point> > poly_point_set;while (c != 'n'){UserData d;d.src = src.clone();cv::namedWindow("2017", 1);cv::setMouseCallback("2017", on_mouse, &d);cv::imshow("2017", src);c = cv::waitKey(0);if (c == 'a'){if (d.pts.size()>0){const cv::Point* ppt[1] = { &d.pts[0] };int npt[] = { static_cast<int>(d.pts.size()) };cv::fillPoly(src, ppt, npt, 1, cv::Scalar(0, 0, 255), 16);cv::fillPoly(mask, ppt, npt, 1, cv::Scalar(255), 16);poly_point_set.push_back(d.pts);}}}fprintf(stdout, "%s %d\n", name.c_str(), poly_point_set.size());fprintf(fpts_set, "%s %d\n", name.c_str(), poly_point_set.size());//將點集寫入文件for (int i = 0; i < poly_point_set.size(); i++){WriteTxT(poly_point_set[i]);}return 0;
}
int main()
{FILE* fp;fp = fopen("start.txt", "r");int start_i = 0;int start_j = 0;fscanf(fp, "%d %d", &start_i, &start_j);fclose(fp);fpts_set = fopen("semantic_label.txt", "a+");printf("start_i: %d, start_j: %d\n", start_i, start_j);for (int i = start_i; i<IM_ROWS / ROI_SIZE + 1; i++){for (int j = start_j; j<IM_COLS / ROI_SIZE; j++){stringstream ss1,ss2,ss3;cv::Mat mask(256, 256, CV_8UC1);mask.setTo(0);ss1 << DATA_DIR << "2017\\" << i << "_" << j << "_" << ROI_SIZE << "_.jpg";ss2 << DATA_DIR << "label\\" << i << "_" << j << "_" << ROI_SIZE << "_.jpg";ss3 << i << "_" << j << "_" << ROI_SIZE << "_.jpg";cout << ss1.str() << endl;cv::Mat src = cv::imread(ss1.str());label_img(src, mask, string(ss3.str()));// label based on tinycv::imwrite(ss2.str(), mask);}}fclose(fpts_set);return 0;
}

所以我們可以利用這個標注工具對任意形狀的物體進行標注,原理就是利用多邊形的逼近。看看效果吧

1093303-20170930001743262-521663242.png

1093303-20170930001757872-1072670254.png

1093303-20170930001811575-1929206117.png

生成的mask圖

1093303-20170930001823044-889069293.png

當然我們也可以根據需求把每個標注的每個圖形的邊緣點記錄下來
1093303-20170930001834215-1434494172.png

希望這三款小工具能給你帶來一點小幫助和小啟發~

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/540545.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/540545.shtml
英文地址,請注明出處:http://en.pswp.cn/news/540545.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

固件的完整形式是什么?

FW&#xff1a;前進 (FW: Forward) FW is an abbreviation of "Forward". FW是“ Forward”的縮寫 。 It is an expression, which is commonly used in Gmail or messaging platform. It is also written as FWD or Fwd or Fw. It shows that the email has been s…

[轉載] python __slots__ 詳解(上篇)

參考鏈接&#xff1a; Python的__name __(特殊變量) python中的new-style class要求繼承Python中的一個內建類型&#xff0c; 一般繼承object&#xff0c;也可以繼承list或者dict等其他的內建類型。 在python新式類中&#xff0c;可以定義一個變量__slots__&#xff0c;它的作…

委托BegionInvoke和窗體BegionInvoke

委托BegionInvoke是指通過委托方法執行多線程任務&#xff0c;例如&#xff1a; //定義委托成員變量 delegate void dg_DeleAirport(); //指定委托函數 dg_DeleAirport dga AirportBLL.DeleteHistoryTransAirport; //通過BeginInvoke以異步線程方式執行委托函數&#xff0c;可…

圖論 弦_混亂的弦

圖論 弦Problem statement: 問題陳述&#xff1a; You are provided an input string S and the string "includehelp". You need to figure out all possible subsequences "includehelp" in the string S? Find out the number of ways in which the s…

[轉載] Python列表操作

參考鏈接&#xff1a; Python中的基本運算符 Python列表&#xff1a; 序列是Python中最基本的數據結構。序列中的每個元素都分配一個數字 - 它的位置&#xff0c;或索引&#xff0c;第一個索引是0&#xff0c;第二個索引是1&#xff0c;依此類推&#xff1b; Python有6個序列的…

「原創」從馬云、馬化騰、李彥宏的對話,看出三人智慧差在哪里?

在今年中國IT領袖峰會上&#xff0c;馬云、馬化騰、李彥宏第一次單獨合影&#xff0c;同框畫面可以說很難得了。BAT關心的走勢一直是同行們競相捕捉的熱點&#xff0c;所以三位大Boss在這次大會上關于人工智能的見解&#xff0c;也受到廣泛關注與多方解讀。馬云認為機器比人聰明…

python 注釋含注釋_Python注釋

python 注釋含注釋Python注釋 (Python comments) Comments in Python are used to improve the readability of the code. It is useful information given by the programmer in source code for a better understanding of code and logic that they have used to solve the …

C2的完整形式是什么?

C2&#xff1a;核心2 (C2: Core 2) C2 is an abbreviation of "Core 2" or "Intel Core 2". C2是“ Core 2”或“ Intel Core 2”的縮寫 。 It is a family of Intels processor which was launched on the 27th of July, 2006. It comprises a series of…

scala特性_Scala | 特性應用

scala特性特性應用 (Trait App) Scala uses a trait called "App" which is used to convert objects into feasible programs. This conversion is done using the DelayedInit and the objects are inheriting the trait named App will be using this function. T…

[轉載] Python3中的表達式運算符

參考鏈接&#xff1a; Python中的除法運算符 1&#xff1a;Python常用表達式運算符 yield 生成器函數send協議 lambda args:expression 創建匿名函數 x if y else z 三元選擇表達式(當y為真時&#xff0c;x才會被計算) x or y 邏輯或(僅但x為假時y才會被計算) x and …

字符串矩陣轉換成長字符串_字符串矩陣

字符串矩陣轉換成長字符串Description: 描述&#xff1a; In this article, we are going to see how backtracking can be used to solve following problems? 在本文中&#xff0c;我們將看到如何使用回溯來解決以下問題&#xff1f; Problem statement: 問題陳述&#xf…

pythonchallenge_level2

level2 地址&#xff1a;http://www.pythonchallenge.com/pc/def/ocr.html。 源碼&#xff1a;gitcode.aliyun.com:qianlizhixing12/PythonChallenge.git。 問題&#xff1a;找出頁面源碼一點提示注釋中的稀有字符。 #!/usr/bin/env python3 # -*- coding:UTF-8 -*-# Level 2im…

[轉載] python類運算符的重載

參考鏈接&#xff1a; Python中的運算符重載 alist input().split() blist input().split() n float(input()) class Vector: def __init__(self, x0, y0, z0): # 請在此編寫你的代碼(可刪除pass語句) self.X x self.Y y self.Z z # 代碼結束 def __add__(self, other):…

r語言 運算符_R語言運算符

r語言 運算符R語言中的運算符 (Operators in R Language) Generally speaking, an operator is a symbol that gives proper commands to the compiler regarding a specific action to be executed. The operators are used for carrying out the mathematical or logical cal…

[轉載] Python基礎之類型轉換與算術運算符

參考鏈接&#xff1a; Python中的運算符函數| 1 一、注釋 1.注釋&#xff1a;對程序進行標注和說明&#xff0c;增加程序的可讀性。程序運行的時候會自動忽略注釋。 2.單行注釋&#xff1a;使用#的形式。但是#的形式只能注釋一行&#xff0c;如果有多行&#xff0c;就不方便…

java awt 按鈕響應_Java AWT按鈕

java awt 按鈕響應The Button class is used to implement a GUI push button. It has a label and generates an event, whenever it is clicked. As mentioned in previous sections, it extends the Component class and implements the Accessible interface. Button類用于…

解決“由于應用程序的配置不正確,應用程序未能啟動,重新安裝應用程序可能會糾正這個問題”...

在VS2005下用C寫的程序&#xff0c;在一臺未安裝VS2005的系統上&#xff0c; 用命令行方式運行&#xff0c;提示&#xff1a; “系統無法執行指定的程序” 直接雙擊運行&#xff0c;提示&#xff1a; “由于應用程序的配置不正確&#xff0c;應用程序未能啟動&#xff0c;重新安…

qgis在地圖上畫導航線_在Laravel中的航線

qgis在地圖上畫導航線For further process we need to know something about it, 為了進一步處理&#xff0c;我們需要了解一些有關它的信息&#xff0c; The route is a core part in Laravel because it maps the controller for sending a request which is automatically …