日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學(xué)無(wú)先后,達(dá)者為師

網(wǎng)站首頁(yè) 編程語(yǔ)言 正文

OpenCV實(shí)現(xiàn)簡(jiǎn)單套索工具_(dá)C 語(yǔ)言

作者:蓬?蒿?人 ? 更新時(shí)間: 2022-04-01 編程語(yǔ)言

Photoshop中的套索工具通過(guò)鼠標(biāo)多次點(diǎn)擊可以選中一個(gè)任意多邊形的區(qū)域,然后單獨(dú)對(duì)這塊區(qū)域進(jìn)行編輯,下面就使用OpenCV實(shí)現(xiàn)一個(gè)簡(jiǎn)單的功能,模擬Photoshop中的套索工具。

這里的套索工具通過(guò)鼠標(biāo)左鍵在圖片上多次點(diǎn)擊創(chuàng)建任意多個(gè)點(diǎn),右鍵點(diǎn)擊后將這些點(diǎn)連成封閉的多邊形,形成一塊待編輯的區(qū)域,鍵盤方向鍵控制該區(qū)域的移動(dòng),從而將該區(qū)域內(nèi)的圖像復(fù)制到原圖像的其他地方。

首先定義下列全局變量

const char* winName = "TaoSuoTool";//窗口名稱
cv::Mat resultImg;//最終在OpenCV窗口上顯示的圖像
cv::Mat foregroundImg;//編輯前的圖像
cv::Mat areaMask;//蒙版,多邊形區(qū)域?qū)嶋H繪制在該蒙版上
cv::Point maskLocation;//蒙版位置,通過(guò)方向鍵移動(dòng)后蒙版位置隨之變化
std::vector<cv::Point> drawingPoints;//區(qū)域完成前正在點(diǎn)擊的所有點(diǎn)
std::vector<cv::Point> areaPoints;//區(qū)域完成后其多邊形頂點(diǎn)

main函數(shù)

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

在鼠標(biāo)回調(diào)函數(shù)OnMouseEvent中處理三個(gè)消息:鼠標(biāo)左鍵按下,鼠標(biāo)右鍵按下和鼠標(biāo)移動(dòng)

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

在編寫鼠標(biāo)事件前先定義一個(gè)函數(shù)

void OnCompleteArea(bool bDrawOutline);

它表示完成當(dāng)前區(qū)域的編輯,包括右鍵點(diǎn)擊完成封閉多邊形、移動(dòng)區(qū)域以及合成最終圖片。參數(shù)bDrawOutline表示繪制區(qū)域多邊形的外輪廓,右鍵點(diǎn)擊完成封閉多邊形和移動(dòng)區(qū)域過(guò)程中都要顯示輪廓(bDrawOutline=true),合成最終圖片后就不需要顯示輪廓了(bDrawOutline=false)。
鼠標(biāo)左鍵按下事件:先判斷是否有前一個(gè)區(qū)域存在,存在則先完成前一個(gè)區(qū)域并且不顯示區(qū)域輪廓,然后開(kāi)始繪制新的區(qū)域多邊形的點(diǎn),點(diǎn)與點(diǎn)之間用藍(lán)色線連接,點(diǎn)位置處繪制一個(gè)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);
}

鼠標(biāo)移動(dòng)事件:判斷drawingPoints是否為空,如果已經(jīng)存在點(diǎn)則繪制線和點(diǎn),并且還要繪制一根連接到鼠標(biāo)當(dāng)前位置的線。

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

鼠標(biāo)右鍵按下事件:如果點(diǎn)個(gè)數(shù)少于2,不能形成有效區(qū)域則不做處理(不考慮多個(gè)點(diǎn)共線),否則就在蒙版Area上繪制一個(gè)多邊形區(qū)域,然后調(diào)用OnCompleteArea完成區(qū)域編輯,這里需要畫(huà)多邊形輪廓,參數(shù)傳入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函數(shù)的實(shí)現(xiàn),其中MergeImages函數(shù)通過(guò)蒙版以及蒙版的位置合成最終的圖像,蒙版中區(qū)域內(nèi)的像素值大于0,其他像素值都為0,默認(rèn)圖像是三通道(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;
? ? }
}

繪制區(qū)域之后就可以通過(guò)方向按鍵控制區(qū)域圖像的移動(dòng)了(也可以實(shí)現(xiàn)為鼠標(biāo)左鍵按下拖動(dòng)來(lái)移動(dòng)區(qū)域),移動(dòng)主要是更新maskLocation和areaPoints的坐標(biāo)值,然后調(diào)用OnCompleteArea(true),依然顯示區(qū)域的輪廓。

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

將上面函數(shù)在主函數(shù)的按鍵循環(huán)中調(diào)用,方向按鍵通過(guò)key的高16位判斷,在Windows下可以使用虛擬鍵碼宏表示。 同時(shí)為了能看到最終合成的圖片加入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;
}

這樣一個(gè)簡(jiǎn)單的套索工具功能就做好了(上面的代碼都是簡(jiǎn)化處理,還有很多可以優(yōu)化的地方,從而使編輯更加流暢)

完整代碼

#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

欄目分類
最近更新