使用GrabCut算法进行交互式前景提取
当前系列所有demo下载地址:
https://github.com/GaoRenBao/OpenCv4-Demo
当前demo在文档中的位置:
OpenCV-Python-Tutorial-中文版.pdf(P174)
不同编程语言对应的OpenCv版本以及开发环境信息如下:
语言 | OpenCv版本 | IDE |
C# | OpenCvSharp4.4.8.0.20230708 | Visual Studio 2022 |
C++ | OpenCv-4.5.5-vc14_vc15 | Visual Studio 2022 |
Python | OpenCv-Python (4.6.0.66) | PyCharm Community Edition 2022.1.3 |
测试原图如下:
demo1:简单提取运行效果:
demo2:经过掩膜处理后的运行效果如下,三个语言的运行效果有点小差异,但总体上还是一样的,下面这个是python的运行效果。
官方提供的掩码,由于尺寸问题,运行会报错:
下面这个掩膜是我自己用PS制作的。勉强可以用,在制作掩膜时,我特地按照官方原图,画了灰色和白色两种线条,貌似白色线条覆盖的区域并不能被完全剔除,灰色的倒是可以。
C#版本代码如下:
using OpenCvSharp;
namespace demo
{
internal class Program
{
static void Main(string[] args)
{
demo1();
demo2();
Cv2.WaitKey(0);
}
public static void demo1()
{
Mat img = Cv2.ImRead("../../../images/messi5.jpg");
Mat mask = new Mat(); // 存储掩码图像
Mat bgdModel = new Mat(); // 存储背景模型
Mat fgdModel = new Mat(); // 存储前景模型
// 使用GrabCut算法进行图像分割,指定感兴趣区域的矩形框作为初始化
Cv2.GrabCut(img, mask, new Rect(50, 50, 450, 290), bgdModel, fgdModel, 5, GrabCutModes.InitWithRect);
// 创建结果图像
Mat mask2 = new Mat(mask.Rows, mask.Cols, MatType.CV_8UC1);
/*
python实现如下:
mask2 = np.where((mask == 2) | (mask == 0), 0, 1).astype('uint8')
该代码可以理解为:
将数组mask中等于2或等于0的元素替换为0,其他元素替换为1。
*/
for (int i = 0; i < mask.Width; i++)
{
for (int j = 0; j < mask.Height; j++)
{
// 获取掩码图像的像素值
byte v = mask.Get<byte>(j, i);
// 根据掩码像素值设置结果图像的像素值
switch (v)
{
case 0: // 0 表示背景
case 2: // 2 表示可能的背景
mask2.Set<byte>(j, i, 0); // 设置为黑色
break;
case 1: // 1 表示前景
case 3: // 3 表示可能的前景
// 设置为白
// 按照python的逻辑,如果设置成1。
// 掩码的使用就必须采用 Cv2.Multiply 方法才行。
// 如果设置成255,就的采用 Cv2.BitwiseAnd 方法。
mask2.Set<byte>(j, i, 1);
// 或者
//mask2.Set<byte>(j, i, 255);
break;
}
}
}
// 按Python逻辑,使用相乘算法
Cv2.CvtColor(mask2, mask2, ColorConversionCodes.GRAY2BGR);
Cv2.Multiply(img, mask2, img);
Cv2.ImShow("out", img);
// 如果前景设置的是255,则用这个
//Mat ret = new Mat();
//Cv2.BitwiseAnd(img, img, ret, mask2);
//Cv2.ImShow("out", ret);
}
public static void demo2()
{
Mat img = Cv2.ImRead("../../../images/messi5.jpg");
Mat mask = new Mat(); // 存储掩码图像
Mat bgdModel = new Mat(); // 存储背景模型
Mat fgdModel = new Mat(); // 存储前景模型
Cv2.GrabCut(img, mask, new Rect(50, 50, 450, 290), bgdModel, fgdModel, 5, GrabCutModes.InitWithRect);
Mat mask2 = new Mat(mask.Rows, mask.Cols, MatType.CV_8UC1);
for (int i = 0; i < mask.Width; i++)
{
for (int j = 0; j < mask.Height; j++)
{
// 获取掩码图像的像素值
byte v = mask.Get<byte>(j, i);
// 根据掩码像素值设置结果图像的像素值
switch (v)
{
case 0: // 0 表示背景
case 2: // 2 表示可能的背景
mask2.Set<byte>(j, i, 0); // 设置为黑色
break;
case 1: // 1 表示前景
case 3: // 3 表示可能的前景
mask2.Set<byte>(j, i, 1);
break;
}
}
}
Cv2.CvtColor(mask2, mask2, ColorConversionCodes.GRAY2BGR);
Cv2.Multiply(img, mask2, img);
Mat newmask = Cv2.ImRead("../../../images/newmask2.jpg", 0);
/*
python中下面的算法在C#中的实现没找到。。
mask[newmask == 0] = 0
mask[newmask == 255] = 1
*/
Mat rMask = new Mat();
Cv2.BitwiseAnd(mask, mask, rMask, newmask);
Cv2.GrabCut(img, rMask, new Rect(), bgdModel, fgdModel, 5, GrabCutModes.InitWithMask);
mask = new Mat(rMask.Rows, rMask.Cols, MatType.CV_8UC1);
for (int i = 0; i < rMask.Width; i++)
{
for (int j = 0; j < rMask.Height; j++)
{
// 获取掩码图像的像素值
byte v = rMask.Get<byte>(j, i);
// 根据掩码像素值设置结果图像的像素值
switch (v)
{
case 0: // 0 表示背景
case 2: // 2 表示可能的背景
mask.Set<byte>(j, i, 0); // 设置为黑色
break;
case 1: // 1 表示前景
case 3: // 3 表示可能的前景
mask.Set<byte>(j, i, 1);
break;
}
}
}
Cv2.CvtColor(mask, mask, ColorConversionCodes.GRAY2BGR);
Cv2.Multiply(img, mask, img);
Cv2.ImShow("result", img);
}
}
}
C++版本代码如下:
C++版本启动运行很慢,所以要等好一会儿才会出图~~
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
void demo1()
{
Mat img = cv::imread("../images/messi5.jpg");
Mat mask; // 存储掩码图像
Mat bgdModel; // 存储背景模型
Mat fgdModel; // 存储前景模型
// 使用GrabCut算法进行图像分割,指定感兴趣区域的矩形框作为初始化
cv::grabCut(img, mask, Rect(50, 50, 450, 290), bgdModel, fgdModel, 5, GC_INIT_WITH_RECT);
// 创建结果图像
Mat mask2(mask.rows, mask.cols, CV_8UC1);
for (int i = 0; i < mask.cols; i++)
{
for (int j = 0; j < mask.rows; j++)
{
// 获取掩码图像的像素值
char v = mask.at<char>(j, i);
// 根据掩码像素值设置结果图像的像素值
switch (v)
{
case 0: // 0 表示背景
case 2: // 2 表示可能的背景
mask2.at<char>(j, i) = 0; // 设置为黑色
break;
case 1: // 1 表示前景
case 3: // 3 表示可能的前景
mask2.at<char>(j, i) = 1; // 设置为白
break;
}
}
}
// 按Python逻辑,使用相乘算法
cv::cvtColor(mask2, mask2, COLOR_GRAY2BGR);
cv::multiply(img, mask2, img);
cv::imshow("out", img);
}
void demo2()
{
Mat img = cv::imread("../images/messi5.jpg");
Mat mask; // 存储掩码图像
Mat bgdModel; // 存储背景模型
Mat fgdModel; // 存储前景模型
// 使用GrabCut算法进行图像分割,指定感兴趣区域的矩形框作为初始化
cv::grabCut(img, mask, Rect(50, 50, 450, 290), bgdModel, fgdModel, 5, GC_INIT_WITH_RECT);
// 创建结果图像
Mat mask2(mask.rows, mask.cols, CV_8UC1);
for (int i = 0; i < mask.cols; i++)
{
for (int j = 0; j < mask.rows; j++)
{
// 获取掩码图像的像素值
char v = mask.at<char>(j, i);
// 根据掩码像素值设置结果图像的像素值
switch (v)
{
case 0: // 0 表示背景
case 2: // 2 表示可能的背景
mask2.at<char>(j, i) = 0; // 设置为黑色
break;
case 1: // 1 表示前景
case 3: // 3 表示可能的前景
mask2.at<char>(j, i) = 1; // 设置为白
break;
}
}
}
// 按Python逻辑,使用相乘算法
cv::cvtColor(mask2, mask2, COLOR_GRAY2BGR);
cv::multiply(img, mask2, img);
Mat newmask = cv::imread("image/newmask2.jpg", 0);
Mat rMask ;
cv::bitwise_and(mask, mask, rMask, newmask);
cv::grabCut(img, rMask, Rect(), bgdModel, fgdModel, 5, GC_INIT_WITH_MASK);
mask = Mat(rMask.rows, rMask.cols, CV_8UC1);
for (int i = 0; i < rMask.cols; i++)
{
for (int j = 0; j < rMask.rows; j++)
{
// 获取掩码图像的像素值
char v = rMask.at<char>(j, i);
// 根据掩码像素值设置结果图像的像素值
switch (v)
{
case 0: // 0 表示背景
case 2: // 2 表示可能的背景
mask.at<char>(j, i) = 0; // 设置为黑色
break;
case 1: // 1 表示前景
case 3: // 3 表示可能的前景
mask.at<char>(j, i) = 1; // 设置为白
break;
}
}
}
cv::cvtColor(mask, mask, COLOR_GRAY2BGR);
cv::multiply(img, mask, img);
cv::imshow("result", img);
}
int main()
{
demo2();
cv::waitKey(0);
}
Python版本代码如下:
demo1,简单提取
import numpy as np
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('../images/messi5.jpg')
mask = np.zeros(img.shape[:2], np.uint8)
bgdModel = np.zeros((1, 65), np.float64)
fgdModel = np.zeros((1, 65), np.float64)
rect = (50, 50, 450, 290)
# 函数的返回值是更新的 mask, bgdModel, fgdModel
cv2.grabCut(img, mask, rect, bgdModel, fgdModel, iterCount=5, mode=cv2.GC_INIT_WITH_RECT)
#迭代 5 次
mask2 = np.where((mask == 2) | (mask == 0), 0, 1).astype('uint8')
img = img * mask2[:, :, np.newaxis]
plt.imshow(img), plt.colorbar(), plt.show()
demo2,追加掩膜
import numpy as np
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('../images/messi5.jpg')
# cv2.imshow("img", img)
mask = np.zeros(img.shape[:2], np.uint8)
bgdModel = np.zeros((1, 65), np.float64)
fgdModel = np.zeros((1, 65), np.float64)
rect = (50, 50, 450, 290)
# 函数的返回值是更新的 mask, bgdModel, fgdModel
cv2.grabCut(img, mask, rect, bgdModel, fgdModel, 5, cv2.GC_INIT_WITH_RECT)
mask2 = np.where((mask == 2) | (mask == 0), 0, 1).astype('uint8')
img = img * mask2[:, :, np.newaxis]
# plt.imshow(img), plt.colorbar(), plt.show()
# newmask is the mask image I manually labelled
newmask = cv2.imread('newmask2.jpg', 0)
# whereever it is marked white (sure foreground), change mask=1
# whereever it is marked black (sure background), change mask=0
mask[newmask == 0] = 0
mask[newmask == 255] = 1
mask, bgdModel, fgdModel = cv2.grabCut(img, mask, None, bgdModel, fgdModel, 5, cv2.GC_INIT_WITH_MASK)
mask = np.where((mask == 2) | (mask == 0), 0, 1).astype('uint8')
img = img * mask[:, :, np.newaxis]
#cv2.imshow("out", img)
#cv2.imwrite("out.jpg", img)
plt.imshow(img), plt.colorbar(), plt.show()