介绍
OpenCV或开源计算机视觉库,是一个用于图像处理和图像识别的强大库。该库拥有庞大的社区,并已在许多领域得到广泛使用,从人脸检测到交互艺术。它最初是用 C++ 构建的,但是已经为不同的语言创建了绑定,例如 Python 和 Java。它甚至可以在 JavaScript 中作为 OpenCV.js 使用,这就是我们将在本教程中使用的内容。
在这个项目中,我们将创建一个网页,用户可以在其中上传图片以检测其中包含的所有圆圈。我们将用黑色轮廓突出显示圆圈,用户将能够下载修改后的图像。
此项目的代码可在此 GitHub 存储库中找到。
先决条件
要完成本教程,您需要引入 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
具有以下模板的文件:
<!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>
标签来完成:
<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>
:
<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 添加到of 中的单独<style>
标记中。默认情况下,微调器是不可见的(感谢):<head>
index.html
display: none;
/* 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>
.
document.body.classList.add('loading');
当 OpenCV.js 加载时,我们需要隐藏加载的 gif。修改<script>
引用本地opencv.js
文件的标签以添加onload
事件监听器:
<script async src="opencv.js" onload="onOpenCvReady();" type="text/javascript"></script>
然后添加onOpenCvReady
到另一个<script>
标签来处理删除"loading"
类:
// previous code is here
function onOpenCvReady() {
document.body.classList.remove('loading');
}
在浏览器中打开 HTML 页面并检查 OpenCV.js 是否按预期加载。
注意:使用浏览器的开发人员工具,您应该验证Console选项卡中没有错误消息,并且Network选项卡显示opencv.js
正确引用的文件。您将定期在浏览器中刷新此页面以查看您的最新更改。
现在您已经设置了项目,您已准备好构建图像上传功能。
第 2 步 – 上传图像
要创建上传功能,首先添加一个<input>
元素到index.html
:
<input type="file" id="fileInput" name="file" />
如果我们只想显示源图像,我们还需要添加一个<img>
元素和一个事件侦听器,它响应<input>
元素上的更改。复制以下标签并将其放在<input>
标签下:
<img id="imageSrc" alt="No Image" />
使用它们的值获取<img>
元素和<input>
元素id
:
// previous code is here
let imgElement = document.getElementById('imageSrc');
let inputElement = document.getElementById('fileInput');
现在,添加事件侦听器,它会在<input>
更改时(即,上传文件时)触发。从更改事件中,可以访问上传的文件 ( event.target.files[0]
),并使用 将其转换为 URL URL.createObjectURL
。图像的src
属性可以更新为这个 URL:
// previous code is here
inputElement.onchange = function() {
imgElement.src = URL.createObjectURL(event.target.files[0]);
};
在原始图像旁边,我们可以显示第二个图像,指示检测到的圆圈。图像将与一个<canvas>
元素一起显示,该元素用于使用 JavaScript 绘制图形:
<canvas id="imageCanvas"></canvas>
我们可以添加另一个事件监听器来更新<canvas>
上传的图像:
// previous code is here
imgElement.onload = function() {
let image = cv.imread(imgElement);
cv.imshow('imageCanvas', image);
image.delete();
};
在此步骤中,您已设置图像上传和显示功能。在下一步中,您将探索如何使用 OpenCV 检测圆。
第 3 步 – 检测圆圈
这就是 OpenCV 的强大之处,因为检测圆圈是一项内置任务。我们想在用户单击按钮时找到圆圈,因此我们需要添加按钮和事件侦听器:
<button type="button" id="circlesButton" class="btn btn-primary">Circle Detection</button>
// previous code is here
document.getElementById('circlesButton').onclick = function() {
// circle detection code
};
根据图像,圆圈检测可能需要一段时间,因此最好禁用该按钮以防止用户多次点击它。在按钮上显示加载微调器也很有用。我们可以重用初始脚本加载中的加载 gif:
// 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
– 显示给用户(我们将在其上绘制突出显示的圆圈)
对于 final Mat
,我们可以使用clone
函数制作第一个的副本:
// 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
是灰度的常数。
cv.cvtColor(srcMat, srcMat, cv.COLOR_RGBA2GRAY);
该cvtColor
函数与其他 OpenCV.js 函数一样,接受更多参数。这些不是必需的,因此它们将被设置为默认值。您可以参考文档以获得更好的自定义。
一旦图像被转换为灰度,就可以使用该HoughCircles
函数来检测圆圈。
该功能需要:
- 来源
Mat
从那里会找到的圆圈(srcMat
) Mat
将存储圆圈的目的地(circlesMat
)- 检测圆的方法 (
cv.HOUGH_GRADIENT
) - 累加器分辨率的反比 (
1
) - 圆心之间的最小距离 (
45
)
有更多参数、算法阈值(75
和40
),可以用来提高图像的准确性。
还可以通过设置最小 ( 0
) 和最大半径 ( 0
)来限制要检测的圆的范围。
cv.HoughCircles(srcMat, circlesMat, cv.HOUGH_GRADIENT, 1, 45, 75, 40, 0, 0);
现在,我们应该有一个Mat
检测到圆圈的对象。
接下来,我们将在我们的<canvas>
.
第 4 步 – 绘制圆圈
现在可以突出显示检测到的所有圆圈。我们想在每个圆圈周围制作一个轮廓以显示给用户。要使用 OpenCV.js 绘制圆,我们需要中心点和半径。这些值存储在 中circlesMat
,因此我们可以通过遍历矩阵的列来检索它:
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];
要获取每个圆圈的所有值,我们可以执行以下操作:
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
:
let center = new cv.Point(x, y);
cv.circle(displayMat, center, radius, [0, 0, 0, 255], 3);
画圆的全部代码如下:
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
,我们就可以将其显示给用户:
cv.imshow('imageCanvas', displayMat);
最后,清理Mat
我们不再需要的对象是一种很好的做法。这样做是为了防止内存问题:
srcMat.delete();
displayMat.delete();
circlesMat.delete();
当我们把它们放在一起时,圆检测和绘制代码将如下所示:
// 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
文件中添加一个超链接:
<a href="#" id="downloadButton">Download Image</a>
我们将 设置href
为图像 URL,将download
属性设置为图像文件名。设置download
属性向浏览器指示应该下载资源而不是导航到它。我们可以<canvas>
使用函数创建图像 URL toDataURL()
。
将以下 JavaScript 添加到 的底部<script>
:
// previous code is here
document.getElementById('downloadButton').onclick = function() {
this.href = document.getElementById('imageCanvas').toDataURL();
this.download = 'image.png';
};
现在用户可以轻松下载修改后的图像。
结论
使用 OpenCV 可以检测圆圈。一旦您习惯了将图像作为Mat
对象进行操作,您就可以做更多的事情。该HoughCircles
算法是 OpenCV 提供的众多算法之一,使图像处理和图像识别变得更加容易。
您可以在OpenCV 网站上找到更多教程,包括人脸识别和模板匹配。您还可以通过访问机器学习主题页面来阅读有关计算机视觉的更多信息。