经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C++ » 查看文章
OpenCV实现简单套索工具
来源:jb51  时间:2022/1/19 13:44:41  对本文有异议

Photoshop中的套索工具通过鼠标多次点击可以选中一个任意多边形的区域,然后单独对这块区域进行编辑,下面就使用OpenCV实现一个简单的功能,模拟Photoshop中的套索工具。

这里的套索工具通过鼠标左键在图片上多次点击创建任意多个点,右键点击后将这些点连成封闭的多边形,形成一块待编辑的区域,键盘方向键控制该区域的移动,从而将该区域内的图像复制到原图像的其他地方。

首先定义下列全局变量

  1. const char* winName = "TaoSuoTool";//窗口名称
  2. cv::Mat resultImg;//最终在OpenCV窗口上显示的图像
  3. cv::Mat foregroundImg;//编辑前的图像
  4. cv::Mat areaMask;//蒙版,多边形区域实际绘制在该蒙版上
  5. cv::Point maskLocation;//蒙版位置,通过方向键移动后蒙版位置随之变化
  6. std::vector<cv::Point> drawingPoints;//区域完成前正在点击的所有点
  7. std::vector<cv::Point> areaPoints;//区域完成后其多边形顶点

main函数

  1. int main(int argc, char **arv)
  2. {
  3. ? ? foregroundImg = cv::imread("test.jpg");
  4. ? ? foregroundImg.copyTo(resultImg);
  5. ? ? areaMask = cv::Mat::zeros(foregroundImg.size(), CV_8U);
  6. ? ? cv::imshow(winName, resultImg);
  7. ?
  8. ? ? maskLocation.x = maskLocation.y = 0;
  9. ? ? cv::setMouseCallback(winName, OnMouseEvent);
  10. ? ? int key = cv::waitKeyEx(0);
  11. ? ? while (key != VK_ESCAPE)
  12. ? ? {
  13. ? ? ? ? key = cv::waitKeyEx(0);
  14. ? ? }
  15. ? ? return 0;
  16. }

在鼠标回调函数OnMouseEvent中处理三个消息:鼠标左键按下,鼠标右键按下和鼠标移动

  1. void OnMouseEvent(int event, int x, int y, int flags, void* userdata)
  2. {
  3. ? ? if (event == cv::EVENT_LBUTTONDOWN)
  4. ? ? {
  5. ? ? ? ? OnLeftMouseButtonDown(x,y);
  6. ? ? }
  7. ? ? else if (event == cv::EVENT_RBUTTONDOWN)
  8. ? ? {
  9. ? ? ? ? OnRightMouseButtonDown(x,y);
  10. ? ? }
  11. ? ? if (event == cv::EVENT_MOUSEMOVE)
  12. ? ? {
  13. ? ? ? ? OnMouseMove(x,y);
  14. ? ? }
  15. }

在编写鼠标事件前先定义一个函数

  1. void OnCompleteArea(bool bDrawOutline);

它表示完成当前区域的编辑,包括右键点击完成封闭多边形、移动区域以及合成最终图片。参数bDrawOutline表示绘制区域多边形的外轮廓,右键点击完成封闭多边形和移动区域过程中都要显示轮廓(bDrawOutline=true),合成最终图片后就不需要显示轮廓了(bDrawOutline=false)。
鼠标左键按下事件:先判断是否有前一个区域存在,存在则先完成前一个区域并且不显示区域轮廓,然后开始绘制新的区域多边形的点,点与点之间用蓝色线连接,点位置处绘制一个4X4的红色矩形。

  1. void OnLeftMouseButtonDown(int x,int y)
  2. {
  3. ? ? if (drawingPoints.empty() && areaPoints.size() > 0)
  4. ? ? {
  5. ? ? ? ? OnCompleteArea(false);
  6. ? ? }
  7. ? ? drawingPoints.push_back(cv::Point(x, y));
  8. ? ? cv::rectangle(resultImg, cv::Rect(x - 2, y - 2, 4, 4), CV_RGB(255, 0, 0), -1);
  9. ? ? if (drawingPoints.size() >= 2)
  10. ? ? {
  11. ? ? ? ? cv::line(resultImg, drawingPoints[drawingPoints.size() - 2], cv::Point(x, y), CV_RGB(0, 0, 255), 1);
  12. ? ? }
  13. ? ? cv::imshow(winName, resultImg);
  14. }

