引言
今年这一年:
独立完成公司的v2->v3项目升级,这是一个巨大工作量的迁移 ✅
旅行了已经计划很久很喜欢的国内外城市 ✅
...
等等,充实且饱满的生活让我最近有时间来总结一下最近学到的,欢迎指点讨论👏🏻
项目
我们项目致力于打造AI工业质检的平台,进而接触到了很多标注平台标注工具。
为什么选择CVAT
CVAT是一款开源的数据标注平台,支持大部分数据标注类型,如2D包围框、多边形、语义分割、立体框、关键点、折线、LiDAR包围框、圆等。并支持嵌入AI模型,用于优化数据标注流程和提升效率。CVAT(Computer Vision Annotation Tool)是一款功能强大、高效的开源数据标注平台,专为计算机视觉数据集设计。作为行业领先的数据引擎,CVAT被广泛应用于机器学习项目中,受到各种规模的团队的信赖和使用。
优势
CVAT拥有庞大的体系,稳定的功能,有兴趣的可以了解他的使用和docker部署与安装步骤,官网写的很详细
cvat-web源码
cvat-web采用React框架配合antd组件库+ts整体结构,先来看看前端部分的目录结构:
- cvat-canvas >>> 标注画布方法
- cvat-canvas3d >>> 标注画布绘制3D图形
- cvat-core >>> 连接cvat-data和cvat-ui的数据处理逻辑
- cvat-data >>> 引入Web Assembly
- cvat-ui >>> 主界面代码引入opencv.js
1.引入WASM
WASM这两年火热,是一种运行在现代网络的的新型代码以提供特定的功能****特别是在性能方面。它一般不会被直接编写,而是为诸如 C、C++ 和 Rust 等底层语言提供一个高效的编译目标。目前,WebAssembly 已成为与HTML、CSS 以及 JavaScript 并列的web领域第四类编程语言。
我们来看到cvat-data部分引入了Broadway.js,文档中也有说到为什么我们呢把文件放在他那里,是以为作者不提供npm包,所以我们需要储存一些组件在我们自己的仓库。
从github仓库我们能看到,这是一个js解码器,主要用于视频播放器部分,视频播放器首先需要下载整个视频才能开始播放,因此一开始会显得有点慢,所以请耐心等待。您可以通过单击每个播放器来开始播放视频。左上角的播放器在主线程上运行,而其余的播放器在后台工作线程中运行。
不得不说cvat里面采用了很多值得借鉴的图片和视频的优化,值得很多借鉴。
2.引入opencv
在cvat-ui中项目拉取了opencv的开放接口,引入了opencv.js。页面中智能标注功能部分预加载了此部分,调用opencv方法完成了此功能。
初识opencv.js
1.环境搭建
环境搭建,官网部分给了详细的步骤,按照步骤安装即可。
2.图像坐标系定义
说明:
Mat::Mat(int rows, int cols, int type)为创建行数为rows,列数为cols,类型为type的图像
坐标体系中的零点坐标为图片的左上角,X轴为图像矩形的上面那条水平线;Y轴为图像矩形左边的那条垂直线。该坐标体系在诸如结构体Mat,Rect,Point中都是适用的。
在使用image.at()来访问图像中点的值的时候,如果是以坐标方式进行访问,则坐标顺序为image.at(y, x)。但是,要是以坐标点(image图像中的Point(x, y)点)的方式进行访问,则为image.at(Point(x, y))
如果所画图像是多通道的,比如说image图像的通道数时n,则使用Mat::at(x, y)时,其x的范围依旧是0到image的height,而y的取值范围则是0到image的width乘以n,因为这个时候是有n个通道,所以每个像素需要占有n列。但是如果在同样的情况下,使用Mat::at(point)来访问的话,则这时候可以不用考虑通道的个数,因为Mat::at(x, y)返回的是一个数字,而,Mat::at(point)返回的是一个对应的n维向量。
3.Mat对象
OpenCV存储和处理图像的基本单元-Mat对象。Mat的引入简化了用户的内存管理(不再需要手动分配其内存),使代码简洁(编写更少,实现更多)。C++接口的主要缺点是,目前许多嵌入式开发系统只支持C。
Mat基本上是一个包含两个数据部分的类:
- 矩阵头(包含矩阵大小、用于存储的方法、存储矩阵的地址等信息)
- 指向包含像素值的矩阵的指针(根据选择的存储方法采用任何维度)
OpenCV是一个图像处理库。它包含大量图像处理功能。为了解决计算难题,大多数时候将使用库的多个函数对同一个Mat对象进行操作。因此,将Mat对象传递给函数是一种常见的做法。但是对较大的图像进行不必要的复制会降低程序的速度。为了解决这个问题,OpenCV使用了参考计数系统。其思想是每个Mat对象都有自己的头,里面包含了一些图像的基本信息(图像大小,数据类型,通道数),然后,通过使两个Mat对象的矩阵指针指向同一地址,这样就可以在两个Mat对象之间共享矩阵,节省存储空间。另外,复制运算符也只是复制矩阵头和指向包含像素值的矩阵的指针,而不是数据本身。
上述例子中所有对象都指向同一个数据矩阵,对其中任何一个对象的像素值进行修改都会影响其他所有对象。实际上,不同的对象只是为相同的底层数据提供不同的访问方法。然而,它们的头部是不同的。我们可以创建仅引用完整数据的一个子部分的矩阵头。例如,要在图像中创建感兴趣区域(ROI),只需创建具有新矩阵头的Mat对象:
矩阵是由最后一个使用它的对象来进行内存释放的。这是通过使用引用计数机制来处理的。每当有人复制Mat对象的矩阵头时,矩阵的计数器就会增加。每个指向该矩阵的Mat对象被释放,此计数器都会减少。当计数器达到零时,该矩阵被释放。要想要复制矩阵本身,使用cv::Mat::clone()和cv::Mat::copyTo()函数
4.灰度的实现
imgElement.onload = function() {
let src = cv.imread('imageUpload-gray');
let dst = new cv.Mat();
cv.cvtColor(src, dst, cv.COLOR_RGBA2GRAY, 0);
cv.imshow('canvasOutput-gray', dst);
src.delete(); dst.delete();
};
5.图像阈值的处理
imgElement.onload = function() {
let src = cv.imread('imageUpload');
let dst = new cv.Mat();
cv.threshold(src, dst, 177, 200, cv.THRESH_BINARY);
cv.imshow('canvasOutput', dst);
src.delete(); dst.delete();
};
自适应阈值
imgElement.onload = function() {
let src = cv.imread('imageUpload');
let dst = new cv.Mat();
cv.cvtColor(src, src, cv.COLOR_RGBA2GRAY, 0);
// You can try more different parameters
cv.adaptiveThreshold(src, dst, 200, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 3, 2);
cv.imshow('canvasOutput', dst);
src.delete(); dst.delete();
};
6.边缘检测
定义
边缘检测是指在数字图像中寻找并识别物体边界的过程。在影像处理中,边缘通常表示著图像中不同区域之间的剧烈变化或不连续性,这些变化可能由物体的几何结构、亮度、颜色或纹理等特征引起。边缘检测的目标是找到这些变化的位置,以便进一步的分析和处理,例如物体侦测、图像分割、特征提取等。常见的边缘检测算法有Canny边缘检测、Sobel算子、Laplacian算子等。
OpenCV提供Canny()函数用于在灰度影像中寻找轮廓。由于边缘侦测容易受到影像中杂讯的影响,因此用 5x5 高斯滤波器去除影像中的杂讯。然后使用 Sobel 在水平和垂直方向上对平滑后的图像进行滤波,获得水平方向上的一阶导数和垂直方向,如下所示:
获得梯度大小和方向后,对影像进行全面扫描,以去除可能不构成边缘的任何不必要的像素,因此在每个像素处,检查像素是否是其邻域中梯度方向的局部最大值。
最后透过两个阈值minVal 和 maxVal,进行边缘判断,梯度大于 maxVal 的任何边缘是图像边缘,而低于 minVal 的边缘不是图像边缘。
其他两种方式,有兴趣可自行学习了解。
cvat加入灰度功能
1. 建立gray.ts文件
opencv-wrapper文件夹下放置所有opencv功能的文件,
- 建立gray.ts用于灰度。同其他文件一样
- 建立progressImage函数,接收当前图片的src和frame
- 创建Mat对象,grayImg
- 转换格式,调用cvtColor进行灰度处理,转为Uint8格式,创建新的imageData图片
同其他文件相同,opencv-wrapper引入,gray是我们用来做灰度的方法,通过ImgProc暴露出去
2.侧边栏controls-side-bar文件夹下面找到你想接入的地方
这里我把原有的直方图均衡化功能改成了灰度功能,功能类似,图标也刚好吻合。thresholded-control文件里面更改了调用方式。调用不是最困难的,因为钩子函数就在那,难点在于怎么把返回的图片数据替换掉,毕竟cvat里面的逻辑众多,要逐步去看。
3.套用Histogram功能
一时间摸不清返回的结构数据怎样才能和主结构渲染上,看到了histogram也是对图片进行直方图均衡化返回的imgData数据(同1截图),返回相同结构
总结
cvat数据管理过于庞大,数据处理地点较多,处理比较麻烦,要耐心寻找数据源头。贴合项目,已二次开发功能较多,如果同需求欢迎交流,如有问题欢迎指正~
规划也在逐步的学习python阶段,之后也会进行总结
参考文档(如有侵权联系删除):