【纯前端】10分钟教你在CVAT中加入Opencv灰度功能

1,031 阅读9分钟
1.jpeg 浅放一张枫叶图🍁,听说秋天和富士更配🍁

引言

今年这一年:
独立完成公司的v2->v3项目升级,这是一个巨大工作量的迁移 ✅
旅行了已经计划很久很喜欢的国内外城市 ✅
...
等等,充实且饱满的生活让我最近有时间来总结一下最近学到的,欢迎指点讨论👏🏻

项目

我们项目致力于打造AI工业质检的平台,进而接触到了很多标注平台标注工具。

免费考试必备单词思维导图.png

为什么选择CVAT

CVAT是一款开源的数据标注平台,支持大部分数据标注类型,如2D包围框、多边形、语义分割立体框关键点折线LiDAR包围框、圆等。并支持嵌入AI模型,用于优化数据标注流程和提升效率。CVAT(Computer Vision Annotation Tool)是一款功能强大、高效的开源数据标注平台,专为计算机视觉数据集设计。作为行业领先的数据引擎,CVAT被广泛应用于机器学习项目中,受到各种规模的团队的信赖和使用。

2.gif

优势

CVAT拥有庞大的体系,稳定的功能,有兴趣的可以了解他的使用和docker部署与安装步骤,官网写的很详细

cvat-web源码

cvat-web采用React框架配合antd组件库+ts整体结构,先来看看前端部分的目录结构:

截屏2024-11-26 14.33.52.png
  • 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领域第四类编程语言。

3.webp

我们来看到cvat-data部分引入了Broadway.js,文档中也有说到为什么我们呢把文件放在他那里,是以为作者不提供npm包,所以我们需要储存一些组件在我们自己的仓库。
从github仓库我们能看到,这是一个js解码器,主要用于视频播放器部分,视频播放器首先需要下载整个视频才能开始播放,因此一开始会显得有点慢,所以请耐心等待。您可以通过单击每个播放器来开始播放视频。左上角的播放器在主线程上运行,而其余的播放器在后台工作线程中运行。
不得不说cvat里面采用了很多值得借鉴的图片和视频的优化,值得很多借鉴。

截屏2024-11-26 14.47.31.png

2.引入opencv

在cvat-ui中项目拉取了opencv的开放接口,引入了opencv.js。页面中智能标注功能部分预加载了此部分,调用opencv方法完成了此功能。

截屏2024-11-26 14.55.45.png

初识opencv.js

1.环境搭建

环境搭建,官网部分给了详细的步骤,按照步骤安装即可。

2.图像坐标系定义

5.png

说明:
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对象之间共享矩阵,节省存储空间。另外,复制运算符也只是复制矩阵头和指向包含像素值的矩阵的指针,而不是数据本身。

截屏2024-11-26 15.15.37.png 上述例子中所有对象都指向同一个数据矩阵,对其中任何一个对象的像素值进行修改都会影响其他所有对象。实际上,不同的对象只是为相同的底层数据提供不同的访问方法。然而,它们的头部是不同的。我们可以创建仅引用完整数据的一个子部分的矩阵头。例如,要在图像中创建感兴趣区域(ROI),只需创建具有新矩阵头的Mat对象:

截屏2024-11-26 15.16.11.png

矩阵是由最后一个使用它的对象来进行内存释放的。这是通过使用引用计数机制来处理的。每当有人复制Mat对象的矩阵头时,矩阵的计数器就会增加。每个指向该矩阵的Mat对象被释放,此计数器都会减少。当计数器达到零时,该矩阵被释放。要想要复制矩阵本身,使用cv::Mat::clone()和cv::Mat::copyTo()函数

截屏2024-11-26 15.16.37.png

4.灰度的实现

imgElement.onload = function() {          
    let src = cv.imread('imageUpload-gray');                 
    let dst = new cv.Mat(); 
    cv.cvtColor(src, dst, cv.COLOR_RGBA2GRAY0); 
    cv.imshow('canvasOutput-gray', dst); 
    src.delete(); dst.delete();             
};