鼠标移动事件:判断drawingPoints是否为空,如果已经存在点则绘制线和点,并且还要绘制一根连接到鼠标当前位置的线。

  1. void OnMouseMove(int x,int y)
  2. {
  3. ? ? if (drawingPoints.size() > 0)
  4. ? ? {
  5. ? ? ? ? foregroundImg.copyTo(resultImg);
  6. ? ? ? ? for (int i = 0; i < drawingPoints.size() - 1; i++)
  7. ? ? ? ? {
  8. ? ? ? ? ? ? cv::rectangle(resultImg, cv::Rect(drawingPoints[i].x - 2, drawingPoints[i].y - 2, 4, 4), CV_RGB(255, 0, 0), -1);
  9. ? ? ? ? ? ? cv::line(resultImg, drawingPoints[i], drawingPoints[i + 1], CV_RGB(0, 0, 255), 1);
  10. ? ? ? ? }
  11. ? ? ? ? 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);
  12. ? ? ? ? cv::line(resultImg, drawingPoints[drawingPoints.size() - 1], cv::Point(x, y), CV_RGB(0, 0, 255), 1);
  13. ? ? ? ? cv::imshow(winName, resultImg);
  14. ? ? }
  15. }

鼠标右键按下事件:如果点个数少于2,不能形成有效区域则不做处理(不考虑多个点共线),否则就在蒙版Area上绘制一个多边形区域,然后调用OnCompleteArea完成区域编辑,这里需要画多边形轮廓,参数传入true。

  1. void OnRightMouseButtonDown(int x,int y)
  2. {
  3. ? ? if (drawingPoints.size() >= 3)
  4. ? ? {
  5. ? ? ? ? areaPoints = drawingPoints;
  6. ? ? ? ? std::vector<std::vector<cv::Point>> polys;
  7. ? ? ? ? polys.push_back(areaPoints);
  8. ? ? ? ? cv::fillPoly(areaMask, polys, cv::Scalar::all(255));
  9. ? ? ? ? OnCompleteArea(true);
  10. ? ? }
  11. ? ? else
  12. ? ? {
  13. ? ? ? ? foregroundImg.copyTo(resultImg);
  14. ? ? }
  15. ? ? drawingPoints.clear();
  16. ? ? cv::imshow(winName, resultImg);
  17. }

下面是OnCompleteArea函数的实现,其中MergeImages函数通过蒙版以及蒙版的位置合成最终的图像,蒙版中区域内的像素值大于0,其他像素值都为0,默认图像是三通道(destImg.at<cv::Vec3b>)

  1. void MergeImages(cv::Mat& destImg, const cv::Mat& srcImg, const cv::Mat& maskImg, int x, int y)
  2. {
  3. ? ? int top = y > 0 ? y : 0;
  4. ? ? int left = x > 0 ? x : 0;
  5. ? ? int right = destImg.cols > x + srcImg.cols ? x + srcImg.cols : destImg.cols;
  6. ? ? int bottom = destImg.rows > y + srcImg.rows ? y + srcImg.rows : destImg.rows;
  7. ? ? for (int i = top; i < bottom; i++)
  8. ? ? {
  9. ? ? ? ? for (int j = left; j < right; j++)
  10. ? ? ? ? {
  11. ? ? ? ? ? ? int destIndex = i * destImg.cols + j;
  12. ? ? ? ? ? ? int srcIndex = (i - top)*srcImg.cols + j - left;
  13. ? ? ? ? ? ? int channel = destImg.channels();
  14. ? ? ? ? ? ? if (maskImg.at<uchar>(i - y, j - x) > 0)
  15. ? ? ? ? ? ? {
  16. ? ? ? ? ? ? ? ? destImg.at<cv::Vec3b>(i, j)[0] = srcImg.at<cv::Vec3b>(i - y, j - x)[0];
  17. ? ? ? ? ? ? ? ? destImg.at<cv::Vec3b>(i, j)[1] = srcImg.at<cv::Vec3b>(i - y, j - x)[1];
  18. ? ? ? ? ? ? ? ? destImg.at<cv::Vec3b>(i, j)[2] = srcImg.at<cv::Vec3b>(i - y, j - x)[2];
  19. ? ? ? ? ? ? }
  20. ? ? ? ? }
  21. ? ? }
  22. }
  23. void OnCompleteArea(bool bDrawOutline)
  24. {
  25. ? ? foregroundImg.copyTo(resultImg);
  26. ? ? MergeImages(resultImg, foregroundImg, areaMask, maskLocation.x, maskLocation.y);
  27. ? ? if (bDrawOutline)
  28. ? ? {
  29. ? ? ? ? if (areaPoints.size() >= 3)
  30. ? ? ? ? {
  31. ? ? ? ? ? ? std::vector<std::vector<cv::Point>> polys;
  32. ? ? ? ? ? ? polys.push_back(areaPoints);
  33. ? ? ? ? ? ? cv::polylines(resultImg, polys, true, cv::Scalar::all(255));
  34. ? ? ? ? }
  35. ? ? }
  36. ? ? else
  37. ? ? {
  38. ? ? ? ? resultImg.copyTo(foregroundImg);
  39. ? ? ? ? areaPoints.clear();
  40. ? ? ? ? memset(areaMask.data, 0, areaMask.rows*areaMask.cols*areaMask.elemSize());
  41. ? ? ? ? maskLocation.x = maskLocation.y = 0;
  42. ? ? }
  43. }

