OpenCV实现简单套索工具
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
相关文章
- 这篇文章主要介绍了python-opencv-画外接矩形框的实例代码,代码简单易懂,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-09-04
- 图片剪裁是常用的方法,那么如何通过4坐标剪裁图片,本文就详细的来介绍一下,感兴趣的小伙伴们可以参考一下...2021-06-04
- 这篇文章主要介绍了OpenCV如何去除图片中的阴影的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-29
- 这篇文章主要介绍了解决使用OpenCV中的imread()内存报错问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-03-16
- 这篇文章主要介绍了python OpenCV的相关资料,帮助大家更好的理解和学习使用python的opencv,感兴趣的朋友可以了解下...2021-03-31
- 这篇文章主要介绍了使用OpenCV去除面积较小的连通域,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-07-05
详解pycharm的python包opencv(cv2)无代码提示问题的解决
这篇文章主要介绍了详解pycharm的python包opencv(cv2)无代码提示问题的解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-01-29在C#中使用OpenCV(使用OpenCVSharp)的实现
这篇文章主要介绍了在C#中使用OpenCV(使用OpenCVSharp)的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-11-15- 这篇文章主要为大家详细介绍了C++ opencv实现车道线识别,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-02-20
详解opencv中画圆circle函数和椭圆ellipse函数
这篇文章主要介绍了opencv中画圆circle函数和椭圆ellipse函数,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下...2020-05-09- 凸包是一个计算几何中的概念,在图像处理过程中,我们常常需要寻找图像中包围某个物体的凸包,本文就使用OpenCV实现,感兴趣的可以了解一下...2021-06-08
python中的opencv和PIL(pillow)转化操作
这篇文章主要介绍了python中的opencv和PIL(pillow)转化操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-03-16- 这篇文章主要为大家详细介绍了Opencv LBPH人脸识别算法的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-04-25
- 这篇文章主要介绍了python基于opencv检测程序运行效率,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-05-09
- 这篇文章主要介绍了python基于OpenCV模板匹配识别图片中的数字,帮助大家更好的理解和学习使用python,感兴趣的朋友可以了解下...2021-03-31
浅谈OpenCV中的新函数connectedComponentsWithStats用法
这篇文章主要介绍了浅谈OpenCV中的新函数connectedComponentsWithStats用法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-07-05- 这篇文章主要为大家详细介绍了Opencv实现绿幕视频背景替换功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-04-25
- 这篇文章主要介绍了Opencv图像处理之详解掩膜mask,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-21
- 这篇文章主要介绍了使用opencv识别图像红色区域,并输出红色区域中心点坐标,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-06-03
- 今天小编就为大家分享一篇python-OpenCV 实现将数组转换成灰度图和彩图,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-04-27