千家信息网

OpenCV是怎么实现简单套索工具

发表于:2024-10-01 作者:千家信息网编辑
千家信息网最后更新 2024年10月01日,这篇文章将为大家详细讲解有关OpenCV是怎么实现简单套索工具,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。Photoshop中的套索工具通过鼠标多次
千家信息网最后更新 2024年10月01日OpenCV是怎么实现简单套索工具

这篇文章将为大家详细讲解有关OpenCV是怎么实现简单套索工具,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。

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

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

首先定义下列全局变量

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

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(i - y, j - x) > 0)            {                destImg.at(i, j)[0] = srcImg.at(i - y, j - x)[0];                destImg.at(i, j)[1] = srcImg.at(i - y, j - x)[1];                destImg.at(i, j)[2] = srcImg.at(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> 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#include"opencv2/opencv.hpp"#includeconst char* winName = "TaoSuoTool";cv::Point maskLocation;cv::Mat resultImg;cv::Mat foregroundImg;cv::Mat areaMask;std::vector drawingPoints;std::vector 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(i - y, j - x) > 0)            {                destImg.at(i, j)[0] = srcImg.at(i - y, j - x)[0];                destImg.at(i, j)[1] = srcImg.at(i - y, j - x)[1];                destImg.at(i, j)[2] = srcImg.at(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> 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> 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是怎么实现简单套索工具就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

0