绘制区域之后就可以通过方向按键控制区域图像的移动了(也可以实现为鼠标左键按下拖动来移动区域),移动主要是更新maskLocation和areaPoints的坐标值,然后调用OnCompleteArea(true),依然显示区域的轮廓。

  1. void OnDirectionKeyDown(short keyCode)
  2. {
  3. ? ? int x = keyCode == VK_LEFT ? -2 : (keyCode == VK_RIGHT ? 2 : 0);
  4. ? ? int y = keyCode == VK_UP ? -2 : (keyCode == VK_DOWN ? 2 : 0);
  5. ? ? maskLocation.x += x;
  6. ? ? maskLocation.y += y;
  7. ? ? for (int i = 0; i < areaPoints.size(); i++)
  8. ? ? {
  9. ? ? ? ? areaPoints[i].x += x;
  10. ? ? ? ? areaPoints[i].y += y;
  11. ? ? }
  12. ? ? OnCompleteArea(true);
  13. ? ? cv::imshow(winName, resultImg);
  14. }

将上面函数在主函数的按键循环中调用,方向按键通过key的高16位判断,在Windows下可以使用虚拟键码宏表示。 同时为了能看到最终合成的图片加入Enter按键消息处理,将图像合成并去掉轮廓。

  1. int key = cv::waitKeyEx(0);
  2. short lowKey = key;
  3. short highKey = key >> 16;
  4. while (key != VK_ESCAPE)
  5. {
  6. ? ? if (key == VK_RETURN)//Enter
  7. ? ? {
  8. ? ? ? ? OnCompleteArea(false);
  9. ? ? ? ? cv::imshow(winName, resultImg);
  10. ? ? }
  11. ? ? else if (lowKey == 0 && (highKey == VK_UP || highKey == VK_DOWN || highKey == VK_LEFT || highKey == VK_RIGHT))
  12. ? ? {
  13. ? ? ? ? OnDirectionKeyDown(highKey);
  14. ? ? }
  15. ? ? key = cv::waitKeyEx(0);
  16. ? ? lowKey = key;
  17. ? ? highKey = key >> 16;
  18. }

这样一个简单的套索工具功能就做好了(上面的代码都是简化处理,还有很多可以优化的地方,从而使编辑更加流畅)

