学习来自【OpenCv】利用roi 掩模 将一张图片添加到另一张上
任务描述:提取图片A的 mask 区域,并粘贴到图片B上
1、代码实现
A 图
A 图的 mask 标签
B 图
结果
下面看看代码流程
import cv2
import numpy as np
from os.path import join
from os import listdir
rootpath = "/home/bryant/datasets/"
img1 = cv2.imread(join(rootpath, "images", "00000.jpg")) # A 图
img2 = cv2.imread(join(rootpath, "matting", "00000.jpg")) # A 图 mask
img3 = cv2.imread("animal_world_70223.jpg") # B 图
h, w, c = img1.shape
img1 = cv2.resize(img1, (w//2, h//2))
img2 = cv2.resize(img2, (w//2, h//2))
img3 = cv2.resize(img3, (w//2, h//2))
img2not = cv2.bitwise_not(img2)
img2_gray = cv2.cvtColor(img2not, cv2.COLOR_BGR2GRAY)
ret, mat = cv2.threshold(img2_gray, 170, 255, cv2.THRESH_BINARY)
cv2.imshow("1", mat) # 图 1 mask 黑白颠倒
fg1 = cv2.bitwise_and(img3, img3, mask=mat)
cv2.imshow("2", fg1) # 图 2,仅输出 mask 非零区域的与
mat2 = cv2.bitwise_not(mat)
cv2.imshow('3', mat2) # 图 3,mask 黑白颠倒回来
fg2 = cv2.bitwise_and(img1, img1, mask=mat2)
cv2.imshow('4', fg2) # 图 4,仅输出 mask 非零区域的与
dst = cv2.add(fg1, fg2) # 图 2 图 4 结合
cv2.imshow('dst', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
2、结果展示
图 1
图 2
图 3
图 4
结果
3、另辟蹊径
import cv2
import numpy as np
import matplotlib.pyplot as plt
# Original image, which is the background
background = cv2.imread('background.jpg')
background = cv2.cvtColor(background, cv2.COLOR_BGR2RGB)
# Image of the object
img = cv2.imread('apple.jpg')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# Image the object's mask
mask = cv2.imread('apple_mask.jpg')
mask = cv2.cvtColor(mask, cv2.COLOR_BGR2RGB)
print("Background shape:", background.shape)
print("Image shape:", img.shape)
print("Mask shape:", img.shape)
"""
Background shape: (2160, 3840, 3)
Image shape: (1080, 1920, 3)
Mask shape: (1080, 1920, 3)
"""
plt.figure(figsize=(16, 16))
plt.title("Background", fontsize=18)
plt.axis("off")
plt.imshow(background)
fig, ax = plt.subplots(1, 2, figsize=(16, 7))
ax[0].imshow(img)
ax[0].set_title('Apple', fontsize=18)
ax[0].axis("off")
ax[1].imshow(mask)
ax[1].set_title('Apple Mask', fontsize=18)
ax[1].axis("off")
plt.show()
def remove_obj_background(img_path, mask_path):
'''
Function returns:
- image of the object with removed background in CV2 RGB format (numpy array with dimensions (width, height, 3))
- boolean mask of the object (numpy array with dimensions (width, height))
'''
img = cv2.imread(img_path)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
mask = cv2.imread(mask_path)
#mask = cv2.bitwise_not(mask) # object black, background white
mask = cv2.cvtColor(mask, cv2.COLOR_BGR2RGB) # (1080, 1920, 3)
# Boolean mask is a numpy array with two dimensions: width and height.
# On the original mask, object area is filled with white color, background area is filled with black color.
# On the boolean mask, object area is filled with True, background area is filled with False.
# mask_boolean = mask[:, :, 0] == 0
mask_boolean = (mask[:,:,0] == 255) # (1080, 1920) #
img_with_removed_background = img * np.stack([mask_boolean, mask_boolean, mask_boolean], axis=2) # (1080, 1920, 3)
return img_with_removed_background, mask_boolean
img_with_removed_background, mask_boolean = remove_obj_background('apple.jpg', 'apple_mask.jpg')
print("Shape of the image of the object:", img_with_removed_background.shape)
print("Shape of the boolean mask:", mask_boolean.shape)
print("\n")
# Image with removed background shape: (860, 1151, 3)
# Boolean mask shape: (860, 1151)
fig, ax = plt.subplots(1, 2, figsize=(16, 7))
ax[0].imshow(img_with_removed_background)
ax[0].set_title('Object with removed background', fontsize=18)
ax[0].axis("off")
ax[1].imshow(mask_boolean)
ax[1].set_title('Boolean mask', fontsize=18)
ax[1].axis("off")
plt.show()
def add_obj(background, img, mask, x, y):
'''
Arguments:
background - background image in CV2 RGB format
img - image of object in CV2 RGB format
mask - mask of object in CV2 RGB format
x, y - coordinates of the center of the object image
0 < x < width of background
0 < y < height of background
Function returns background with added object in CV2 RGB format
CV2 RGB format is a numpy array with dimensions width x height x 3
'''
bg = background.copy()
h_bg, w_bg = bg.shape[0], bg.shape[1]
h, w = img.shape[0], img.shape[1]
# Calculating coordinates of the top left corner of the object image
x = x - int(w/2)
y = y - int(h/2)
mask_boolean = mask[:, :, 0] == 255
# mask_boolean = mask[:,:,0] == 0
mask_rgb_boolean = np.stack([mask_boolean, mask_boolean, mask_boolean], axis=2)
if x >= 0 and y >= 0:
h_part = h - max(0, y+h-h_bg) # h_part - part of the image which overlaps background along y-axis
w_part = w - max(0, x+w-w_bg) # w_part - part of the image which overlaps background along x-axis
bg[y:y+h_part, x:x+w_part, :] = bg[y:y+h_part, x:x+w_part, :] * ~mask_rgb_boolean[0:h_part, 0:w_part, :] \
+ (img * mask_rgb_boolean)[0:h_part, 0:w_part, :]
elif x < 0 and y < 0:
h_part = h + y
w_part = w + x
bg[0:0+h_part, 0:0+w_part, :] = bg[0:0+h_part, 0:0+w_part, :] * ~mask_rgb_boolean[h-h_part:h, w-w_part:w, :] \
+ (img * mask_rgb_boolean)[h-h_part:h, w-w_part:w, :]
elif x < 0 and y >= 0:
h_part = h - max(0, y+h-h_bg)
w_part = w + x
bg[y:y+h_part, 0:0+w_part, :] = bg[y:y+h_part, 0:0+w_part, :] * ~mask_rgb_boolean[0:h_part, w-w_part:w, :] \
+ (img * mask_rgb_boolean)[0:h_part, w-w_part:w, :]
elif x >= 0 and y < 0:
h_part = h + y
w_part = w - max(0, x+w-w_bg)
bg[0:0+h_part, x:x+w_part, :] = bg[0:0+h_part, x:x+w_part, :] * ~mask_rgb_boolean[h-h_part:h, 0:w_part, :] \
+ (img * mask_rgb_boolean)[h-h_part:h, 0:w_part, :]
return bg
composition_1 = add_obj(background, img, mask, 160, 120)
plt.figure(figsize=(15,15))
plt.imshow(composition_1)
plt.axis("off")
plt.show()
composition_2 = add_obj(composition_1, img, mask, 3700, 140)
plt.figure(figsize=(15,15))
plt.imshow(composition_2)
plt.axis("off")
plt.show()
composition_3 = add_obj(composition_2, img, mask, 1800, 1100)
plt.figure(figsize=(15,15))
plt.imshow(composition_3)
plt.axis("off")
plt.show()
composition_4 = add_obj(composition_3, img, mask, 180, 2000)
plt.figure(figsize=(15,15))
plt.imshow(composition_4)
plt.axis("off")
plt.show()
composition_5 = add_obj(composition_4, img, mask, 3700, 2070)
plt.figure(figsize=(15,15))
plt.imshow(composition_5)
plt.axis("off")
plt.show()
composition_6 = add_obj(composition_5, img, mask, 1860, 90)
plt.figure(figsize=(15,15))
plt.imshow(composition_6)
plt.axis("off")
plt.show()
composition_7 = add_obj(composition_6, img, mask, 3785, 1065)
plt.figure(figsize=(15,15))
plt.imshow(composition_7)
plt.axis("off")
plt.show()
composition_8 = add_obj(composition_7, img, mask, 1900, 2025)
plt.figure(figsize=(15,15))
plt.imshow(composition_8)
plt.axis("off")
plt.show()
composition_9 = add_obj(composition_8, img, mask, 35, 920)
plt.figure(figsize=(15,15))
plt.imshow(composition_9)
plt.axis("off")
plt.show()
输入图片
apple.jpg
apple_mask.jpg
background.jpg
输出结果
4、涉及到的库
cv2.bitwise_not
官方英文文档
cv2.bitwise_not 是 OpenCV 图像处理库中的一个函数,用于对图像的每个像素值进行取反操作。以下是对 cv2.bitwise_not 函数的中文解释和参数说明:
一、函数原型:
dst = cv2.bitwise_not(src[, dst[, mask]])
二、参数说明:
src:输入图像,可以是灰度图像或彩色图像。
dst:输出图像,与 src 图像具有相同的大小和类型。通常可以省略此参数,函数会自动返回结果。
mask:可选操作掩码,一个8位单通道数组,用于指定要更改的输出数组的元素。如果指定了掩码,则只有掩码中对应位置为255(非零)的像素才会进行取反操作。
三、功能描述:
cv2.bitwise_not 函数会对输入图像 src 中的每个像素值进行取反操作。对于灰度图像,取反操作是将像素值从0~255的范围映射到其对应的相反值(即 255 - pixel_value)。对于彩色图像,会分别对图像中的每个颜色通道(B、G、R)进行取反操作。
四、应用场景:
图像增强:通过取反操作,可以改变图像的亮度和对比度,从而增强图像中的某些特征或细节。
图像处理:在某些特定的图像处理任务中,取反操作可以作为预处理步骤,用于简化后续处理过程或突出图像中的某些信息。
五、示例代码:
python
import cv2
# 读取图像
img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE) # 读取为灰度图像
# 对图像进行取反操作
not_img = cv2.bitwise_not(img)
# 显示原图像和取反后的图像
cv2.imshow('Original Image', img)
cv2.imshow('Bitwise NOT Image', not_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
请注意,cv2.bitwise_not 函数只要求输入一张图像,不需要两张图像深度、像素行列必须一致等条件。
cv2.bitwise_and
官方英文文档
mask,图像掩膜,可选参数,为8位单通道的灰度图像,用于指定要更改的输出图像数组的元素,即输出图像像素只有mask对应位置元素不为0的部分才输出,否则该位置像素的所有通道分量都设置为0
cv2.bitwise_and 是 OpenCV 库中的一个函数,用于对两个图像进行按位与(bitwise AND)操作。以下是关于 cv2.bitwise_and 的中文说明:
一、函数原型:
dst = cv2.bitwise_and(src1, src2[, dst[, mask]])
二、参数说明:
src1:第一幅输入图像。
src2:第二幅输入图像,其大小和类型应与 src1 相同。
dst:输出图像,与输入图像具有相同的尺寸和数据类型。这是一个可选参数,如果没有提供,则会自动创建一个新的输出图像。
mask:一个可选的8位单通道数组,用于指定哪些像素进行按位与操作。如果指定了掩码,则只有掩码中对应位置为255(非零)的像素才会进行按位与操作。
三、功能描述:
cv2.bitwise_and 函数会对输入的两个图像(src1 和 src2)的每个对应像素值进行按位与操作。具体来说,它会将两个图像在相同位置上的像素值视为二进制数,并对这些二进制数执行按位与运算。按位与操作的特点是,只有当两个二进制数的对应位都为1时,结果的该位才为1,否则为0。
在图像处理中,cv2.bitwise_and 常用于掩码操作、图像融合等场景。例如,可以使用一个二值掩码图像来指定哪些像素应该保留在输出图像中,哪些像素应该被丢弃。
四、示例代码:
以下是一个简单的示例,展示如何使用 cv2.bitwise_and 函数对两个图像进行按位与操作:
python
import cv2
# 读取两个图像
img1 = cv2.imread('image1.jpg', cv2.IMREAD_GRAYSCALE)
img2 = cv2.imread('image2.jpg', cv2.IMREAD_GRAYSCALE)
# 确保两个图像具有相同的大小
if img1.shape != img2.shape:
print("Images do not have the same size. Resizing...")
# 在此可以添加代码来调整图像大小
# 执行按位与操作
result = cv2.bitwise_and(img1, img2)
# 显示结果图像
cv2.imshow('Bitwise AND Result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
注意:在实际应用中,如果两个图像的大小或数据类型不匹配,你可能需要首先进行相应的预处理步骤,例如调整图像大小或转换数据类型。
cv2.add
官方英文文档
cv2.add 是 OpenCV 库中的一个函数,用于对两个图像或图像与标量进行加法运算。以下是关于 cv2.add 的中文说明:
一、函数原型:
dst = cv2.add(src1, src2[, dst[, mask[, dtype]]])
二、参数说明:
src1:第一个输入数组或图像。
src2:第二个输入数组或图像,或者是一个标量(即单一的数值)。如果 src2 是标量,那么它会与 src1 中的所有像素进行相加。
dst:输出数组或图像,与输入图像具有相同的尺寸和通道数。如果指定了 dst,则结果会保存在 dst 中;否则,会创建一个新的数组或图像。
mask:可选参数,8位单通道的灰度图像,用于指定要更改的输出图像数组的元素。即,只有 mask 对应位置元素不为0的像素才会进行加法运算,否则该位置像素的所有通道分量都设置为0。
dtype:可选参数,输出图像数组的深度(即图像单个像素值的位数)。如果没有指定,则默认与输入图像相同。
三、功能描述:
cv2.add 函数对两个图像或图像与标量进行加法运算。对于两个图像,它们必须具有相同的大小和通道数。对于图像与标量的加法,标量会与图像中的每个像素值进行相加。
加法运算的结果会考虑数据类型和可能的溢出。例如,如果图像是8位无符号整型,那么结果会限制在0到255之间。如果结果超过255,它会被截断为255。
四、注意事项:
当使用 cv2.add 进行图像加法时,需要确保两个输入图像具有相同的大小和通道数。
如果可能的话,为了避免图像失真,建议在进行图像加法运算时将数据类型转换为更高的位数,如16位有符号整型或浮点型。
五、示例代码:
以下是一个简单的示例,展示如何使用 cv2.add 函数对两个图像进行加法运算:
import cv2
# 读取两个图像
img1 = cv2.imread('image1.jpg')
img2 = cv2.imread('image2.jpg')
# 确保两个图像具有相同的大小
if img1.shape != img2.shape:
print("Images do not have the same size. Resizing...")
# 在此可以添加代码来调整图像大小
# 使用 cv2.add 进行图像加法运算
result = cv2.add(img1, img2)
# 显示结果图像
cv2.imshow('Image Addition Result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
注意:以上示例假设两个图像是彩色图像,并且已经加载为 NumPy 数组。如果图像是灰度图像,或者你需要对图像进行其他类型的预处理,你可能需要相应地修改代码。
附录——获取 mask 的边界框
mask = cv2.imread("mask.jpg")
gray = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
_, new_mask = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY)
non_zero_indices = np.nonzero(new_mask)
x_ind = non_zero_indices[1]
y_ind = non_zero_indices[0]
x_min, x_max = np.min(x_ind), np.max(x_ind)
y_min, y_max = np.min(y_ind), np.max(y_ind)
bbox = [x_min, x_max, y_min, y_max]