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

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

(0)

相关推荐

  • Opencv使用鼠标任意形状的抠图

    本文实例为大家分享了Opencv使用鼠标任意形状抠图的具体代码,供大家参考,具体内容如下 主要的方法思路是:首先利用鼠标在图上画任意形状,利用掩码将任意形状抠出来 主要难点是怎么填充,因为鼠标在画线的时候,滑动越快,点是不连续的,利用floodFill和drawContours都是没有办法进行填充的,从另一个方面想,一个面是由很多个点组成的,虽然鼠标滑动保存下来的就是一系列点,可以利用这一系列点构成一个面,利用面的性质进行填充就比较简单了. 一.首先使用鼠标点击事件,鼠标点击事件的函数为: vo

  • OpenCV实现抠图工具

    本文实例为大家分享了OpenCV实现抠图工具的具体代码,供大家参考,具体内容如下 在计算机图像领域,我们经常需要做一些抠图的工作,将图像中的目标感兴趣区域提取出来,剔除其他冗余的背景元素,以实现计算机视觉的各项功能(如车辆检测.人脸检测等).如果纯粹使用美图秀秀等工具类软件的话,由于工具类软件将图像处理中各种可能用到的功能都集成在了一起,所以纯粹做抠图的话效率很低.现在我们就用 OpenCV 来实现一段简易的抠图程序,只需要在画面上选定目标的感兴趣区域,该目标就会被自动按序号保存. 代码如下,同

  • OpenCV实现简单套索工具

    Photoshop中的套索工具通过鼠标多次点击可以选中一个任意多边形的区域,然后单独对这块区域进行编辑,下面就使用OpenCV实现一个简单的功能,模拟Photoshop中的套索工具. 这里的套索工具通过鼠标左键在图片上多次点击创建任意多个点,右键点击后将这些点连成封闭的多边形,形成一块待编辑的区域,键盘方向键控制该区域的移动,从而将该区域内的图像复制到原图像的其他地方. 首先定义下列全局变量 const char* winName = "TaoSuoTool";//窗口名称 cv::M

  • Python基于opencv的简单图像轮廓形状识别(全网最简单最少代码)

    可以直接跳到最后整体代码看一看是不是很少的代码!!!! 思路: 1. 数据的整合 2. 图片的灰度转化 3. 图片的二值转化 4. 图片的轮廓识别 5. 得到图片的顶点数 6. 依据顶点数判断图像形状 一.原数据的展示 图片文件共36个文件夹,每个文件夹有100张图片,共3600张图片. 每一个文件夹里都有形同此类的图形 二.数据的整合 对于多个文件夹,分析起来很不方便,所有决定将其都放在一个文件夹下进行分析,在python中具体实现如下: 本次需要的包 import cv2 import os

  • python 使用OpenCV进行简单的人像分割与合成

    实现思路 通过背景建模的方法,对源图像中的动态人物前景进行分割,再将目标图像作为背景,进行合成操作,获得一个可用的合成影像. 实现步骤如下. 使用BackgroundSubtractorMOG2进行背景分割 BackgroundSubtractorMOG2是一个以高斯混合模型为基础的背景前景分割算法, 混合高斯模型 分布概率是K个高斯分布的和,每个高斯分布有属于自己的 μμ 和 σσ 参数,以及对应的权重参数,权重值必须为正数,所有权重的和必须等于1,以确保公式给出数值是合理的概率密度值.换句话

  • Python中OpenCV实现简单车牌字符切割

    在Jupyter Notebook上使用Python+opencv实现如下简单车牌字符切割.关于opencv库的安装可以参考:Python下opencv库的安装过程与一些问题汇总. 1.实现代码 import cv2 import numpy as np import matplotlib.pyplot as plt from PIL import Image #读取原图片 image1=cv2.imread("123456.jpg") cv2.imshow("image1&

  • 如何基于opencv实现简单的数字识别

    目录 前言 要解决的问题 解决问题的思路 总结 前言 由于自己学识尚浅,不能用python深度学习来识别这里的数字,所以就完全采用opencv来识别数字,然后在这里分享.记录一下自己在学习过程中的一些所见所得和所想 要解决的问题 这是一个要识别的数字,我这里首先是对图像进行一个ROI的提取,提取结果就仅仅剩下数字,把其他的一些无关紧要的要素排除在外, 这是ROI图片,我们要做的就是识别出该照片中的数字, 解决问题的思路 1.先把这个图片中的数字分割,分割成为5张小图片,每张图片包含一个数字,为啥

  • Python+OpenCV 实现简单的高斯滤波(推荐)

    基本原理讲解:高斯模糊的算法 高斯核函数的编写:构建权重矩阵,采用高斯二维分布函数的形式进行处理.需要注意的是,这里我没有特判当sigma = 0的时候的情况. 即是实现: 1)权重矩阵的构建 根据公式: 计算矩阵内部结构,其中因为要进行归一化处理,e前方的系数会被约去,因此代码中不体现. 2)矩阵元素归一化处理 计算矩阵内部元素总和sum,最后做矩阵除法得到归一化处理后的权重矩阵. # 高斯核生成函数 kernel_size:滤波核大小 sigma:高斯核函数的局部影响范围 def gauss

  • Python3+OpenCV实现简单交通标志识别流程分析

    由于该项目是针对中小学生竞赛并且是第一次举行,所以识别的目标交通标志仅仅只有直行.右转.左转和停车让行. 数据集: 链接: https://pan.baidu.com/s/1SL0qE-qd4cuatmfZeNuK0Q 提取码: vuvi  源代码:https://github.com/ccxiao5/Traffic_sign_recognition 整体流程如下: 数据集收集(包括训练集和测试集的分类) 图像预处理 图像标注 根据标注分割得到目标图像 HOG特征提取 训练得到模型 将模型带入识

  • 详解OpenCV中简单的鼠标事件处理

    目录 cv2.setMouseCallback函数语法 回调函数 谈及鼠标事件,就是在触发鼠标按钮后程序所做出相应的反应,但是不影响程序的整个线程.这有些像异步处理.鼠标事件响应不会一直等着我们去按而后续程序不执行,这样会造成阻塞,而是在我们不按鼠标的时候程序也会正常进行,按的时候会调用鼠标的事件响应,这个过程就像程序一边正常运行一边等待鼠标响应. 为了将鼠标响应和操作画面进行绑定,我们要创建一个回调函数: cv2.setMouseCallback函数语法 cv2.setMouseCallbac

  • OpenCV实现简单录屏功能

    本文实例为大家分享了OpenCV实现简单录屏功能的具体代码,供大家参考,具体内容如下 OpenCV中VideoCapture和VideoWriter用于读写视频文件,这里的录屏功能用到VideoWriter,用于将捕获的屏幕的每一帧数据保存到视频文件. VideoWriter写视频文件的步骤 1.bool open(const String& filename, int fourcc, double fps,Size frameSize, bool isColor = true);2.void

  • opencv实现简单人脸识别

    对于opencv 它提供了许多已经练习好的模型可供使用,我们需要通过他们来进行人脸识别 参考了网上许多资料 假设你已经配好了开发环境 ,在我之前的博客中由开发环境的配置. 项目代码结构: dataSet : 存储训练用的图片,他由data_gen生成,当然也可以修改代码由其他方式生成 haarcascade_frontalface_alt.xml  . haarcascade_frontalface_default.xml: 用于人脸检测的haar分类器,网上普遍说第一个效果更好,第二个运行速度

随机推荐