使用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

算法有更多参数和阈值(7540),可以用来提高图像的准确性。

也可以通过设置最小 (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 依次存储中心点和半径的 xy 值。

例如,对于第一个圆圈,可以按如下方式检索值:

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 网站 上找到更多教程,包括人脸识别和模板匹配。 您还可以通过访问 机器学习主题页面 了解更多关于计算机视觉的信息。