網站首頁 編程語言 正文
Photoshop中的套索工具通過鼠標多次點擊可以選中一個任意多邊形的區域,然后單獨對這塊區域進行編輯,下面就使用OpenCV實現一個簡單的功能,模擬Photoshop中的套索工具。
這里的套索工具通過鼠標左鍵在圖片上多次點擊創建任意多個點,右鍵點擊后將這些點連成封閉的多邊形,形成一塊待編輯的區域,鍵盤方向鍵控制該區域的移動,從而將該區域內的圖像復制到原圖像的其他地方。
首先定義下列全局變量
const char* winName = "TaoSuoTool";//窗口名稱 cv::Mat resultImg;//最終在OpenCV窗口上顯示的圖像 cv::Mat foregroundImg;//編輯前的圖像 cv::Mat areaMask;//蒙版,多邊形區域實際繪制在該蒙版上 cv::Point maskLocation;//蒙版位置,通過方向鍵移動后蒙版位置隨之變化 std::vector<cv::Point> drawingPoints;//區域完成前正在點擊的所有點 std::vector<cv::Point> areaPoints;//區域完成后其多邊形頂點
main函數
int main(int argc, char **arv) { ? ? foregroundImg = cv::imread("test.jpg"); ? ? foregroundImg.copyTo(resultImg); ? ? areaMask = cv::Mat::zeros(foregroundImg.size(), CV_8U); ? ? cv::imshow(winName, resultImg); ? ? ? maskLocation.x = maskLocation.y = 0; ? ? cv::setMouseCallback(winName, OnMouseEvent); ? ? int key = cv::waitKeyEx(0); ? ? while (key != VK_ESCAPE) ? ? { ? ? ? ? key = cv::waitKeyEx(0); ? ? } ? ? return 0; }
在鼠標回調函數OnMouseEvent中處理三個消息:鼠標左鍵按下,鼠標右鍵按下和鼠標移動
void OnMouseEvent(int event, int x, int y, int flags, void* userdata) { ? ? if (event == cv::EVENT_LBUTTONDOWN) ? ? { ? ? ? ? OnLeftMouseButtonDown(x,y); ? ? } ? ? else if (event == cv::EVENT_RBUTTONDOWN) ? ? { ? ? ? ? OnRightMouseButtonDown(x,y); ? ? } ? ? if (event == cv::EVENT_MOUSEMOVE) ? ? { ? ? ? ? OnMouseMove(x,y); ? ? } }
在編寫鼠標事件前先定義一個函數
void OnCompleteArea(bool bDrawOutline);
它表示完成當前區域的編輯,包括右鍵點擊完成封閉多邊形、移動區域以及合成最終圖片。參數bDrawOutline表示繪制區域多邊形的外輪廓,右鍵點擊完成封閉多邊形和移動區域過程中都要顯示輪廓(bDrawOutline=true),合成最終圖片后就不需要顯示輪廓了(bDrawOutline=false)。
鼠標左鍵按下事件:先判斷是否有前一個區域存在,存在則先完成前一個區域并且不顯示區域輪廓,然后開始繪制新的區域多邊形的點,點與點之間用藍色線連接,點位置處繪制一個4X4的紅色矩形。
void OnLeftMouseButtonDown(int x,int y) { ? ? if (drawingPoints.empty() && areaPoints.size() > 0) ? ? { ? ? ? ? OnCompleteArea(false); ? ? } ? ? drawingPoints.push_back(cv::Point(x, y)); ? ? cv::rectangle(resultImg, cv::Rect(x - 2, y - 2, 4, 4), CV_RGB(255, 0, 0), -1); ? ? if (drawingPoints.size() >= 2) ? ? { ? ? ? ? cv::line(resultImg, drawingPoints[drawingPoints.size() - 2], cv::Point(x, y), CV_RGB(0, 0, 255), 1); ? ? } ? ? cv::imshow(winName, resultImg); }
鼠標移動事件:判斷drawingPoints是否為空,如果已經存在點則繪制線和點,并且還要繪制一根連接到鼠標當前位置的線。
void OnMouseMove(int x,int y) { ? ? if (drawingPoints.size() > 0) ? ? { ? ? ? ? foregroundImg.copyTo(resultImg); ? ? ? ? for (int i = 0; i < drawingPoints.size() - 1; i++) ? ? ? ? { ? ? ? ? ? ? cv::rectangle(resultImg, cv::Rect(drawingPoints[i].x - 2, drawingPoints[i].y - 2, 4, 4), CV_RGB(255, 0, 0), -1); ? ? ? ? ? ? cv::line(resultImg, drawingPoints[i], drawingPoints[i + 1], CV_RGB(0, 0, 255), 1); ? ? ? ? } ? ? ? ? cv::rectangle(resultImg, cv::Rect(drawingPoints[drawingPoints.size() - 1].x - 2, drawingPoints[drawingPoints.size() - 1].y - 2, 4, 4), CV_RGB(255, 0, 0), -1); ? ? ? ? cv::line(resultImg, drawingPoints[drawingPoints.size() - 1], cv::Point(x, y), CV_RGB(0, 0, 255), 1); ? ? ? ? cv::imshow(winName, resultImg); ? ? } }
鼠標右鍵按下事件:如果點個數少于2,不能形成有效區域則不做處理(不考慮多個點共線),否則就在蒙版Area上繪制一個多邊形區域,然后調用OnCompleteArea完成區域編輯,這里需要畫多邊形輪廓,參數傳入true。
void OnRightMouseButtonDown(int x,int y) { ? ? if (drawingPoints.size() >= 3) ? ? { ? ? ? ? areaPoints = drawingPoints; ? ? ? ? std::vector<std::vector<cv::Point>> polys; ? ? ? ? polys.push_back(areaPoints); ? ? ? ? cv::fillPoly(areaMask, polys, cv::Scalar::all(255)); ? ? ? ? OnCompleteArea(true); ? ? } ? ? else ? ? { ? ? ? ? foregroundImg.copyTo(resultImg); ? ? } ? ? drawingPoints.clear(); ? ? cv::imshow(winName, resultImg); }
下面是OnCompleteArea函數的實現,其中MergeImages函數通過蒙版以及蒙版的位置合成最終的圖像,蒙版中區域內的像素值大于0,其他像素值都為0,默認圖像是三通道(destImg.at<cv::Vec3b>)
void MergeImages(cv::Mat& destImg, const cv::Mat& srcImg, const cv::Mat& maskImg, int x, int y) { ? ? int top = y > 0 ? y : 0; ? ? int left = x > 0 ? x : 0; ? ? int right = destImg.cols > x + srcImg.cols ? x + srcImg.cols : destImg.cols; ? ? int bottom = destImg.rows > y + srcImg.rows ? y + srcImg.rows : destImg.rows; ? ? for (int i = top; i < bottom; i++) ? ? { ? ? ? ? for (int j = left; j < right; j++) ? ? ? ? { ? ? ? ? ? ? int destIndex = i * destImg.cols + j; ? ? ? ? ? ? int srcIndex = (i - top)*srcImg.cols + j - left; ? ? ? ? ? ? int channel = destImg.channels(); ? ? ? ? ? ? if (maskImg.at<uchar>(i - y, j - x) > 0) ? ? ? ? ? ? { ? ? ? ? ? ? ? ? destImg.at<cv::Vec3b>(i, j)[0] = srcImg.at<cv::Vec3b>(i - y, j - x)[0]; ? ? ? ? ? ? ? ? destImg.at<cv::Vec3b>(i, j)[1] = srcImg.at<cv::Vec3b>(i - y, j - x)[1]; ? ? ? ? ? ? ? ? destImg.at<cv::Vec3b>(i, j)[2] = srcImg.at<cv::Vec3b>(i - y, j - x)[2]; ? ? ? ? ? ? } ? ? ? ? } ? ? } } void OnCompleteArea(bool bDrawOutline) { ? ? foregroundImg.copyTo(resultImg); ? ? MergeImages(resultImg, foregroundImg, areaMask, maskLocation.x, maskLocation.y); ? ? if (bDrawOutline) ? ? { ? ? ? ? if (areaPoints.size() >= 3) ? ? ? ? { ? ? ? ? ? ? std::vector<std::vector<cv::Point>> polys; ? ? ? ? ? ? polys.push_back(areaPoints); ? ? ? ? ? ? cv::polylines(resultImg, polys, true, cv::Scalar::all(255)); ? ? ? ? } ? ? } ? ? else ? ? { ? ? ? ? resultImg.copyTo(foregroundImg); ? ? ? ? areaPoints.clear(); ? ? ? ? memset(areaMask.data, 0, areaMask.rows*areaMask.cols*areaMask.elemSize()); ? ? ? ? maskLocation.x = maskLocation.y = 0; ? ? } }
繪制區域之后就可以通過方向按鍵控制區域圖像的移動了(也可以實現為鼠標左鍵按下拖動來移動區域),移動主要是更新maskLocation和areaPoints的坐標值,然后調用OnCompleteArea(true),依然顯示區域的輪廓。
void OnDirectionKeyDown(short keyCode) { ? ? int x = keyCode == VK_LEFT ? -2 : (keyCode == VK_RIGHT ? 2 : 0); ? ? int y = keyCode == VK_UP ? -2 : (keyCode == VK_DOWN ? 2 : 0); ? ? maskLocation.x += x; ? ? maskLocation.y += y; ? ? for (int i = 0; i < areaPoints.size(); i++) ? ? { ? ? ? ? areaPoints[i].x += x; ? ? ? ? areaPoints[i].y += y; ? ? } ? ? OnCompleteArea(true); ? ? cv::imshow(winName, resultImg); }
將上面函數在主函數的按鍵循環中調用,方向按鍵通過key的高16位判斷,在Windows下可以使用虛擬鍵碼宏表示。 同時為了能看到最終合成的圖片加入Enter按鍵消息處理,將圖像合成并去掉輪廓。
int key = cv::waitKeyEx(0); short lowKey = key; short highKey = key >> 16; while (key != VK_ESCAPE) { ? ? if (key == VK_RETURN)//Enter ? ? { ? ? ? ? OnCompleteArea(false); ? ? ? ? cv::imshow(winName, resultImg); ? ? } ? ? else if (lowKey == 0 && (highKey == VK_UP || highKey == VK_DOWN || highKey == VK_LEFT || highKey == VK_RIGHT)) ? ? { ? ? ? ? OnDirectionKeyDown(highKey); ? ? } ? ? key = cv::waitKeyEx(0); ? ? lowKey = key; ? ? highKey = key >> 16; }
這樣一個簡單的套索工具功能就做好了(上面的代碼都是簡化處理,還有很多可以優化的地方,從而使編輯更加流暢)
完整代碼
#include<iostream> #include"opencv2/opencv.hpp" #include<windows.h> const char* winName = "TaoSuoTool"; cv::Point maskLocation; cv::Mat resultImg; cv::Mat foregroundImg; cv::Mat areaMask; std::vector<cv::Point> drawingPoints; std::vector<cv::Point> areaPoints; void MergeImages(cv::Mat& destImg, const cv::Mat& srcImg, const cv::Mat& maskImg, int x, int y) { ? ? int top = y > 0 ? y : 0; ? ? int left = x > 0 ? x : 0; ? ? int right = destImg.cols > x + srcImg.cols ? x + srcImg.cols : destImg.cols; ? ? int bottom = destImg.rows > y + srcImg.rows ? y + srcImg.rows : destImg.rows; ? ? for (int i = top; i < bottom; i++) ? ? { ? ? ? ? for (int j = left; j < right; j++) ? ? ? ? { ? ? ? ? ? ? int destIndex = i * destImg.cols + j; ? ? ? ? ? ? int srcIndex = (i - top)*srcImg.cols + j - left; ? ? ? ? ? ? int channel = destImg.channels(); ? ? ? ? ? ? if (maskImg.at<uchar>(i - y, j - x) > 0) ? ? ? ? ? ? { ? ? ? ? ? ? ? ? destImg.at<cv::Vec3b>(i, j)[0] = srcImg.at<cv::Vec3b>(i - y, j - x)[0]; ? ? ? ? ? ? ? ? destImg.at<cv::Vec3b>(i, j)[1] = srcImg.at<cv::Vec3b>(i - y, j - x)[1]; ? ? ? ? ? ? ? ? destImg.at<cv::Vec3b>(i, j)[2] = srcImg.at<cv::Vec3b>(i - y, j - x)[2]; ? ? ? ? ? ? } ? ? ? ? } ? ? } } ? void OnCompleteArea(bool bDrawOutline) { ? ? foregroundImg.copyTo(resultImg); ? ? MergeImages(resultImg, foregroundImg, areaMask, maskLocation.x, maskLocation.y); ? ? if (bDrawOutline) ? ? { ? ? ? ? if (areaPoints.size() >= 3) ? ? ? ? { ? ? ? ? ? ? std::vector<std::vector<cv::Point>> polys; ? ? ? ? ? ? polys.push_back(areaPoints); ? ? ? ? ? ? cv::polylines(resultImg, polys, true, cv::Scalar::all(255)); ? ? ? ? } ? ? } ? ? else ? ? { ? ? ? ? resultImg.copyTo(foregroundImg); ? ? ? ? areaPoints.clear(); ? ? ? ? memset(areaMask.data, 0, areaMask.rows*areaMask.cols*areaMask.elemSize()); ? ? ? ? maskLocation.x = maskLocation.y = 0; ? ? } } void OnLeftMouseButtonDown(int x,int y) { ? ? if (drawingPoints.empty() && areaPoints.size() > 0) ? ? { ? ? ? ? OnCompleteArea(false); ? ? } ? ? drawingPoints.push_back(cv::Point(x, y)); ? ? cv::rectangle(resultImg, cv::Rect(x - 2, y - 2, 4, 4), CV_RGB(255, 0, 0), -1); ? ? if (drawingPoints.size() >= 2) ? ? { ? ? ? ? cv::line(resultImg, drawingPoints[drawingPoints.size() - 2], cv::Point(x, y), CV_RGB(0, 0, 255), 1); ? ? } ? ? cv::imshow(winName, resultImg); } void OnRightMouseButtonDown(int x,int y) { ? ? if (drawingPoints.size() >= 3) ? ? { ? ? ? ? areaPoints = drawingPoints; ? ? ? ? std::vector<std::vector<cv::Point>> polys; ? ? ? ? polys.push_back(areaPoints); ? ? ? ? cv::fillPoly(areaMask, polys, cv::Scalar::all(255)); ? ? ? ? OnCompleteArea(true); ? ? } ? ? else ? ? { ? ? ? ? foregroundImg.copyTo(resultImg); ? ? } ? ? drawingPoints.clear(); ? ? cv::imshow(winName, resultImg); } void OnMouseMove(int x,int y) { ? ? if (drawingPoints.size() > 0) ? ? { ? ? ? ? foregroundImg.copyTo(resultImg); ? ? ? ? for (int i = 0; i < drawingPoints.size() - 1; i++) ? ? ? ? { ? ? ? ? ? ? cv::rectangle(resultImg, cv::Rect(drawingPoints[i].x - 2, drawingPoints[i].y - 2, 4, 4), CV_RGB(255, 0, 0), -1); ? ? ? ? ? ? cv::line(resultImg, drawingPoints[i], drawingPoints[i + 1], CV_RGB(0, 0, 255), 1); ? ? ? ? } ? ? ? ? cv::rectangle(resultImg, cv::Rect(drawingPoints[drawingPoints.size() - 1].x - 2, drawingPoints[drawingPoints.size() - 1].y - 2, 4, 4), CV_RGB(255, 0, 0), -1); ? ? ? ? cv::line(resultImg, drawingPoints[drawingPoints.size() - 1], cv::Point(x, y), CV_RGB(0, 0, 255), 1); ? ? ? ? cv::imshow(winName, resultImg); ? ? } } void OnMouseEvent(int event, int x, int y, int flags, void* userdata) { ? ? if (event == cv::EVENT_LBUTTONDOWN) ? ? { ? ? ? ? OnLeftMouseButtonDown(x,y); ? ? } ? ? else if (event == cv::EVENT_RBUTTONDOWN) ? ? { ? ? ? ? OnRightMouseButtonDown(x,y); ? ? } ? ? if (event == cv::EVENT_MOUSEMOVE) ? ? { ? ? ? ? OnMouseMove(x,y); ? ? } } ? void OnDirectionKeyDown(short keyCode) { ? ? int x = keyCode == VK_LEFT ? -2 : (keyCode == VK_RIGHT ? 2 : 0); ? ? int y = keyCode == VK_UP ? -2 : (keyCode == VK_DOWN ? 2 : 0); ? ? maskLocation.x += x; ? ? maskLocation.y += y; ? ? for (int i = 0; i < areaPoints.size(); i++) ? ? { ? ? ? ? areaPoints[i].x += x; ? ? ? ? areaPoints[i].y += y; ? ? } ? ? OnCompleteArea(true); ? ? cv::imshow(winName, resultImg); } int main(int argc, char **arv) { ? ? foregroundImg = cv::imread("test.jpg"); ? ? foregroundImg.copyTo(resultImg); ? ? areaMask = cv::Mat::zeros(foregroundImg.size(), CV_8U); ? ? cv::imshow(winName, resultImg); ? ? ? maskLocation.x = maskLocation.y = 0; ? ? cv::setMouseCallback(winName, OnMouseEvent); ? ? int key = cv::waitKeyEx(0); ? ? short lowKey = key; ? ? short highKey = key >> 16; ? ? while (key != VK_ESCAPE) ? ? { ? ? ? ? if (key == VK_RETURN)//Enter ? ? ? ? { ? ? ? ? ? ? OnCompleteArea(false); ? ? ? ? ? ? cv::imshow(winName, resultImg); ? ? ? ? } ? ? ? ? else if (lowKey == 0 && (highKey == VK_UP || highKey == VK_DOWN || highKey == VK_LEFT || highKey == VK_RIGHT)) ? ? ? ? { ? ? ? ? ? ? OnDirectionKeyDown(highKey); ? ? ? ? } ? ? ? ? key = cv::waitKeyEx(0); ? ? ? ? lowKey = key; ? ? ? ? highKey = key >> 16; ? ? } ? ? return 0; }
原文鏈接:https://blog.csdn.net/yb0022/article/details/103892932
相關推薦
- 2022-12-29 Android?Fragment的具體使用方式詳解_Android
- 2022-08-29 Python?GUI?圖形用戶界面_python
- 2022-07-15 python向量化與for循環耗時對比分析_python
- 2022-06-07 ?分享一個Python?遇到數據庫超好用的模塊_python
- 2023-05-14 opencv繪制矩形和圓的實現_python
- 2022-07-09 python?協程并發數控制_python
- 2023-01-10 CentOS7?minimal?最小化安裝網絡設置過程_Linux
- 2022-08-07 詳解Qt使用QImage類實現圖像基本操作_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支