用OpenCV进行人物图像分割与背景替换:从GrabCut到图像融合的完整过程

任务:通过openCV,将群中所示图片中人物扣出并放置于新背景中。

在这里插入图片描述

第一步:读取图片和背景图像

背景

读取图像是任何图像处理任务的起始步骤。通过cv2.imread来加载图像文件,之后可以进行各种操作(如调整大小、裁剪等)。

原理

  • 使用cv2.imread()读取图片文件。
  • 确保图像尺寸匹配,调整背景图像的大小使其与人物图像的尺寸一致。

函数原型

cv2.imread(filename, flags)

参数含义

filename:图像的路径,必须为字符串。
flags:读取图像的方式。0表示灰度图1表示彩色图-1表示包括透明通道的图像

返回值

返回一个NumPy数组,包含图像的像素数据。如果路径无效或文件损坏,返回None

代码示例

person_image = cv2.imread('person.png')
background_image = cv2.imread('background.png')
mask = cv2.imread('mask.png', 0)

变量内容

person_image:存储人物图像的数组。
background_image:存储背景图像的数组。

常见问题

  • 路径错误或图像文件损坏导致图像无法读取。
  • 读取的图像格式不一致,导致操作时产生错误。

应用场景

此步骤常见于图像预处理,尤其是需要加载多个图像进行处理时。

第二步:调整背景图像大小

背景

通常情况下,背景图像的尺寸可能与人物图像不匹配,因此需要对背景图像进行大小调整。

原理

使用cv2.resize()函数调整背景图像的尺寸,使其与人物图像一致。

函数原型

cv2.resize(src, dsize, fx, fy, interpolation)

参数含义

src:输入图像。
dsize:输出图像的大小,表示为 (宽度, 高度) 的元组。它定义了调整后图像的宽度和高度。如果使用了fxfy,则可以将dsize设置为(0, 0),并由缩放因子来确定大小
fx, fy:缩放因子,可以根据这两个值动态计算目标大小。
interpolation:插值方法,常用的有:
      cv2.INTER_NEAREST: 最近邻插值(速度快,效果较差)
      cv2.INTER_LINEAR: 双线性插值(默认方法,适用于放大)
      cv2.INTER_CUBIC: 三次样条插值(效果好,但速度慢)
      cv2.INTER_LANCZOS4: Lanczos插值(高质量,适用于下采样)

返回值

返回调整大小后的图像数组。

代码示例

background_image = cv2.resize(background_image, (person_image.shape[1], person_image.shape[0]))

变量内容

background_image:调整大小后的背景图像。

常见问题

  • 插值方法选择不当可能导致图像模糊或失真。
  • 背景图像与人物图像尺寸不匹配,可能影响后续的图像融合效果。

应用场景

在图像合成中,通常需要将不同来源的图像尺寸统一,使得它们能够顺利融合。

第三步:二值化掩码图像

背景

掩码图像用于区分前景(人物)和背景。二值化处理将图像分为两类:背景和前景。前景通常为白色(255),背景为黑色(0)。

原理

  • 使用cv2.threshold()将掩码图像二值化。
  • 通过设定一个阈值,将图像中低于该阈值的像素设为0,高于该阈值的像素设为255。

函数原型

cv2.threshold(src, thresh, maxval, type)

参数含义

src:输入图像,通常为灰度图。
thresh:阈值,小于该值的像素设为0。
maxval:大于阈值的像素值,通常为255。
type:阈值类型,cv2.THRESH_BINARY表示大于阈值的设为最大值,其他设为0。

返回值

返回二值化后的图像和阈值。

代码示例

_, mask = cv2.threshold(mask, 127, 255, cv2.THRESH_BINARY)

变量内容

mask:二值化后的掩码图像。

常见问题

  • 阈值设置不合适,导致前景和背景分割不准确。
  • 掩码不准确时,可能需要使用其他方法,如OTSU自适应阈值。。

应用场景

掩码图像广泛用于背景去除、前景提取等任务中。