6.png

5.图像阈值的处理

imgElement.onload = function() {          
    let src = cv.imread('imageUpload');     
    let dst = new cv.Mat();    
    cv.threshold(src, dst, 177200, cv.THRESH_BINARY);
    cv.imshow('canvasOutput', dst);     
    src.delete(); dst.delete();
};

7.png 自适应阈值

imgElement.onload = function() {          
    let src = cv.imread('imageUpload');     
    let dst = new cv.Mat();     
    cv.cvtColor(src, src, cv.COLOR_RGBA2GRAY0);
    // You can try more different parameters
    cv.adaptiveThreshold(src, dst, 200, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY32);     
    cv.imshow('canvasOutput', dst);     
    src.delete(); dst.delete();
};

6.边缘检测

定义
边缘检测是指在数字图像中寻找并识别物体边界的过程。在影像处理中,边缘通常表示著图像中不同区域之间的剧烈变化或不连续性,这些变化可能由物体的几何结构、亮度、颜色或纹理等特征引起。边缘检测的目标是找到这些变化的位置,以便进一步的分析和处理,例如物体侦测、图像分割、特征提取等。常见的边缘检测算法有Canny边缘检测、Sobel算子、Laplacian算子等。

OpenCV提供Canny()函数用于在灰度影像中寻找轮廓。由于边缘侦测容易受到影像中杂讯的影响,因此用 5x5 高斯滤波器去除影像中的杂讯。然后使用 Sobel 在水平和垂直方向上对平滑后的图像进行滤波,获得水平方向上的一阶导数和垂直方向,如下所示:

截屏2024-11-26 15.35.05.png

获得梯度大小和方向后,对影像进行全面扫描,以去除可能不构成边缘的任何不必要的像素,因此在每个像素处,检查像素是否是其邻域中梯度方向的局部最大值。

截屏2024-11-26 15.35.24.png

最后透过两个阈值minVal 和 maxVal,进行边缘判断,梯度大于 maxVal 的任何边缘是图像边缘,而低于 minVal 的边缘不是图像边缘。

截屏2024-11-26 15.35.43.png

截屏2024-11-26 15.36.13.png

截屏2024-11-26 15.36.35.png 其他两种方式,有兴趣可自行学习了解。

cvat加入灰度功能

1. 建立gray.ts文件

opencv-wrapper文件夹下放置所有opencv功能的文件,

  • 建立gray.ts用于灰度。同其他文件一样
  • 建立progressImage函数,接收当前图片的src和frame
  • 创建Mat对象,grayImg
  • 转换格式,调用cvtColor进行灰度处理,转为Uint8格式,创建新的imageData图片

截屏2024-11-26 15.39.22.png

同其他文件相同,opencv-wrapper引入,gray是我们用来做灰度的方法,通过ImgProc暴露出去

截屏2024-11-26 15.43.03.png

截屏2024-11-26 15.43.18.png

2.侧边栏controls-side-bar文件夹下面找到你想接入的地方

这里我把原有的直方图均衡化功能改成了灰度功能,功能类似,图标也刚好吻合。thresholded-control文件里面更改了调用方式。调用不是最困难的,因为钩子函数就在那,难点在于怎么把返回的图片数据替换掉,毕竟cvat里面的逻辑众多,要逐步去看。

截屏2024-11-26 15.44.59.png

截屏2024-11-26 15.49.35.png

3.套用Histogram功能

一时间摸不清返回的结构数据怎样才能和主结构渲染上,看到了histogram也是对图片进行直方图均衡化返回的imgData数据(同1截图),返回相同结构

截屏2024-11-26 16.01.42.png

总结

cvat数据管理过于庞大,数据处理地点较多,处理比较麻烦,要耐心寻找数据源头。贴合项目,已二次开发功能较多,如果同需求欢迎交流,如有问题欢迎指正~

规划也在逐步的学习python阶段,之后也会进行总结

参考文档(如有侵权联系删除):