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