第四步:使用GrabCut进行图像分割

背景

GrabCut是一种基于图像分割的算法,可以根据初始化的前景和背景信息进行迭代优化,自动分离前景和背景。

原理

  • 初始化掩码,其中前景设为cv2.GC_FGD,背景设为cv2.GC_BGD
  • 使用cv2.grabCut()进行图像分割,得到精确的前景和背景。

函数原型

cv2.grabCut(image, mask, rect, bgdModel, fgdModel, iterCount, mode)

参数含义

image:输入的图像,必须是一个 8 位或 32 位浮点型的图像。
mask :与输入图像大小相同的掩膜(mask),它是一个 8 位单通道图像。每个像素的值代表它所属的类别:
          0: 背景。
          1: 前景。
          2: 不确定区域。
          3: 可能是前景(用来初始化前景)。
   在运行grabCut之前,你可以初始化这个掩膜,告诉算法哪些区域是已知的前景或背景。
rect:一个矩形区域 (x, y, w, h),表示大致的前景区域。(x, y) 是矩形左上角的坐标,(w, h) 是矩形的宽度和高度。这个矩形是算法的初始猜测,告诉grabCut大致的前景位置。
bgdModel:一个 1x13 的数组,用于存储背景模型。grabCut算法会根据图像的颜色特征来构建背景模型。
fgdModel:一个 1x13 的数组,用于存储前景模型。与bgdModel类似,用于构建前景模型。
iterCount:最大迭代次数。算法会根据给定的迭代次数逐步优化结果。如果设置为 5,意味着最多进行 5 次迭代。
mode:模式参数,指定grabCut的运行模式。它有两个值:
     cv2.GC_INIT_WITH_RECT: 使用矩形区域初始化前景和背景。
     cv2.GC_INIT_WITH_MASK: 使用掩膜初始化前景和背景。适用于已经有初始掩膜的情况。

返回值

mask:更新后的掩膜,其中每个像素的值表示该像素属于前景(值为 1)还是背景(值为 0)。
bgdModel:更新后的背景模型。
fgdModel:更新后的前景模型。

代码示例

_, mask = cv2.threshold(mask, 127, 255, cv2.THRESH_BINARY)

# GrabCut初始化掩码
grabcut_mask = np.where(mask == 255, cv2.GC_FGD, cv2.GC_BGD).astype('uint8')
bgd_model = np.zeros((1, 65), np.float64)
fgd_model = np.zeros((1, 65), np.float64)

# 使用GrabCut进行前景/背景分离
cv2.grabCut(person_image, grabcut_mask, None, bgd_model, fgd_model, 5, cv2.GC_INIT_WITH_MASK)

# 根据GrabCut的输出创建最终的前景/背景掩码
mask2 = np.where((grabcut_mask == 2) | (grabcut_mask == 0), 0, 1).astype('uint8')
mask2 = mask2 * 255  # 将mask2放大为0和255的值

变量内容

grabcut_mask:更新后的掩码。

常见问题

  • 掩码不准确时,GrabCut分割效果不理想。
  • 对于复杂图像,可能需要更多的迭代次数。

应用场景

GrabCut被广泛应用于图像分割、虚拟背景替换、前景提取等领域。

第五步:图像融合(羽化处理)

背景

图像融合需要将前景和背景合成,通常涉及羽化处理,以确保过渡平滑。

原理

  • 使用cv2.GaussianBlur()对掩码进行模糊处理。
  • 使用加权平均法将前景和背景图像融合。

函数原型

cv2.GaussianBlur(src, ksize, sigmaX)

参数含义

src:输入图像,通常是一个彩色或灰度图像。
ksize:高斯核的大小。它是一个 (宽度, 高度) 的元组,必须是正奇数(例如 3, 5, 7 等)。它表示应用于图像的卷积核的大小。较大的 ksize 会导致更强的模糊效果。
sigmaX:高斯核在 X 方向的标准差。sigmaX控制模糊的程度,值越大,模糊效果越强。通常,sigmaX取值在 0 到 10 之间。如果 sigmaX 设置为 0,OpenCV 会根据ksize自动计算合适的标准差。

