0

Phân đoạn hình ảnh trong OpenCV

I. Phân đoạn hình ảnh sử dụng watersheds

Việc chuyển đổi đầu nguồn là một thuật toán xử lý hình ảnh phổ biến được sử dụng để nhanh chóng phân khúc một hình ảnh thành các vùng đồng nhất. Nó dựa trên ý tưởng rằng khi hình ảnh được xem như là một cứu trợ topo, vùng đồng nhất tương ứng với lưu vực tương đối bằng phẳng delimitated bởi cạnh dốc. Như một kết quả của sự đơn giản của nó, phiên bản gốc của thuật toán này có xu hướng trên phân đoạn hình ảnh trong đó sản xuất nhiều khu vực nhỏ. Đây là lý do tại sao OpenCV đề xuất một biến thể của thuật toán này là sử dụng một tập hợp các dấu hiệu được xác định trước đó hướng dẫn việc xác định các phân đoạn ảnh.

II. Làm thế nào để phân loại

Các phân khúc đầu nguồn thu được thông qua việc sử dụng các chức năng cv :: đầu nguồn. Đầu vào của hàm này là một hình ảnh đánh dấu số nguyên 32-bit, trong đó mỗi khác không điểm ảnh đại diện cho một nhãn. Ý tưởng là để đánh dấu một số điểm ảnh của hình ảnh mà được biết chắc chắn thuộc về một khu vực nhất định. Từ nhãn ban đầu này, các thuật toán đầu nguồn sẽ xác định các khu vực mà các điểm ảnh khác thuộc. Trong công thức này, đầu tiên chúng ta sẽ tạo ra hình ảnh đánh dấu như một hình ảnh màu xám cấp, và sau đó chuyển đổi nó thành một hình ảnh của các số nguyên. Chúng tôi thuận tiện đóng gói bước này thành một lớp WatershedSegmenter:

