使用OpenCV.js在JavaScript中介绍计算机视觉
介绍
OpenCV,即开源计算机视觉库,是用于图像处理和图像识别的强大库。 该图书馆拥有庞大的社区,并已在许多领域广泛使用,从人脸检测到互动艺术。 它最初是用 C++ 构建的,但已经为不同的语言创建了绑定,例如 Python 和 Java。 它甚至可以在 JavaScript 中作为 OpenCV.js 使用,这就是我们将在本教程中使用的。
在这个项目中,我们将创建一个网页,用户可以在其中上传图片以检测其中包含的所有圆圈。 我们将用黑色轮廓突出显示圆圈,用户将能够下载修改后的图像。
该项目的代码可在 this GitHub repo 中找到。
先决条件
要完成本教程,您需要引入 OpenCV.js 库。 3.3.1 版本可在此处获得:
https://docs.opencv.org/3.3.1/opencv.js
将此文件在本地保存为 opencv.js
在您可以轻松找到的位置。
第 1 步 — 设置项目
要开始,您首先需要为您的项目创建一个空间。 创建一个名为 opencvjs-project
的目录:
mkdir opencvjs-project
将 opencv.js
的本地副本移动到此目录。
接下来,使用以下模板添加一个 index.html
文件:
索引.html
<!DOCTYPE html> <html> <head> <title>OpenCV.js</title> </head> <body> <!-- Our HTML will go here--> <script type="text/javascript"> // Our JavaScript code will go here </script> </body> </html>
除了此文件中现有的空 <script>
标记之外,添加一个新的 <script>
标记,它引用本地 opencv.js
文件。 脚本很大,加载需要一些时间,所以最好异步加载。 这可以通过将 async
添加到 <script>
标签来完成:
索引.html
<script type="text/javascript"> // Our JavaScript code will go here </script> <script async src="opencv.js" type="text/javascript"></script>
由于文件大小,OpenCV.js 可能无法立即准备好,我们可以通过显示内容正在加载来提供更好的用户体验。 我们可以向页面添加一个加载微调器(感谢 StackOverflow 上的 Sampson)。
首先,添加一个 <div>
元素 <body>
:
索引.html
<body> <!-- Our HTML will go here--> <div class="modal"></div> <script type="text/javascript"> // Our JavaScript code will go here </script> <script async src="opencv.js" type="text/javascript"></script> </body>
接下来,将以下 CSS 添加到 index.html
的 <head>
中单独的 <style>
标记中。 微调器默认是不可见的(感谢 display: none;
):
索引.html
/* display loading gif and hide webpage */ .modal { display: none; position: fixed; z-index: 1000; top: 0; left: 0; height: 100%; width: 100%; background: rgba( 255, 255, 255, .8) url('http://i.stack.imgur.com/FhHRx.gif') 50% 50% no-repeat; } /* prevent scrollbar from display during load */ body.loading { overflow: hidden; } /* display the modal when loading class is added to body */ body.loading .modal { display: block; }
为了显示加载 gif,我们可以将 "loading"
类添加到正文中。 将以下内容添加到空的 <script>
。
索引.html
document.body.classList.add('loading');
当 OpenCV.js 加载时,我们想要隐藏加载 gif。 修改引用本地 opencv.js
文件的 <script>
标记以添加 onload
事件监听器:
索引.html
<script async src="opencv.js" onload="onOpenCvReady();" type="text/javascript"></script>
然后将 onOpenCvReady
添加到另一个 <script>
标记以处理删除 "loading"
类:
索引.html
// previous code is here function onOpenCvReady() { document.body.classList.remove('loading'); }
在浏览器中打开 HTML 页面并检查 OpenCV.js 是否按预期加载。
注意: 使用浏览器的开发者工具,您应该验证 Console 选项卡中没有错误消息,并且 Network 选项卡显示 [ X180X] 文件被正确引用。 您将定期在浏览器中刷新此页面以查看您的最新更改。
既然您已经设置了项目,就可以构建图像上传功能了。
第 2 步 — 上传图片
要创建上传功能,首先将 <input>
元素添加到 index.html
:
索引.html
<input type="file" id="fileInput" name="file" />
如果我们只想显示源图像,我们还需要添加一个 <img>
元素和一个事件监听器,它会响应 <input>
元素的变化。 复制以下标签并将其放在 <input>
标签下:
索引.html
<img id="imageSrc" alt="No Image" />
使用 id
值获取 <img>
元素和 <input>
元素:
索引.html
// previous code is here let imgElement = document.getElementById('imageSrc'); let inputElement = document.getElementById('fileInput');
现在,添加事件侦听器,它会在 <input>
更改时触发(即,上传文件时)。 通过更改事件,可以访问上传的文件(event.target.files[0]
),并使用 URL.createObjectURL 将其转换为 URL)。 图片的 src
属性可以更新为这个 URL:
索引.html
// previous code is here inputElement.onchange = function() { imgElement.src = URL.createObjectURL(event.target.files[0]); };
在原始图像旁边,我们可以显示第二张图像,指示检测到的圆圈。 图像将使用 <canvas>
元素显示,该元素用于使用 JavaScript 绘制图形:
索引.html
<canvas id="imageCanvas"></canvas>
我们可以添加另一个事件监听器,用上传的图像更新 <canvas>
:
索引.html
// previous code is here imgElement.onload = function() { let image = cv.imread(imgElement); cv.imshow('imageCanvas', image); image.delete(); };
在此步骤中,您已设置图像上传和显示功能。 在下一步中,您将探索如何使用 OpenCV 检测圆圈。
第 3 步 - 检测圆圈
这就是 OpenCV 的强大之处,因为检测圆圈是一项内置任务。 我们希望在用户单击按钮时找到圆圈,因此我们需要添加按钮和事件侦听器:
索引.html
<button type="button" id="circlesButton" class="btn btn-primary">Circle Detection</button>
索引.html
// previous code is here document.getElementById('circlesButton').onclick = function() { // circle detection code };
根据图像,圆形检测可能需要一段时间,因此最好禁用按钮以防止用户多次点击它。 在按钮上显示加载微调器也很有用。 我们可以重用初始脚本加载中的加载 gif:
索引.html
// previous code is here document.getElementById('circlesButton').onclick = function() { this.disabled = true; document.body.classList.add('loading'); // circle detection code this.disabled = false; document.body.classList.remove('loading'); };
检测圆圈的第一步是从 <canvas>
读取图像。
在 OpenCV 中,图像作为 Mat
对象进行存储和操作。 这些本质上是保存图像中每个像素值的矩阵。
对于我们的圆形检测,我们将需要三个 Mat 对象:
srcMat
- 保存源图像(从中检测到圆圈)circlesMat
- 存储我们检测到的圆圈displayMatOne
- 向用户显示(我们将在其上绘制突出显示的圆圈)
对于最终的 Mat
,我们可以使用 clone
函数复制第一个:
索引.html
// circle detection code let srcMat = cv.imread('imageCanvas'); let displayMat = srcMat.clone(); let circlesMat = new cv.Mat();
srcMat
需要转灰度。 这通过简化图像使圆检测更快。 我们可以使用 cvtColor
函数来做到这一点。
该功能需要:
- 源
Mat
(srcMat
) - 目标
Mat
(在这种情况下,源和目标 Mat 将是相同的srcMat
) - 指颜色转换的值。
cv.COLOR_RGBA2GRAY
是灰度常数。
索引.html
cv.cvtColor(srcMat, srcMat, cv.COLOR_RGBA2GRAY);
cvtColor
函数与其他 OpenCV.js 函数一样,接受更多参数。 这些不是必需的,因此它们将设置为默认值。 您可以参考 文档 以获得更好的自定义。
一旦图像转换为灰度,就可以使用HoughCircles
函数来检测圆圈。
该功能需要:
- 一个源
Mat
从中可以找到圆圈 (srcMat
) - 一个目的地
Mat
它将存储圆圈 (circlesMat
) - 检测圆的方法(
cv.HOUGH_GRADIENT
) - 累加器分辨率的反比 (
1
) - 圆心之间的最小距离(
45
)
算法有更多参数和阈值(75
和 40
),可以用来提高图像的准确性。
也可以通过设置最小 (0
) 和最大半径 (0
) 来限制要检测的圆的范围。
索引.html
cv.HoughCircles(srcMat, circlesMat, cv.HOUGH_GRADIENT, 1, 45, 75, 40, 0, 0);
现在,我们应该有一个 Mat
对象,其中检测到圆圈。
接下来,我们将在 <canvas>
中绘制圆圈。
第 4 步 — 画圆圈
现在可以突出显示所有检测到的圆圈。 我们想在每个圆圈周围画一个轮廓以显示给用户。 要使用 OpenCV.js 绘制圆,我们需要中心点和半径。 这些值存储在 circlesMat
中,因此我们可以通过遍历矩阵的列来检索它:
索引.html
for (let i = 0; i < circlesMat.cols; ++i) { // draw circles }
circlesMat
依次存储中心点和半径的 x
和 y
值。
例如,对于第一个圆圈,可以按如下方式检索值:
let x = circlesMat.data32F[0]; let y = circlesMat.data32F[1]; let radius = circlesMat.data32F[2];
要获取每个圆圈的所有值,我们可以执行以下操作:
索引.html
for (let i = 0; i < circlesMat.cols; ++i) { let x = circlesMat.data32F[i * 3]; let y = circlesMat.data32F[i * 3 + 1]; let radius = circlesMat.data32F[i * 3 + 2]; // draw circles }
最后,使用所有这些值,我们能够在圆圈周围绘制轮廓。
要在 OpenCV.js 中绘制圆圈,我们需要:
- 目的地
Mat
(我们要向用户显示的图像 -displayMat
) - 一个中心
Point
(使用 x 和 y 值) - 半径值
- 标量(RGB 值数组)
还有一些额外的参数可以传入 circles
,例如线条粗细,在本例中为 3
:
索引.html
let center = new cv.Point(x, y); cv.circle(displayMat, center, radius, [0, 0, 0, 255], 3);
画圆的全部代码如下:
索引.html
for (let i = 0; i < circlesMat.cols; ++i) { let x = circlesMat.data32F[i * 3]; let y = circlesMat.data32F[i * 3 + 1]; let radius = circlesMat.data32F[i * 3 + 2]; let center = new cv.Point(x, y); // draw circles cv.circle(displayMat, center, radius, [0, 0, 0, 255], 3); }
一旦我们在 displayMat
上画完所有的圆圈,我们就可以将它显示给用户:
索引.html
cv.imshow('imageCanvas', displayMat);
最后,清理我们不再需要的 Mat
对象是一个很好的做法。 这样做是为了防止内存问题:
索引.html
srcMat.delete(); displayMat.delete(); circlesMat.delete();
当我们把它们放在一起时,圆形检测和绘制代码将如下所示:
索引.html
// previous code is here document.getElementById('circlesButton').onclick = function() { this.disabled = true; document.body.classList.add('loading'); let srcMat = cv.imread('imageCanvas'); let displayMat = srcMat.clone(); let circlesMat = new cv.Mat(); cv.cvtColor(srcMat, srcMat, cv.COLOR_RGBA2GRAY); cv.HoughCircles(srcMat, circlesMat, cv.HOUGH_GRADIENT, 1, 45, 75, 40, 0, 0); for (let i = 0; i < circlesMat.cols; ++i) { let x = circlesMat.data32F[i * 3]; let y = circlesMat.data32F[i * 3 + 1]; let radius = circlesMat.data32F[i * 3 + 2]; let center = new cv.Point(x, y); // draw circles cv.circle(displayMat, center, radius, [0, 0, 0, 255], 3); } cv.imshow('imageCanvas', displayMat); srcMat.delete(); displayMat.delete(); circlesMat.delete(); this.disabled = false; document.body.classList.remove('loading'); };
这样,您就添加了检测和围绕图像中的圆圈绘制圆圈的逻辑。
第 5 步 — 下载图像
图像被修改后,用户可能想要下载它。 为此,请在 index.html
文件中添加一个超链接:
索引.html
<a href="#" id="downloadButton">Download Image</a>
我们将 href
设置为图像 URL,将 download
属性设置为图像文件名。 设置 download
属性向浏览器指示应该下载资源而不是导航到它。 我们可以使用函数 toDataURL() 从 <canvas>
创建图像 URL。
将以下 JavaScript 添加到 <script>
的底部:
索引.html
// previous code is here document.getElementById('downloadButton').onclick = function() { this.href = document.getElementById('imageCanvas').toDataURL(); this.download = 'image.png'; };
现在用户可以轻松下载修改后的图像。
结论
使用 OpenCV 可以检测圆圈。 一旦您习惯了将图像作为 Mat
对象进行操作,您就可以做更多的事情。 HoughCircles
算法是 OpenCV 提供的众多算法之一,可以使图像处理和图像识别变得更加容易。
您可以在 OpenCV 网站 上找到更多教程,包括人脸识别和模板匹配。 您还可以通过访问 机器学习主题页面 了解更多关于计算机视觉的信息。