完整代码

  1. #include<iostream>
  2. #include"opencv2/opencv.hpp"
  3. #include<windows.h>
  4. const char* winName = "TaoSuoTool";
  5. cv::Point maskLocation;
  6. cv::Mat resultImg;
  7. cv::Mat foregroundImg;
  8. cv::Mat areaMask;
  9. std::vector<cv::Point> drawingPoints;
  10. std::vector<cv::Point> areaPoints;
  11. void MergeImages(cv::Mat& destImg, const cv::Mat& srcImg, const cv::Mat& maskImg, int x, int y)
  12. {
  13. ? ? int top = y > 0 ? y : 0;
  14. ? ? int left = x > 0 ? x : 0;
  15. ? ? int right = destImg.cols > x + srcImg.cols ? x + srcImg.cols : destImg.cols;
  16. ? ? int bottom = destImg.rows > y + srcImg.rows ? y + srcImg.rows : destImg.rows;
  17. ? ? for (int i = top; i < bottom; i++)
  18. ? ? {
  19. ? ? ? ? for (int j = left; j < right; j++)
  20. ? ? ? ? {
  21. ? ? ? ? ? ? int destIndex = i * destImg.cols + j;
  22. ? ? ? ? ? ? int srcIndex = (i - top)*srcImg.cols + j - left;
  23. ? ? ? ? ? ? int channel = destImg.channels();
  24. ? ? ? ? ? ? if (maskImg.at<uchar>(i - y, j - x) > 0)
  25. ? ? ? ? ? ? {
  26. ? ? ? ? ? ? ? ? destImg.at<cv::Vec3b>(i, j)[0] = srcImg.at<cv::Vec3b>(i - y, j - x)[0];
  27. ? ? ? ? ? ? ? ? destImg.at<cv::Vec3b>(i, j)[1] = srcImg.at<cv::Vec3b>(i - y, j - x)[1];
  28. ? ? ? ? ? ? ? ? destImg.at<cv::Vec3b>(i, j)[2] = srcImg.at<cv::Vec3b>(i - y, j - x)[2];
  29. ? ? ? ? ? ? }
  30. ? ? ? ? }
  31. ? ? }
  32. }
  33. ?
  34. void OnCompleteArea(bool bDrawOutline)
  35. {
  36. ? ? foregroundImg.copyTo(resultImg);
  37. ? ? MergeImages(resultImg, foregroundImg, areaMask, maskLocation.x, maskLocation.y);
  38. ? ? if (bDrawOutline)
  39. ? ? {
  40. ? ? ? ? if (areaPoints.size() >= 3)
  41. ? ? ? ? {
  42. ? ? ? ? ? ? std::vector<std::vector<cv::Point>> polys;
  43. ? ? ? ? ? ? polys.push_back(areaPoints);
  44. ? ? ? ? ? ? cv::polylines(resultImg, polys, true, cv::Scalar::all(255));
  45. ? ? ? ? }
  46. ? ? }
  47. ? ? else
  48. ? ? {
  49. ? ? ? ? resultImg.copyTo(foregroundImg);
  50. ? ? ? ? areaPoints.clear();
  51. ? ? ? ? memset(areaMask.data, 0, areaMask.rows*areaMask.cols*areaMask.elemSize());
  52. ? ? ? ? maskLocation.x = maskLocation.y = 0;
  53. ? ? }
  54. }
  55. void OnLeftMouseButtonDown(int x,int y)
  56. {
  57. ? ? if (drawingPoints.empty() && areaPoints.size() > 0)
  58. ? ? {
  59. ? ? ? ? OnCompleteArea(false);
  60. ? ? }
  61. ? ? drawingPoints.push_back(cv::Point(x, y));
  62. ? ? cv::rectangle(resultImg, cv::Rect(x - 2, y - 2, 4, 4), CV_RGB(255, 0, 0), -1);
  63. ? ? if (drawingPoints.size() >= 2)
  64. ? ? {
  65. ? ? ? ? cv::line(resultImg, drawingPoints[drawingPoints.size() - 2], cv::Point(x, y), CV_RGB(0, 0, 255), 1);
  66. ? ? }
  67. ? ? cv::imshow(winName, resultImg);
  68. }
  69. void OnRightMouseButtonDown(int x,int y)
  70. {
  71. ? ? if (drawingPoints.size() >= 3)
  72. ? ? {
  73. ? ? ? ? areaPoints = drawingPoints;
  74. ? ? ? ? std::vector<std::vector<cv::Point>> polys;
  75. ? ? ? ? polys.push_back(areaPoints);
  76. ? ? ? ? cv::fillPoly(areaMask, polys, cv::Scalar::all(255));
  77. ? ? ? ? OnCompleteArea(true);
  78. ? ? }
  79. ? ? else
  80. ? ? {
  81. ? ? ? ? foregroundImg.copyTo(resultImg);
  82. ? ? }
  83. ? ? drawingPoints.clear();
  84. ? ? cv::imshow(winName, resultImg);
  85. }
  86. void OnMouseMove(int x,int y)
  87. {
  88. ? ? if (drawingPoints.size() > 0)
  89. ? ? {
  90. ? ? ? ? foregroundImg.copyTo(resultImg);
  91. ? ? ? ? for (int i = 0; i < drawingPoints.size() - 1; i++)
  92. ? ? ? ? {
  93. ? ? ? ? ? ? cv::rectangle(resultImg, cv::Rect(drawingPoints[i].x - 2, drawingPoints[i].y - 2, 4, 4), CV_RGB(255, 0, 0), -1);
  94. ? ? ? ? ? ? cv::line(resultImg, drawingPoints[i], drawingPoints[i + 1], CV_RGB(0, 0, 255), 1);
  95. ? ? ? ? }
  96. ? ? ? ? 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);
  97. ? ? ? ? cv::line(resultImg, drawingPoints[drawingPoints.size() - 1], cv::Point(x, y), CV_RGB(0, 0, 255), 1);
  98. ? ? ? ? cv::imshow(winName, resultImg);
  99. ? ? }
  100. }
  101. void OnMouseEvent(int event, int x, int y, int flags, void* userdata)
  102. {
  103. ? ? if (event == cv::EVENT_LBUTTONDOWN)
  104. ? ? {
  105. ? ? ? ? OnLeftMouseButtonDown(x,y);
  106. ? ? }
  107. ? ? else if (event == cv::EVENT_RBUTTONDOWN)
  108. ? ? {
  109. ? ? ? ? OnRightMouseButtonDown(x,y);
  110. ? ? }
  111. ? ? if (event == cv::EVENT_MOUSEMOVE)
  112. ? ? {
  113. ? ? ? ? OnMouseMove(x,y);
  114. ? ? }
  115. }
  116. ?
  117. void OnDirectionKeyDown(short keyCode)
  118. {
  119. ? ? int x = keyCode == VK_LEFT ? -2 : (keyCode == VK_RIGHT ? 2 : 0);
  120. ? ? int y = keyCode == VK_UP ? -2 : (keyCode == VK_DOWN ? 2 : 0);
  121. ? ? maskLocation.x += x;
  122. ? ? maskLocation.y += y;
  123. ? ? for (int i = 0; i < areaPoints.size(); i++)
  124. ? ? {
  125. ? ? ? ? areaPoints[i].x += x;
  126. ? ? ? ? areaPoints[i].y += y;
  127. ? ? }
  128. ? ? OnCompleteArea(true);
  129. ? ? cv::imshow(winName, resultImg);
  130. }
  131. int main(int argc, char **arv)
  132. {
  133. ? ? foregroundImg = cv::imread("test.jpg");
  134. ? ? foregroundImg.copyTo(resultImg);
  135. ? ? areaMask = cv::Mat::zeros(foregroundImg.size(), CV_8U);
  136. ? ? cv::imshow(winName, resultImg);
  137. ?
  138. ? ? maskLocation.x = maskLocation.y = 0;
  139. ? ? cv::setMouseCallback(winName, OnMouseEvent);
  140. ? ? int key = cv::waitKeyEx(0);
  141. ? ? short lowKey = key;
  142. ? ? short highKey = key >> 16;
  143. ? ? while (key != VK_ESCAPE)
  144. ? ? {
  145. ? ? ? ? if (key == VK_RETURN)//Enter
  146. ? ? ? ? {
  147. ? ? ? ? ? ? OnCompleteArea(false);
  148. ? ? ? ? ? ? cv::imshow(winName, resultImg);
  149. ? ? ? ? }
  150. ? ? ? ? else if (lowKey == 0 && (highKey == VK_UP || highKey == VK_DOWN || highKey == VK_LEFT || highKey == VK_RIGHT))
  151. ? ? ? ? {
  152. ? ? ? ? ? ? OnDirectionKeyDown(highKey);
  153. ? ? ? ? }
  154. ? ? ? ? key = cv::waitKeyEx(0);
  155. ? ? ? ? lowKey = key;
  156. ? ? ? ? highKey = key >> 16;
  157. ? ? }
  158. ? ? return 0;
  159. }

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持w3xue。

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号