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;
}

OpenCV实现简单的套索工具相关推荐

  1. 使用Python,OpenCV实现简单的场景边界/拍摄转换检测器

    使用Python,OpenCV进行简单的场景边界/拍摄转换检测器 1. 效果图 2. 实现 2.1 步骤 2.2 什么是"场景边界"和"拍摄过渡"? 2.3 代 ...

  2. OpenCV GPU 简单遍历图像

    OpenCV GPU  简单遍历图像 [cpp] view plaincopy print? #include "cuda_runtime.h" #include "de ...

  3. OpenCV实现简单人脸检测

    用OpenCV实现简单的人脸识别,基于静态图像. 1.材料准备:找到OpenCV提供的人脸训练模型文件.可以通过 https://github.com/opencv/opencv/tree/maste ...

  4. win7·64bit+VS2013+opencv的简单配置

    win7·64bit+VS2013+opencv的简单配置 win7x64 vs2013 opencv 如果你不需要: 在调试的时候看OpenCV的源代码: 添加新的功能支持,比如GPU. OpenC ...

  5. OpenCV 编程简单介绍(矩阵/图像/视频的基本读写操作)

    PS. 因为csdn博客文章长度有限制,本文有部分内容被截掉了. 在OpenCV中文站点的wiki上有可读性更好.而且是完整的版本号,欢迎浏览. OpenCV Wiki :<OpenCV 编程简 ...

  6. python人像和图片比对_python 使用OpenCV进行简单的人像分割与合成

    实现思路 通过背景建模的方法,对源图像中的动态人物前景进行分割,再将目标图像作为背景,进行合成操作,获得一个可用的合成影像. 实现步骤如下. 使用BackgroundSubtractorMOG2进行背 ...

  7. python opencv 绘制简单图形

    09-python opencv 绘制简单图形 09-python opencv 绘制简单图形 概述 实现过程 引用与创建空图 绘制直线 绘制矩形 绘制圆 绘制椭圆 添加文字 显示图像 源代码 运行结 ...

  8. 基于OpenCV实现简单人脸面具、眼镜、胡须、鼻子特效(详细步骤 + 源码)

    点击下方卡片,关注"OpenCV与AI深度学习"公众号! 视觉/图像重磅干货,第一时间送达! 导读 本文给大家分享一个基于OpenCV实现简单人脸面具.眼镜.胡须.鼻子特效的实例, ...

  9. 利用Matlab进行相机标定并使用openCV进行简单三维重建

    注:本文主要针对Matlab和OpenCV跨平台进行相机标定.单相机三维重建工作的实现,因为我发现网上竟然没有一篇博客径直指出这两者在进行图像处理时的巨大差异(坐标系完全不同),不然我也不会走了很多弯 ...

最新文章

  1. 中国首个量子计算机诞生 中科院、阿里巴巴共同研发
  2. Django - - 进阶 - - 同源策略和跨域解决方案
  3. 【awk】用awk将Fasta文件序列变成一行
  4. java 01入门 取数字_jmu-Java-01入门-取数字
  5. 【C语言简单说】十三:变量的生命周期
  6. 土城战役_避免使用FOR –反假战役
  7. 经济学与计算机学收入,考研心得,计算机专业跨考经济学复习经验谈
  8. 将本地项目上传到gitLab操作
  9. SNMP报文抓取与分析(一)
  10. Atitit etl之道 attilax著 1. ETL 1 1.1. (数据仓库技术) 2 1.2. ETL的质量问题具体表现为正确性、完整性、一致性、完备性、有效性、时效性和可获取性等几个特性
  11. 今天和孝辉一起去交大买书了!
  12. html用css美化表格
  13. 互联网人的Q2,被业务目标支配
  14. 隋政军---将木屋烧烤打造成中国领先的烧烤品牌
  15. 9.3. debug ip igrp
  16. 5.frp对外提供简单的文件访问服务
  17. unittest使用详解
  18. 福建省区块链应用商会成立大会 发起单位零伽壹现场做主题分享
  19. 团队开发背景及团队分工
  20. #脉冲压缩仿真及源码

热门文章

  1. 一个简单的吃鸡背包编写——1
  2. 刚刚,2018年度中国科学十大进展正式发布! 1
  3. Shell 异常处理
  4. ECCV2022 商汤 发布最大的表征学习预训练数据集OmniBenchmark解读
  5. 2011年.NET面试题总结(很经典的)
  6. FFmpeg多媒体格式分类详解
  7. Andorid-15k+的面试题
  8. xcode7中,无需证书即可进行真机编译调试!
  9. Windows MSDT RCE(CVE-2022-30190)复现
  10. 避障模块的使用——避免小车撞到障碍物