學習圖像處理時,為了快速驗證處理效果,經常需要手動選取ROI區域。其中,多邊形區域是最具普適性的,而有時候我們可能還有一次性提取多個區域的需求。本文實現了該過程,先上效果圖。
鼠標交互
鼠標交互使用OpenCV函數setMouseCallback:
void cv::setMouseCallback(const cv::String &winname, cv::MouseCallback onMouse, void *userdata = (void *)0)
主要實現的就是回調函數onMouse,以下是我實現的回調,用於監聽鼠標滑動和左鍵點擊事件。在左鍵點擊后在圖上繪制出該角點以及末兩點的連線;在鼠標滑動時繪制當前位置點和輪廓始末點的兩條連線,作為效果預估。
//鼠標交互相應 void polygonCallback(int EVENT, int x, int y, int flags, void* userdata) { //獲取窗口顯示圖像 Mat img = *(Mat*) userdata; Mat dst; img.copyTo(dst); //獲取坐標 Point pos(x,y); switch (EVENT) { //鼠標左鍵點擊確認角點 case CV_EVENT_LBUTTONDOWN: { mousePoints.push_back(pos); //繪制點以反饋 circle(img, pos, 4, Scalar(255, 0, 0), -1); size_t end = mousePoints.size(); if (end > 1) { //將輪廓的最后兩個點進行連線 line(img, mousePoints[end - 2], mousePoints[end - 1], Scalar(255, 0, 0)); line(dst, mousePoints[end - 2], mousePoints[end - 1], Scalar(255, 0, 0)); } break; } //鼠標滑動時可以進行預估 case CV_EVENT_MOUSEMOVE: { size_t end = mousePoints.size(); if (end > 1) { //當前位置點和輪廓始末點的連線 line(dst, mousePoints[end - 1], pos, Scalar(255, 0, 0)); line(dst, mousePoints[0], pos, Scalar(255, 0, 0)); } } } //展示dst,而img不含預估線 imshow(windowName, dst); }
掩碼獲取
這個步驟主要是用一個二維的vector存儲多個歷史輪廓,然后使用OpenCV函數drawContours繪制掩碼區域:
void cv::drawContours(cv::InputOutputArray image, cv::InputArrayOfArrays contours, int contourIdx, const cv::Scalar &color, int thickness = 1, int lineType = 8, cv::InputArray hierarchy = noArray(), int maxLevel = 2147483647, cv::Point offset = cv::Point())
具體函數如下:
//ROI多邊形區域選擇 void selectPolygon(const Mat &srcMat, Mat &dstMat) { if (srcMat.empty()) { std::cerr << "srcMat is empty!" << std::endl; return; } imshow(windowName, srcMat); Mat selectMat; char key; srcMat.copyTo(selectMat); std::vector<std::vector<Point>> contours; do{ //鼠標左鍵選擇角點,任意非q按鍵新建選區,q按鍵退出ROI選擇 setMouseCallback(windowName, polygonCallback, &selectMat); key = waitKey(0); //判斷是否能構成多邊形,不能則忽略本次選擇 if (mousePoints.size() < 3) { std::cout << "points are too little!:" << std::endl; mousePoints.clear(); } else { //補出輪廓始末點連線 line(selectMat, mousePoints[0], mousePoints[mousePoints.size() - 1], Scalar(255, 0, 0)); //存儲邊界 contours.push_back(mousePoints); //清空本次輪廓點,准備接收下一個區域 mousePoints.clear(); } }while (key != 'q'); destroyAllWindows(); //實心roi掩碼 //掩碼圖像 Mat mask(srcMat.rows, srcMat.cols, CV_8UC1, Scalar(0)); for (size_t i = 0; i < contours.size(); i++) { drawContours(mask, contours, i, Scalar(255), -1); } mask.copyTo(dstMat); mousePoints.clear(); }