算法的目标是这样的,目前有一批图像,是旋转矫正之后的图像,旋转之后,周围的空白,使用黑色填充。现在需要一批,矫正角度之后的图像,用于训练和预测,图像的旋转角度。因此,需要提取这些图像的最大内接矩形,去掉周围的黑色区域。
原始图像:
目标效果,即只截取中心区域:
获取图像的高和宽
h, w, _ = img_bgr.shape
img_copy = copy.copy(img_bgr)
使用洪水漫灌算法,避免周围出现一些干扰点,保证4个角的都是连通域,处理之后,Mask的四个角的值是1,其他地方是0。
def show_img_bgr(img_bgr, save_name=None):
"""
展示BGR彩色图
"""
import cv2
import matplotlib.pyplot as plt
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
plt.imshow(img_rgb)
plt.show()
if save_name:
print('[Info] 存储图像: {}'.format(save_name))
plt.imsave(save_name, img_rgb)
mask = np.zeros((h + 2, w + 2), np.uint8) # Mask
cv2.floodFill(img_copy, mask=mask, seedPoint=(0, 0), newVal=(0, 0, 255),
loDiff=(5, 5, 5, 5), upDiff=(5, 5, 5, 5))
cv2.floodFill(img_copy, mask=mask, seedPoint=(w-1, 0), newVal=(0, 0, 255),
loDiff=(5, 5, 5, 5), upDiff=(5, 5, 5, 5))
cv2.floodFill(img_copy, mask=mask, seedPoint=(0, h-1), newVal=(0, 0, 255),
loDiff=(5, 5, 5, 5), upDiff=(5, 5, 5, 5))
cv2.floodFill(img_copy, mask=mask, seedPoint=(w-1, h-1), newVal=(0, 0, 255),
loDiff=(5, 5, 5, 5), upDiff=(5, 5, 5, 5))
show_img_bgr(img_copy)
效果:
反转Mask的值,0和1互换,因为中心区域才是我们关注的区域,使用np.where(),即可反转,即:
mask_inv = np.where(mask > 0.5, 0, 1).astype(np.uint8) # 反转mask的0和1
因为图像周围可能会存在黑色区域,导致洪水漫灌之后,会出现其他异常区域,使用形态学的开区间,则可以去掉小的区域。
# 形态学Opening,过滤较小的非连通区域
kernel = np.ones((9, 9), np.uint8)
mask_inv = cv2.morphologyEx(mask_inv, cv2.MORPH_OPEN, kernel)
show_img_bgr(mask_inv * 255)
现在就是要从白色区域中,获得最大的内接矩形。
获取关注区域的角点,只保留1个连通区域,如:
contours, hierarchy = cv2.findContours(mask_inv, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
c_points = np.squeeze(contours) # 确保1个连通区域,多个连通区域会异常
边界图像:
获取角点的X和Y坐标,如下:
# 获取点的全部X\Y坐标
x_list, y_list = [], []
for p in c_points:
x_list.append(p[0])
y_list.append(p[1])
x_list = np.asarray(x_list)
y_list = np.asarray(y_list)
根据XY的坐标,获取内接Box的值,即[x_min, y_min, x_max, y_max]
,如下:
# 从XY坐标中,提取区域的最大矩形区域,即[x_min, y_min, x_max, y_max]
x_min_idx = np.argmin(x_list)
y_1 = y_list[x_min_idx]
x_max_idx = np.argmax(x_list)
y_2 = y_list[x_max_idx]
y_max, y_min = max(y_1, y_2), min(y_1, y_2)
y_min_idx = np.argmin(y_list)
x_1 = x_list[y_min_idx]
y_max_idx = np.argmax(y_list)
x_2 = x_list[y_max_idx]
x_max, x_min = max(x_1, x_2), min(x_1, x_2)
根据Bounding Box获取中心区域的图像,如下:
def get_patch(img, box):
"""
获取Img的Patch
:param img: 图像
:param box: [x_min, y_min, x_max, y_max]
:return 图像块
"""
h, w, _ = img.shape
x_min = int(max(0, box[0]))
y_min = int(max(0, box[1]))
x_max = int(min(box[2], w))
y_max = int(min(box[3], h))
img_patch = img[y_min:y_max, x_min:x_max, :]
return img_patch
img_patch = get_patch(img_bgr, [x_min, y_min, x_max, y_max])
show_img_bgr(img_patch)
结果如下:
最终实现,我们需要的效果。
全部源码:
def cut_img_without_margin(self, img_bgr):
"""
去掉旋转图像四个角的黑色区域,保留最大的内接矩形
:param img_bgr: bgr图像
:return: 最大的内接矩形图像
"""
from myutils.cv_utils import show_img_bgr
show_img_bgr(img_bgr)
h, w, _ = img_bgr.shape
img_copy = copy.copy(img_bgr)
# 从4个角,洪水漫灌,选择4个角的联通区域,
mask = np.zeros((h + 2, w + 2), np.uint8) # Mask
cv2.floodFill(img_copy, mask=mask, seedPoint=(0, 0), newVal=(0, 0, 255),
loDiff=(5, 5, 5, 5), upDiff=(5, 5, 5, 5))
cv2.floodFill(img_copy, mask=mask, seedPoint=(w-1, 0), newVal=(0, 0, 255),
loDiff=(5, 5, 5, 5), upDiff=(5, 5, 5, 5))
cv2.floodFill(img_copy, mask=mask, seedPoint=(0, h-1), newVal=(0, 0, 255),
loDiff=(5, 5, 5, 5), upDiff=(5, 5, 5, 5))
cv2.floodFill(img_copy, mask=mask, seedPoint=(w-1, h-1), newVal=(0, 0, 255),
loDiff=(5, 5, 5, 5), upDiff=(5, 5, 5, 5))
show_img_bgr(img_copy)
mask_inv = np.where(mask > 0.5, 0, 1).astype(np.uint8) # 反转mask的0和1
# 形态学Opening,过滤较小的非连通区域
kernel = np.ones((9, 9), np.uint8)
mask_inv = cv2.morphologyEx(mask_inv, cv2.MORPH_OPEN, kernel)
show_img_bgr(mask_inv * 255)
# 中心区域是1,其他位置是0
contours, hierarchy = cv2.findContours(mask_inv, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
c_points = np.squeeze(contours) # 确保1个连通区域,多个连通区域会异常
# 测试连通区域
cv2.drawContours(img_bgr, contours, -1, (0, 255, 0), 3)
show_img_bgr(img_bgr)
# 获取点的全部X\Y坐标
x_list, y_list = [], []
for p in c_points:
x_list.append(p[0])
y_list.append(p[1])
x_list = np.asarray(x_list)
y_list = np.asarray(y_list)
# 从XY坐标中,提取区域的最大矩形区域,即[x_min, y_min, x_max, y_max]
x_min_idx = np.argmin(x_list)
y_1 = y_list[x_min_idx]
x_max_idx = np.argmax(x_list)
y_2 = y_list[x_max_idx]
y_max, y_min = max(y_1, y_2), min(y_1, y_2)
y_min_idx = np.argmin(y_list)
x_1 = x_list[y_min_idx]
y_max_idx = np.argmax(y_list)
x_2 = x_list[y_max_idx]
x_max, x_min = max(x_1, x_2), min(x_1, x_2)
# 获取核心的中心区域
img_patch = get_patch(img_bgr, [x_min, y_min, x_max, y_max])
show_img_bgr(img_patch)
# 写入输出图像
img_out_path = os.path.join(DATA_DIR, 'out.jpg')
cv2.imwrite(img_out_path, img_patch)
print('[Info] 处理完成!')
return img_patch
参考: