opencv 计算机视觉
介绍 (Introduction)
OpenCV is a powerful library used for image processing and image recognition. The library, Open-Source Computer Vision, has a massive community and has been used extensively in many fields, from face detection to interactive art. It was first built in C++ but bindings have since been created for different languages, such as Python and Java. It is even available in JavaScript as OpenCV.js, which is what we’ll be using for this tutorial.
OpenCV是一个功能强大的库,用于图像处理和图像识别。 该库名为“开放源代码计算机视觉”,具有庞大的社区,并在从人脸检测到交互艺术的许多领域中得到广泛使用。 它最初是用C ++构建的,但是此后已为不同的语言(例如Python和Java)创建了绑定。 它甚至可以在JavaScript中以OpenCV.js的形式获得,这就是本教程将使用的内容。
In this project, we will create a webpage where a user can upload an image in order to detect all the circles contained in it. We will highlight the circles with a black outline and the user will be able download the modified image.
在这个项目中,我们将创建一个网页,用户可以在其中上传图像,以便检测其中包含的所有圆圈。 我们将用黑色轮廓突出显示圆圈,用户将可以下载修改后的图像。
The code for this project is available in this Github repo.
这个项目的代码可以在这个Github仓库中找到 。
第1步-设置项目 (Step 1 — Setting Up the Project)
Create a folder opencvjs-project
and add an index.html
file with the following template:
创建一个文件夹opencvjs-project
并使用以下模板添加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>
We will need to pull in the OpenCV.js library. The latest version can be built following the instructions in the official OpenCV docs, or you can copy the file for v3.3.1 from here on the OpenCV website and save it locally as opencv.js
. Add a script tag in the index.html
file which references the local ‘opencv.js’ file. The script is quite large and takes a bit of time to load, so it is better load it asynchronously. This can be done by adding async
to the script tag:
我们将需要引入OpenCV.js库。 可以按照OpenCV官方文档中的说明构建最新版本,也可以从OpenCV网站上的此处复制v3.3.1的文件,然后将其另存为opencv.js
。 在index.html
文件中添加引用本地“ opencv.js”文件的脚本标签。 该脚本非常大,需要花费一些时间来加载,因此最好以异步方式加载它。 这可以通过向script标签添加async
来完成:
<script async src="opencv.js" type="text/javascript"></script>
As OpenCV.js won’t be ready immediately, we can provide a better user experience by showing that the content is being loaded. We can add a loading spinner to the page (credit to Sampson). In brief, add a div tag at the bottom of the body and the following CSS into a separate style tag at the top of the page. The spinner is invisible by default (thanks to display: none;
):
由于OpenCV.js不会立即准备就绪,因此我们可以通过显示内容正在加载来提供更好的用户体验。 我们可以在页面上添加加载微调器(贷记为Sampson )。 简而言之,将div标签添加到正文底部,并将以下CSS添加到页面顶部的单独样式标签中。 默认情况下,微调器是不可见的(感谢display: none;
):
<body>
...
<div class="modal"></div>
<body>
/* 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;
}
To show the loading gif, we can add the "loading"
class to the body. Add the following to the top of the empty script.
要显示加载的gif,我们可以将"loading"
类添加到主体。 将以下内容添加到空脚本的顶部。
document.body.classList.add("loading");
When Opencv.js loads, we’ll want to hide the gif. Modify the script tag to add an onload
event listener:
加载Opencv.js时,我们将要隐藏gif。 修改脚本标记以添加onload
事件侦听器:
<script async src="opencv.js" onload="onOpenCvReady();" type="text/javascript"></script>
This allows us to remove the "loading"
class:
这使我们可以删除"loading"
类:
// previous code is here
function onOpenCvReady() {
document.body.classList.remove("loading");
}
Open the HTML page in your browser and check that OpenCV.js loads as expected.
在浏览器中打开HTML页面,并检查OpenCV.js是否按预期加载。
第2步-上传图像 (Step 2 — Uploading the Image)
Next step is to add an input tag so the user can upload an image:
下一步是添加输入标签,以便用户可以上传图片:
<input type="file" id="fileInput" name="file" />
If we just want to display the source image, we’ll also need to add an image tag and an event listener which responds to change on the input element. Copy the following element and place it under the input tag:
如果我们只想显示源图像,则还需要添加一个图像标签和一个事件侦听器,以响应输入元素上的更改。 复制以下元素并将其放在输入标签下:
<img id="imageSrc" alt="No Image" />
Get both the image element and the input element using their IDs:
使用其ID来获取图像元素和输入元素:
// previous code is here
let imgElement = document.getElementById('imageSrc');
let inputElement = document.getElementById('fileInput');
Now add the event listener which triggers when the input changes (i.e. when a file is uploaded). From the change event, it’s possible to access the uploaded file (event.target.files[0]
), and convert it into a URL using URL.createObjectURL
. The image’s src attribute can be updated to this URL:
现在添加事件侦听器,该事件侦听器将在输入更改(即,文件上传)时触发。 通过change事件,可以访问上载的文件( event.target.files[0]
),然后使用URL.createObjectURL
将其转换为URL。 图像的src属性可以更新为以下URL:
// previous code is here
inputElement.onchange = function() {
imgElement.src = URL.createObjectURL(event.target.files[0]);
};
Next to the original image, we can show a second image which we’re going to modify. The image will be displayed with a canvas element which are used for drawing graphics with JavaScript:
在原始图像旁边,我们可以显示第二张要修改的图像。 图像将显示有canvas元素,用于使用JavaScript绘制图形:
<canvas id="imageCanvas" ></canvas>
We can add another event listener which updates the canvas with the uploaded image:
我们可以添加另一个事件监听器,该事件监听器使用上传的图像更新画布:
// previous code is here
imgElement.onload = function() {
let image = cv.imread(imgElement);
cv.imshow('imageCanvas', image);
image.delete();
};
第3步-检测圈子 (Step 3 — Detecting Circles)
This is where the power of OpenCV is evident, as detecting circles is a built-in task. We want to find the circles when the user clicks a button, so we’ll need to add the button and an event listener:
这是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
};
Depending on the image, circle detection may take a while so it is a good idea to disable the button to prevent the user from spamming it. It could also be useful to show a loading spinner on the button. We can reuse the loading gif from the initial script load:
根据图像的不同,圈子检测可能需要一段时间,因此最好禁用该按钮以防止用户向其发送垃圾邮件。 在按钮上显示加载微调器也可能很有用。 我们可以重用初始脚本加载中的加载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");
};
The first step to detecting the circles is reading the image from the canvas.
检测圆圈的第一步是从画布读取图像。
In OpenCV, images are stored and manipulated as Mat
objects. These are essentially matrices which hold values for each pixel in the image. For our circle detection, we’re going to need three Mat objects. One to hold the source image (from which detect the circles) srcMat
, one to store the circles we detect circlesMat
and one to display to the user (on which we will draw our highlighted circles) displayMat
. For the final Mat, we can make a copy of the first using the clone
function:
在OpenCV中,图像作为Mat
对象存储和操作。 这些本质上是保存图像中每个像素值的矩阵。 为了检测圆,我们将需要三个Mat对象。 一个用于保存源图像(从中检测圆)( srcMat
,一个用于存储我们检测到的circlesMat
的圆,另一个用于显示给用户(我们将在其上绘制突出显示的圆) displayMat
。 对于最终的Mat,我们可以使用clone
函数复制第一个:
let srcMat = cv.imread('imageCanvas');
let displayMat = srcMat.clone();
let circlesMat = new cv.Mat();
The srcMat
needs to be converted to grayscale. This makes circle detection faster by simplifying the image. We can use cvtColor
function to do this, which requires the source Mat (srcMat
), the destination Mat (in this case the source and the destination Mat will be the same srcMat
), and a value which refers to the colour conversion. cv.COLOR_RGBA2GRAY
is the constant for grayscale:
srcMat
需要转换为灰度。 通过简化图像,可以更快地检测圆。 我们可以使用cvtColor
函数来执行此操作,这需要源Mat( srcMat
),目标Mat(在这种情况下,源Mat和目标Mat将是相同的srcMat
),以及一个引用颜色转换的值。 cv.COLOR_RGBA2GRAY
是灰度常数:
cv.cvtColor(srcMat, srcMat, cv.COLOR_RGBA2GRAY);
The cvtColor
function, like other OpenCV.js functions, accepts more parameters but these are not required and so will be set to the default. You can look at the documentation for better customisation.
该cvtColor
功能,如其他OpenCV.js功能,接受更多的参数,但这些不是必需的,因此将被设置为默认值。 您可以查看文档以获得更好的自定义。
Once the image is converted to grayscale, it’s possible to use the HoughCircles
function to detect the circles. This function needs a source Mat, srcMat
, from where it’ll find the circles and a destination Mat, circlesMat
, where it’ll store the circles. The other parameters required for the HoughCircles function are the method to detect circles (cv.HOUGH_GRADIENT
), the inverse ratio of the accumulator resolution (1
), and the minimum distance between the center point of circles (45
). There are more parameters, thresholds for the algorithm (75
and 40
), which can be played with to improve accuracy for your images. It is also possible to limit the range of the circles you want to detect by setting a minimum (0
) and maximum radius (0
).
将图像转换为灰度后,可以使用HoughCircles
函数检测圆。 此功能需要一个源Mat srcMat
,从中可以找到圆,并需要一个目标Mat circlesMat
,可以在其中存储圆。 HoughCircles函数所需的其他参数是检测圆的方法( cv.HOUGH_GRADIENT
),累加器分辨率的反比( 1
)以及圆心之间的最小距离( 45
)。 还有更多的参数,算法的阈值( 75
和40
),可以使用这些参数来提高图像的准确性。 通过设置最小( 0
)和最大半径( 0
),也可以限制要检测的圆的范围。
cv.HoughCircles(srcMat, circlesMat, cv.HOUGH_GRADIENT, 1, 45, 75, 40, 0, 0);
Now we should have a Mat object with the circles detected in it. Next we’ll draw the circles in our canvas.
现在我们应该有一个Mat对象,其中检测到了圆圈。 接下来,我们将在画布上绘制圆圈。
步骤4 —画圆 (Step 4 — Drawing Circles)
All the circles which were detected can now be highlighted. We want to make an outline around each circle to show to the user. To draw a circle with OpenCV.js, we need the center point and the radius. These values are stored inside circlesMat
and so we can retrieve it by looping through the matrix’s columns:
现在可以突出显示所有检测到的圆圈。 我们希望在每个圆圈周围绘制轮廓,以显示给用户。 要使用OpenCV.js画一个圆,我们需要中心点和半径。 这些值存储在circlesMat
内部,因此我们可以通过遍历矩阵的列来检索它:
for (let i = 0; i < circlesMat.cols; ++i) {
// draw circles
}
The circlesMat
stores the x and y values for the center point and the radius sequentially. So for the first circle, it would be possible to retrieve the values as follows:
circlesMat
存储中心点和半径的x和y值。 因此,对于第一个圆,可以按以下方式检索值:
let x = circlesMat.data32F[0];
let y = circlesMat.data32F[1];
let radius = circlesMat.data32F[2];
To get all the values for each circle, we can do the following:
要获取每个圆的所有值,我们可以执行以下操作:
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
}
Finally, with all these values, we are able to draw outlines around the circles. We create a new Point for the center using the x and y values. To draw circles in OpenCV.js, we need a destination Mat (the image we’re going to display to the user displayMat
), the center Point, the radius value, and a scalar (an array of RBG values). There are also additional parameters which can be passed into circles
, such as the line thickness which for this example is 3:
最后,利用所有这些值,我们能够在圆周围绘制轮廓。 我们使用x和y值为中心创建一个新的Point。 要在OpenCV.js中绘制圆,我们需要一个目标Mat(将要显示给用户displayMat
),中心点,半径值和一个标量(一组RBG值)。 还有一些其他参数可以传递到circles
,例如线粗,在本示例中为3:
let center = new cv.Point(x, y);
cv.circle(displayMat, center, radius, [0, 0, 0, 255], 3);
All the code for drawing circles is as follows:
绘制圆的所有代码如下:
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);
cv.circle(displayMat, center, radius, [0, 0, 0, 255], 3);
}
Once we’re done drawing all the circles on displayMat
, we can show it to the user:
在displayMat
上绘制完所有圆之后,我们可以将其显示给用户:
cv.imshow('imageCanvas', displayMat);
Finally, it’s good practice to clean up the Mat objects which we’ll no longer be needing. This is done to prevent memory problems:
最后,优良作法是清理不再需要的Mat对象。 这样做是为了防止出现内存问题:
srcMat.delete();
displayMat.delete();
circlesMat.delete();
Altogether the circle detection and drawing code looks like this:
圆检测和绘制代码总共如下所示:
// 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);
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 —下载图像 (Step 5 — Downloading the Image)
After the image has been modified, the user may want to download it. To do this, add a hyperlink to your index.html file:
修改图像后,用户可能要下载它。 为此,将超链接添加到您的index.html文件:
<a href="#" id="downloadButton">Download Image</a>
We set the href to the image URL and the download attribute to the image file name. Setting the download attribute indicates to the browser that the resource should be downloaded rather than navigating to it. We can create the image URL from the canvas using the function toDataURL()
.
我们将href设置为图像URL,将download属性设置为图像文件名。 设置下载属性会向浏览器指示应下载资源,而不是导航至该资源。 我们可以使用toDataURL()
函数从画布创建图像URL。
Add the following JavaScript to the bottom of the script:
在脚本底部添加以下JavaScript:
// previous code is here
document.getElementById('downloadButton').onclick = function() {
this.href = document.getElementById("imageCanvas").toDataURL();
this.download = "image.png";
};
结论 (Conclusion)
Detecting circles is a fairly basic task with OpenCV. Once you get accustomed to manipulating images as Mat objects, there is so much more you can do. The HoughCircles
algorithm is one of many provided by OpenCV to make image processing and image recognition that much easier. You can find more tutorials, including face recognition and template matching, on the OpenCV website. OpenCV has a lot of support in the different languages and I hope this tutorial shows just how powerful it is to use in JavaScript.
使用OpenCV检测圈子是一项相当基本的任务。 一旦习惯了将图像作为Mat对象进行操作,您可以做更多的事情。 HoughCircles
算法是OpenCV提供的众多算法之一,它使图像处理和图像识别变得更加容易。 您可以在OpenCV网站上找到更多教程,包括人脸识别和模板匹配。 OpenCV在不同的语言中有很多支持,我希望本教程展示在JavaScript中使用它的功能。
opencv 计算机视觉