Giới thiệu về Thị giác máy tính trong JavaScript sử dụng OpenCV.js
OpenCV là một thư viện mạnh mẽ được sử dụng để xử lý hình ảnh và nhận dạng hình ảnh. Thư viện, Open-Source Computer Vision, có một cộng đồng lớn và đã được sử dụng rộng rãi trong nhiều lĩnh vực, từ nhận diện khuôn mặt đến nghệ thuật tương tác. Nó lần đầu tiên được xây dựng bằng C ++ nhưng các ràng buộc đã được tạo ra cho các ngôn ngữ khác nhau, chẳng hạn như Python và Java. Nó thậm chí có sẵn trong JavaScript dưới dạng OpenCV.js, Đây là kết quả ta sẽ sử dụng cho hướng dẫn này.Trong dự án này, ta sẽ tạo một trang web nơi user có thể tải lên hình ảnh để phát hiện tất cả các vòng kết nối có trong đó. Ta sẽ đánh dấu các vòng tròn bằng đường viền màu đen và user sẽ có thể download hình ảnh đã sửa đổi.
Mã cho dự án này có sẵn trong repo Github này .
Bước 1 - Cài đặt dự án
Tạo folder opencvjs-project
và thêm index.html
với mẫu sau:
<!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>
Ta cần kéo thư viện OpenCV.js vào. Phiên bản mới nhất có thể được tạo theo hướng dẫn trong tài liệu OpenCV chính thức hoặc bạn có thể sao chép file cho v3.3.1 từ đây trên trang web OpenCV và lưu nó local dưới dạng opencv.js
. Thêm thẻ script vào index.html
tham chiếu đến file 'opencv.js' local . Tập lệnh khá lớn và mất một chút thời gian để tải, vì vậy tốt hơn là tải nó một cách không đồng bộ. Điều này có thể được thực hiện bằng cách thêm async
cho thẻ script:
<script async src="opencv.js" type="text/javascript"></script>
Vì OpenCV.js chưa sẵn sàng ngay lập tức, ta có thể cung cấp trải nghiệm user tốt hơn bằng cách hiển thị rằng nội dung đang được tải. Ta có thể thêm một vòng quay tải vào trang (tín dụng cho Sampson ). Tóm lại, hãy thêm thẻ div ở cuối nội dung và CSS sau vào một thẻ kiểu riêng biệt ở đầu trang. Spinner ẩn theo mặc định (nhờ vào 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; }
Để hiển thị gif đang tải, ta có thể thêm lớp "loading"
vào nội dung. Thêm phần sau vào đầu tập lệnh trống.
document.body.classList.add("loading");
Khi Opencv.js tải, ta sẽ muốn ẩn gif. Sửa đổi thẻ script để thêm trình xử lý sự kiện onload
:
<script async src="opencv.js" onload="onOpenCvReady();" type="text/javascript"></script>
Điều này cho phép ta loại bỏ lớp "loading"
:
// previous code is here function onOpenCvReady() { document.body.classList.remove("loading"); }
Mở trang HTML trong trình duyệt của bạn và kiểm tra xem OpenCV.js có tải như mong đợi hay không.
Bước 2 - Tải lên hình ảnh
Bước tiếp theo là thêm thẻ đầu vào để user có thể tải lên hình ảnh:
<input type="file" id="fileInput" name="file" />
Nếu ta chỉ muốn hiển thị hình ảnh nguồn, ta cũng cần thêm thẻ hình ảnh và trình xử lý sự kiện phản hồi thay đổi trên phần tử đầu vào. Sao chép phần tử sau và đặt nó dưới thẻ đầu vào:
<img id="imageSrc" alt="No Image" />
Nhận cả phần tử hình ảnh và phần tử đầu vào bằng cách sử dụng ID của chúng:
// previous code is here let imgElement = document.getElementById('imageSrc'); let inputElement = document.getElementById('fileInput');
Bây giờ thêm trình xử lý sự kiện sẽ kích hoạt khi đầu vào thay đổi (tức là khi file được tải lên). Từ sự kiện thay đổi, bạn có thể truy cập file đã tải lên ( event.target.files[0]
) và chuyển đổi nó thành URL bằng URL.createObjectURL
. Thuộc tính src của hình ảnh có thể được cập nhật thành URL này:
// previous code is here inputElement.onchange = function() { imgElement.src = URL.createObjectURL(event.target.files[0]); };
Bên cạnh hình ảnh root , ta có thể hiển thị hình ảnh thứ hai mà ta sẽ sửa đổi. Hình ảnh sẽ được hiển thị với phần tử canvas được sử dụng để vẽ đồ họa bằng JavaScript:
<canvas id="imageCanvas" ></canvas>
Ta có thể thêm một trình xử lý sự kiện khác cập nhật canvas với hình ảnh đã tải lên:
// previous code is here imgElement.onload = function() { let image = cv.imread(imgElement); cv.imshow('imageCanvas', image); image.delete(); };
Bước 3 - Phát hiện vòng kết nối
Đây là nơi sức mạnh của OpenCV được thể hiện rõ ràng, vì việc phát hiện các vòng kết nối là một nhiệm vụ được tích hợp sẵn. Ta muốn tìm các vòng kết nối khi user nhấp vào một nút, vì vậy ta cần thêm nút và trình xử lý sự kiện:
<button type="button" id="circlesButton" class="btn btn-primary">Circle Detection</button>
// previous code is here document.getElementById('circlesButton').onclick = function() { // circle detection code };
Tùy thuộc vào hình ảnh, việc phát hiện vòng kết nối có thể mất một khoảng thời gian vì vậy bạn nên tắt nút để ngăn user gửi spam. Nó cũng có thể hữu ích khi hiển thị một vòng quay tải trên nút. Ta có thể sử dụng lại gif đang tải từ lần tải tập lệnh ban đầu:
// 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"); };
Bước đầu tiên để phát hiện các vòng tròn là đọc hình ảnh từ canvas.
Trong OpenCV, hình ảnh được lưu trữ và thao tác dưới dạng đối tượng Mat
. Đây thực chất là các ma trận giữ các giá trị cho mỗi pixel trong hình ảnh. Để phát hiện vòng tròn của ta , ta cần ba đối tượng Mat. Một để giữ hình ảnh nguồn (từ đó phát hiện các vòng tròn) srcMat
, một để lưu trữ các vòng tròn mà ta phát hiện circlesMat
và một để hiển thị cho user (trên đó ta sẽ vẽ các vòng tròn được đánh dấu của ta ) displayMat
. Đối với Mat cuối cùng, ta có thể tạo một bản sao của Mat đầu tiên bằng cách sử dụng hàm clone
:
let srcMat = cv.imread('imageCanvas'); let displayMat = srcMat.clone(); let circlesMat = new cv.Mat();
srcMat
cần được chuyển đổi thành thang độ xám. Điều này giúp phát hiện vòng tròn nhanh hơn bằng cách đơn giản hóa hình ảnh. Ta có thể sử dụng hàm cvtColor
để thực hiện điều này, hàm này yêu cầu Mat nguồn ( srcMat
), Mat đích (trong trường hợp này Mat nguồn và Mat đích sẽ giống nhau srcMat
) và một giá trị đề cập đến việc chuyển đổi màu. cv.COLOR_RGBA2GRAY
là hằng số cho thang độ xám:
cv.cvtColor(srcMat, srcMat, cv.COLOR_RGBA2GRAY);
Hàm cvtColor
, giống như các hàm OpenCV.js khác, chấp nhận nhiều tham số hơn nhưng những tham số này không bắt buộc và do đó sẽ được đặt thành mặc định. Bạn có thể xem tài liệu để tùy chỉnh tốt hơn.
Sau khi hình ảnh được chuyển đổi sang thang độ xám, bạn có thể sử dụng chức năng HoughCircles
để phát hiện các vòng tròn. Hàm này cần một Mat nguồn, srcMat
, từ đó nó sẽ tìm các vòng kết nối và một Mat đích, circlesMat
, nơi nó sẽ lưu trữ các vòng kết nối. Các tham số khác cần thiết cho hàm HoughCircles là phương pháp phát hiện các vòng tròn ( cv.HOUGH_GRADIENT
), tỷ lệ nghịch của độ phân giải tích lũy ( 1
) và khoảng cách tối thiểu giữa điểm trung tâm của các vòng tròn ( 45
). Có nhiều tham số, ngưỡng cho thuật toán ( 75
và 40
), có thể được chơi với để cải thiện độ chính xác cho hình ảnh của bạn. Cũng có thể giới hạn phạm vi của các vòng tròn mà bạn muốn phát hiện bằng cách đặt bán kính tối thiểu ( 0
) và tối đa ( 0
).
cv.HoughCircles(srcMat, circlesMat, cv.HOUGH_GRADIENT, 1, 45, 75, 40, 0, 0);
Bây giờ ta sẽ có một đối tượng Mat với các vòng tròn được phát hiện trong đó. Tiếp theo, ta sẽ vẽ các vòng tròn trong canvas của ta .
Bước 4 - Vẽ vòng tròn
Bây giờ có thể đánh dấu tất cả các vòng kết nối đã được phát hiện. Ta muốn tạo một đường viền xung quanh mỗi vòng tròn để hiển thị cho user . Để vẽ một đường tròn bằng OpenCV.js, ta cần tâm điểm và bán kính. Các giá trị này được lưu trữ bên trong circlesMat
và vì vậy ta có thể truy xuất nó bằng cách lặp qua các cột của ma trận:
for (let i = 0; i < circlesMat.cols; ++i) { // draw circles }
circlesMat
lưu trữ các giá trị x và y cho điểm trung tâm và bán kính một cách tuần tự. Vì vậy, đối với vòng kết nối đầu tiên, có thể lấy các giá trị như sau:
let x = circlesMat.data32F[0]; let y = circlesMat.data32F[1]; let radius = circlesMat.data32F[2];
Để nhận tất cả các giá trị cho mỗi vòng kết nối, ta có thể làm như sau:
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 }
Cuối cùng, với tất cả các giá trị này, ta có thể vẽ các đường viền xung quanh các vòng tròn. Ta tạo một Điểm mới cho trung tâm bằng cách sử dụng các giá trị x và y. Để vẽ các vòng tròn trong OpenCV.js, ta cần một Mat đích (hình ảnh ta sẽ hiển thị cho user displayMat
), điểm trung tâm, giá trị bán kính và một đại lượng vô hướng (một mảng các giá trị RBG). Ngoài ra còn có các tham số bổ sung có thể được chuyển vào các circles
, chẳng hạn như độ dày của đường đối với ví dụ này là 3:
let center = new cv.Point(x, y); cv.circle(displayMat, center, radius, [0, 0, 0, 255], 3);
Tất cả mã để vẽ vòng tròn như sau:
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); }
Sau khi vẽ xong tất cả các vòng tròn trên displayMat
, ta có thể hiển thị nó cho user :
cv.imshow('imageCanvas', displayMat);
Cuối cùng, bạn nên dọn dẹp các đối tượng Mat mà ta không còn cần nữa. Điều này được thực hiện để ngăn ngừa các vấn đề về bộ nhớ:
srcMat.delete(); displayMat.delete(); circlesMat.delete();
Nhìn chung, mã phát hiện và vẽ vòng kết nối trông giống như sau:
// 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"); };
Bước 5 - Download hình ảnh
Sau khi hình ảnh đã được sửa đổi, user có thể cần download . Để thực hiện việc này, hãy thêm một siêu liên kết vào file index.html của bạn:
<a href="#" id="downloadButton">Download Image</a>
Ta đặt href thành URL hình ảnh và thuộc tính download cho tên file hình ảnh. Việc đặt thuộc tính download cho trình duyệt biết rằng tài nguyên nên được download thay vì chuyển đến tài nguyên đó. Ta có thể tạo URL hình ảnh từ canvas bằng cách sử dụng hàm toDataURL()
.
Thêm JavaScript sau vào cuối tập lệnh:
// previous code is here document.getElementById('downloadButton').onclick = function() { this.href = document.getElementById("imageCanvas").toDataURL(); this.download = "image.png"; };
Kết luận
Phát hiện vòng kết nối là một nhiệm vụ khá cơ bản với OpenCV. Một khi bạn đã quen với việc thao tác hình ảnh dưới dạng đối tượng Mat, bạn có thể làm được nhiều việc hơn thế. Thuật toán HoughCircles
là một trong nhiều thuật toán được OpenCV cung cấp để giúp xử lý hình ảnh và nhận dạng hình ảnh dễ dàng hơn nhiều. Bạn có thể tìm thêm các hướng dẫn, bao gồm nhận dạng khuôn mặt và đối sánh mẫu, trên trang web OpenCV . OpenCV có rất nhiều hỗ trợ trong các ngôn ngữ khác nhau và tôi hy vọng hướng dẫn này cho thấy nó mạnh mẽ như thế nào khi sử dụng trong JavaScript.
Các tin liên quan
Thêm, loại bỏ & chuyển đổi lớp với classList trong JavaScript2020-10-13
Cách Chèn Javascript vào HTML bằng Thẻ script
2020-09-23
Mẫu thiết kế module trong JavaScript
2020-09-21
Mẫu thiết kế trình quan sát trong JavaScript
2020-09-21
Mẫu thiết kế Singleton trong JavaScript
2020-09-21
Mẫu thiết kế nguyên mẫu trong JavaScript
2020-09-21
Lời hứa của JavaScript dành cho người giả
2020-09-15
Sao chép các đối tượng trong JavaScript
2020-09-15
Cách sử dụng API tìm nạp JavaScript để lấy dữ liệu
2020-09-15
Cách mã hóa và giải mã chuỗi với Base64 trong JavaScript
2020-09-15