class WatershedSegmenter {
 private:
 cv::Mat markers;
public:
 void setMarkers(const cv::Mat& markerImage) {
 // Convert to image of ints
 markerImage.convertTo(markers,CV_32S);
 }
 cv::Mat process(const cv::Mat &image) {
 // Apply watershed
 cv::watershed(image,markers);
 return markers;
 }

Cách các điểm đánh dấu thu được phụ thuộc vào ứng dụng. Ví dụ, một số bước tiền xử lý có thể dẫn đến việc xác định một số điểm ảnh thuộc một đối tượng quan tâm. Các lưu vực sau đó sẽ được sử dụng để phân định các đối tượng hoàn chỉnh từ đó phát hiện ban đầu. Trong công thức này, chúng ta sẽ chỉ cần sử dụng các hình ảnh nhị phân được sử dụng trong các bài viết trước ( OpenCV: Xử lý ảnh sử dụng bộ lọc hình thái ) để xác định các loài động vật của ảnh gốc tương ứng.

Vì vậy, từ hình ảnh nhị phân của chúng tôi, chúng ta cần phải xác định điểm ảnh đó chắc chắn thuộc về phía sau (động vật) và điểm ảnh đó chắc chắn thuộc về nền (chủ yếu là cỏ). Ở đây, chúng tôi sẽ đánh dấu điểm ảnh cận cảnh với nhãn 255 và nền pixel với nhãn 128 (lựa chọn này là hoàn toàn tùy tiện, bất kỳ số nhãn khác hơn 255 sẽ làm việc). Các điểm ảnh khác, đó là những người mà việc ghi nhãn là không rõ, được giao nhiệm vụ giá trị 0. Như bây giờ, hình ảnh nhị phân bao gồm quá nhiều điểm ảnh màu trắng thuộc bộ phận khác nhau của hình ảnh. Sau đó chúng tôi sẽ làm xói mòn nghiêm trọng hình ảnh này để chỉ giữ lại các điểm ảnh thuộc các đối tượng quan trọng:

// Loại bỏ nhiễu và các đối tượng nhỏ hơn
cv :: Mat fg;
cv :: xói mòn (nhị phân, fg, cv :: Mat (), cv :: Point (-1, -1), 6);

Lưu ý rằng một vài điểm ảnh thuộc rừng nền vẫn còn hiện hữu. Hãy chỉ đơn giản là giữ chúng. Do đó, họ sẽ được coi là tương ứng với một đối tượng quan tâm. Tương tự như vậy, chúng tôi cũng chọn một vài điểm ảnh của các nền bằng một sự giãn nở lớn của hình ảnh nhị phân ban đầu:

// Xác định điểm ảnh hình ảnh mà không cần đối tượng
cv :: Mat bg;
cv::dilate(binary,bg,cv::Mat(),cv::Point(-1,-1),6);
cv::threshold(bg,bg,1,128,cv::THRESH_BINARY_INV);

Kết quả là các điểm ảnh màu đen tương ứng với điểm ảnh nền. Đây là lý do tại sao các hoạt động ngưỡng ngay lập tức sau khi sự giãn nở giao cho các điểm ảnh giá trị 128.

Những hình ảnh này được kết hợp để tạo thành hình ảnh đánh dấu:

// Tạo dấu hình ảnh
cv :: dấu Mát (binary.size (), CV_8U, cv :: Scalar (0));
dấu = fg + bg;

Lưu ý cách chúng ta sử dụng quá tải toán tử + ở đây để kết hợp các hình ảnh. Sau đó các phân khúc thu được như sau:

// Tạo đầu nguồn phân khúc đối tượng
WatershedSegmenter segmenter;
// Set mốc và quá trình
segmenter.setMarkers (đánh dấu);
segmenter.process (hình ảnh);

Những hình ảnh đánh dấu sau đó được cập nhật như vậy mà mỗi không điểm ảnh được gán một trong những nhãn đầu vào, trong khi các điểm ảnh thuộc địa giới tìm thấy có giá trị -1.

Làm thế nào nó hoạt động...

Như chúng ta đã làm trong các công thức trước, chúng ta sẽ sử dụng bản đồ tương tự topo trong mô tả của thuật toán đầu nguồn. Để tạo ra một phân khúc ngoặt, ý tưởng là để dần dần tràn ngập hình ảnh bắt đầu ở mức 0. Khi mức độ "nước" tăng dần (mức 1, 2, 3, vv), các lưu vực lưu vực được hình thành. Kích thước của các lưu vực cũng tăng dần và, do đó, nước của hai lưu vực khác nhau cuối cùng sẽ hợp nhất. Khi điều này xảy ra, một lưu vực được tạo ra để giữ cho hai lưu vực tách. Khi mực nước đã đạt mức tối đa của nó, là bộ các lưu vực tạo ra và lưu vực sông hình thành các phân khúc ngoặt.

Như người ta có thể mong đợi, quá trình làm ngập ban đầu tạo ra nhiều lưu vực cá nhân nhỏ. Khi tất cả những được sáp nhập, nhiều dòng đầu nguồn được tạo ra mà kết quả trong một hình ảnh trên-phân đoạn. Để khắc phục vấn đề này, một sự thay đổi thuật toán này đã được đề xuất, trong đó quá trình lũ lụt bắt đầu từ một bộ định sẵn của các điểm ảnh được đánh dấu. Các lưu vực tạo ra từ các điểm đánh dấu được dán nhãn phù hợp với các giá trị được gán cho nhãn hiệu ban đầu. Khi hai lưu vực có nhãn hợp nhau, không có lưu vực sông được tạo ra, do đó ngăn ngừa các oversegmentation.

Đây là những gì sẽ xảy ra khi các cv :: ngoặt hàm được gọi. Những hình ảnh đánh dấu đầu vào được cập nhật để sản xuất các phân khúc ngoặt thức. Người dùng có thể nhập vào một hình ảnh đánh dấu với bất kỳ số lượng nhãn với điểm ảnh ghi nhãn chưa biết còn lại để đánh giá 0. ảnh đánh dấu đã được chọn để trở thành một hình ảnh của một số nguyên 32-bit để có thể xác định hơn 255 nhãn. Nó cũng cho phép các giá trị đặc biệt -1, được gán cho các điểm ảnh kết hợp với một bước ngoặt. Đây là những gì được trả về bởi các cv :: lưu vực chức năng. Để thuận lợi cho việc hiển thị các kết quả, chúng tôi đã giới thiệu hai phương pháp đặc biệt. Người đầu tiên trả về một hình ảnh của các nhãn (với lưu vực sông ở giá trị 0). Điều này rất dễ thực hiện thông qua ngưỡng:

// Return kết quả dưới dạng một hình ảnh cv :: Mat getSegmentation () { cv :: Mat tmp; // tất cả các phân khúc với nhãn cao hơn 255 // sẽ được gán giá trị 255 markers.convertTo (tmp, CV_8U); trở lại tmp; }

Tương tự như vậy, phương pháp thứ hai trả về một ảnh mà trong đó các dòng đầu nguồn đang giao giá trị 0, và phần còn lại của hình ảnh là 255. Thời gian này, cv :: ConvertTo phương pháp được sử dụng để đạt được kết quả này:

// Quay trở lại đầu nguồn trong các hình thức của một hình ảnh
cv :: Mat getWatersheds () {
cv :: Mat tmp;
// Mỗi pixel p được chuyển thành
// 255p + 255 trước khi chuyển đổi
markers.convertTo (tmp, CV_8U, 255.255);
trở tmp;
}

Sự biến đổi tuyến tính được áp dụng trước khi chuyển đổi cho phép -1 điểm ảnh được chuyển đổi thành 0 (kể từ -1 * 255 + 255 = 0).

Pixels với một giá trị lớn hơn 255 được gán giá trị là 255. Điều này là do các hoạt động bão hòa được áp dụng khi số nguyên ký kết được chuyển đổi thành các ký tự không dấu.


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.