返回值

dst`:返回模糊处理后的图像。

代码示例

# 羽化处理并转换为归一化的alpha通道
blur_mask = cv2.GaussianBlur(mask, (7, 7), 0)
alpha = blur_mask.astype(float) / 255

# 羽化处理
mask2_blur = cv2.GaussianBlur(mask2, (7, 7), 0) / 255

# 计算前景和背景图像
person_fg = person_image.astype(float)
background_fg = background_image.astype(float)

# 通过mask进行平滑融合
final_image = person_fg * alpha[..., np.newaxis] + background_fg * (1 - alpha[..., np.newaxis])
final_image = np.clip(final_image, 0, 255).astype(np.uint8)

# 对最终结果进行融合
person_resized = cv2.resize(person_fg, (background_image.shape[1], background_image.shape[0]))
mask2_blur = cv2.resize(mask2_blur, (background_image.shape[1], background_image.shape[0]))[..., np.newaxis]

# 使用羽化过的mask进行融合
result = person_resized * mask2_blur + background_fg * (1 - mask2_blur)
result = result.astype(np.uint8)

变量内容

lpha:羽化后的掩码,控制前景的透明度。
final_image:融合后的最终图像。

常见问题

  • 羽化半径设置不当可能导致过渡不自然。
  • 图像尺寸不一致时,融合效果会受到影响。

应用场景

在图像合成、视频编辑、虚拟现实等领域中,图像融合广泛应用于背景替换和合成任务

完整代码

import cv2
import numpy as np

# 读取图像文件
person_image = cv2.imread('person.png')
background_image = cv2.imread('background.png')
mask = cv2.imread('mask.png', 0)

# 调整背景图像大小
background_image = cv2.resize(background_image, (person_image.shape[1], person_image.shape[0]))

# 二值化掩码
_, mask = cv2.threshold(mask, 127, 255, cv2.THRESH_BINARY)

# 羽化处理并转换为归一化的alpha通道
blur_mask = cv2.GaussianBlur(mask, (7, 7), 0)
alpha = blur_mask.astype(float) / 255

# GrabCut初始化掩码
grabcut_mask = np.where(mask == 255, cv2.GC_FGD, cv2.GC_BGD).astype('uint8')
bgd_model = np.zeros((1, 65), np.float64)
fgd_model = np.zeros((1, 65), np.float64)

# 使用GrabCut进行前景/背景分离
cv2.grabCut(person_image, grabcut_mask, None, bgd_model, fgd_model, 5, cv2.GC_INIT_WITH_MASK)

# 根据GrabCut的输出创建最终的前景/背景掩码
mask2 = np.where((grabcut_mask == 2) | (grabcut_mask == 0), 0, 1).astype('uint8')
mask2 = mask2 * 255  # 将mask2放大为0和255的值

# 羽化处理
mask2_blur = cv2.GaussianBlur(mask2, (7, 7), 0) / 255.0

# 计算前景和背景图像
person_fg = person_image.astype(float)
background_fg = background_image.astype(float)

# 通过mask进行平滑融合
final_image = person_fg * alpha[..., np.newaxis] + background_fg * (1 - alpha[..., np.newaxis])
final_image = np.clip(final_image, 0, 255).astype(np.uint8)

# 对最终结果进行融合
person_resized = cv2.resize(person_fg, (background_image.shape[1], background_image.shape[0]))
mask2_blur = cv2.resize(mask2_blur, (background_image.shape[1], background_image.shape[0]))[..., np.newaxis]

# 使用羽化过的mask进行融合
result = person_resized * mask2_blur + background_fg * (1 - mask2_blur)
result = result.astype(np.uint8)

# 保存并显示最终结果
cv2.imwrite('final_image.png', result)
cv2.imshow('Result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()

在这里插入图片描述

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