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,一经查实,立即删除!

相关文章

图论 弦_混乱的弦

图论 弦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…

「原创」从马云、马化腾、李彦宏的对话,看出三人智慧差在哪里?

在今年中国IT领袖峰会上&#xff0c;马云、马化腾、李彦宏第一次单独合影&#xff0c;同框画面可以说很难得了。BAT关心的走势一直是同行们竞相捕捉的热点&#xff0c;所以三位大Boss在这次大会上关于人工智能的见解&#xff0c;也受到广泛关注与多方解读。马云认为机器比人聪明…

字符串矩阵转换成长字符串_字符串矩阵

字符串矩阵转换成长字符串Description: 描述&#xff1a; In this article, we are going to see how backtracking can be used to solve following problems? 在本文中&#xff0c;我们将看到如何使用回溯来解决以下问题&#xff1f; Problem statement: 问题陈述&#xf…

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类用于…

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 …

Logistic回归和SVM的异同

这个问题在最近面试的时候被问了几次&#xff0c;让谈一下Logistic回归&#xff08;以下简称LR&#xff09;和SVM的异同。由于之前没有对比分析过&#xff0c;而且不知道从哪个角度去分析&#xff0c;一时语塞&#xff0c;只能不知为不知。 现在对这二者做一个对比分析&#xf…

构建安全网络 比格云全系云产品30天内5折购

一年之计在于春&#xff0c;每年的三、四月&#xff0c;都是个人创业最佳的起步阶段&#xff0c;也是企业采购最火热的时期。为了降低用户的上云成本&#xff0c;让大家能无门槛享受到优质高性能的云服务&#xff0c;比格云从3月16日起&#xff0c;将上线“充值30天内&#xff…

数据结构 基础知识

一。逻辑结构: 是指数据对象中数据 素之间的相互关系。 其实这也是我 今后最需要关注的问题 逻辑结构分为以 四种1. 集合结构 2.线性结构 3.数形结构 4&#xff0c;图形结构 二。物理结构&#xff1a; 1&#xff0c;顺序存储结,2 2. 链式存储结构 一&#xff0c;时间复杂…

ruby 变量类中范围_Ruby中的类

ruby 变量类中范围Ruby类 (Ruby Classes) In the actual world, we have many objects which belong to the same category. For instance, I am working on my laptop and this laptop is one of those laptops which exist around the globe. So, this laptop is an object o…

以云计算的名义 驻云科技牵手阿里云

本文讲的是以云计算的名义 驻云科技牵手阿里云一次三个公司的牵手 可能会改变无数企业的命运 2017年4月17日&#xff0c;对于很多人来说可能只是个平常的工作日&#xff0c;但是对于国内无数的企业来说却可能是个会改变企业命运的日。驻云科技联合国内云服务提供商阿里云及国外…

浏览器端已支持 ES6 规范(包括 export import)

当然&#xff0c;是几个比较优秀的浏览器&#xff0c;既然是优秀的浏览器&#xff0c;大家肯定知道是那几款啦&#xff0c;我就不列举了&#xff0c;我用的是 chrome。 对 script 声明 type 为 module 后就可以享受 es6 规范所带来的模块快感了。 基础语法既然是全支持&#xf…

一文读懂深度学习框架下的目标检测(附数据集)

从简单的图像分类到3D位置估算&#xff0c;在机器视觉领域里从来都不乏有趣的问题。其中我们最感兴趣的问题之一就是目标检测。 如同其他的机器视觉问题一样&#xff0c;目标检测目前为止还没有公认最好的解决方法。在了解目标检测之前&#xff0c;让我们先快速地了解一下这个领…

设计一个应用程序,以在C#中的按钮单击事件上在MessageBox中显示TextBox中的文本...

Here, we took two controls on windows form that are TextBox and Button, named txtInput and btnShow respectively. We have to write C# code to display TextBox’s text in the MessageBox on Button Click. 在这里&#xff0c;我们在Windows窗体上使用了两个控件&…

Oracle优化器:星型转换(Star Query Transformation )

Oracle优化器&#xff1a;星型转换&#xff08;Star Query Transformation &#xff09;Star query是一个事实表&#xff08;fact table&#xff09;和一些维度表&#xff08;dimension&#xff09;的join。每个维度表都跟事实表通过主外键join&#xff0c;且每个维度表之间不j…

JavaScript | 声明数组并使用数组索引分配元素的代码

Declare an array, assign elements by indexes and print all elements in JavaScript. 声明一个数组&#xff0c;通过索引分配元素&#xff0c;并打印JavaScript中的所有元素。 Code: 码&#xff1a; <html><head><script>var fruits [];fruits[0]"…

Kubernetes基础组件概述

本文讲的是Kubernetes基础组件概述【编者的话】最近总有同学问Kubernetes中的各个组件的相关问题&#xff0c;其实这些概念内容在官方文档中都有&#xff0c;奈何我们有些同学可能英文不好&#xff0c;又或者懒得去看&#xff0c;又或者没有找到&#xff0c;今天有时间就专门写…

c语言将链表写入二进制文件_通过逐级遍历将二进制树转换为单链表的C程序

c语言将链表写入二进制文件Problem statement: Write a C program to convert a binary tree into a single linked list by traversing level-wise. 问题陈述&#xff1a;编写一个C程序&#xff0c;通过逐级遍历将二进制树转换为单个链表 。 Example: 例&#xff1a; The ab…

洛谷 P2689 东南西北【模拟/搜索】

题目描述 给出起点和终点的坐标及接下来T个时刻的风向(东南西北)&#xff0c;每次可以选择顺风偏移1个单位或者停在原地。求到达终点的最少时间。 如果无法偏移至终点&#xff0c;输出“-1”。 输入输出格式 输入格式&#xff1a; 第一行两个正整数x1,y1&#xff0c;表示小明所…

单链表遍历_单链表及其遍历实现的基本操作

单链表遍历单链表 (Single linked list) Single linked list contains a number of nodes where each node has a data field and a pointer to next node. The link of the last node is to NULL, indicates end of list. 单个链表包含许多节点&#xff0c;其中每个节点都有一…

8086简单的指令流水线_在8086微处理器中执行流水线的指令和概念的步骤

8086简单的指令流水线Any computer or machine works according to some instructions. These instructions are responsible for all the work that the machine does. But how does a machine work to understand and execute that instruction? 任何计算机或机器都按照某些…