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.mzph.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 …