OpenCV版泊松融合seamlessClone融合结果1像素偏差问题记录与分析

本文主要是介绍OpenCV版泊松融合seamlessClone融合结果1像素偏差问题记录与分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

泊松融合(Poisson Blending)是图像处理领域著名的图像融合算法,自从2003年发表以来,有很多基于此算法的应用和改进研究出现。泊松融合无需像Alpha blending一样的精确抠图就可以得到很自然的结果。

关于泊松融合原理部分的解析见之前的博客《泊松融合原理浅析》。

关于针对OpenCV中泊松融合的实现代码(以normalClone为例)进行解读见之前的博客《OpenCV源码解读:泊松融合seamlessClone(normalClone)》。
在这里插入图片描述
在使用泊松融合OpenCV实现的方法时,需要注意一些细节,否则在某些特定场景下可能出现融合结果有细微差异的问题。

问题

在泊松融合过程中,有一个比较重要的步骤就是计算源图像(记为src)融合到目标图像(记为dst)上的具体位置和大小。

反映到具体实现部分,就是根据传参传入的mask和p计算取的src以及dst的融合部分。OpenCV实现代码如下:
在这里插入图片描述
特别注意这三行代码:

Mat mask_inner = mask(Rect(1, 1, mask.cols - 2, mask.rows - 2));copyMakeBorder(mask_inner, mask, 1, 1, 1, 1, BORDER_ISOLATED | BORDER_CONSTANT, Scalar(0));Rect roi_s = boundingRect(mask);

这三行代码将传入的mask进行了边界处理,将mask的边界用0做了填充,然后调用boundingRect求外接矩形。确保了边界能够正常参与到泊松方程构建。

用于截取src的融合区域的roi_s确定了,随后根据roi_s和传入的点p,计算用于截取dst融合区域的roi_d。

Rect roi_d(p.x - roi_s.width / 2, p.y - roi_s.height / 2, roi_s.width, roi_s.height);

最后根据计算得到的roi_s和roi_d分别从src和dst中截取用于融合的图像区域,以及用于指导融合的mask区域。

Mat destinationROI = dest(roi_d).clone();Mat sourceROI = Mat::zeros(roi_s.height, roi_s.width, src.type());
src(roi_s).copyTo(sourceROI,mask(roi_s));Mat maskROI = mask(roi_s);
Mat recoveredROI = blend(roi_d);

由于这一部分计算在函数cv::seamlessClone中内置,所以需要注意点p的计算。特别是当在用于融合的原始dst和原始src有着一样的size时,点p的计算则需要根据这一内置计算过程和使用的mask特殊处理。

假设这样一种场景,源图像大小为(128, 128, 3),目标图像大小也为(128, 128, 3),mask大小是(128,128)。我们希望将源图像按照给定的mask融合到目标图像上。请注意mask在保证是闭区间的条件下,其边界形状可以是任意的。也就是说其边界像素点可以是矩形(128, 128)内的任意点。这时点p则一般根据mask的外接矩形计算得到,取值通常是矩形长宽的中点。

Rect roi = cv::boundingRect(mask)
Point p = Point(int(roi.x + roi.width / 2), int(roi.y + roi.height / 2))

在绝大多数情况下,特别是只融合单张图片时,融合不会有明显的问题。但是当对视频中某些场景的连续帧都做相同的融合操作时,根据mask计算的点p则可能导致融合结果不自然的问题,在视频中能明显的观察到融合结果抖动的现象。

分析

深入分析,发现在产生抖动的情况下,mask在边界上的表现不一致,部分帧的mask边界在非图像边界上,而部分帧的边界直接到了图像的边界。推测这是导致视频连续帧融合结果抖动的原因。

根据mask计算点p的操作:

Rect roi = cv::boundingRect(mask)
Point p = Point(int(roi.x + roi.width / 2), int(roi.y + roi.height / 2))

以及在函数cv::seamlessClone中内置的mask处理:

Mat mask_inner = mask(Rect(1, 1, mask.cols - 2, mask.rows - 2));copyMakeBorder(mask_inner, mask, 1, 1, 1, 1, BORDER_ISOLATED | BORDER_CONSTANT, Scalar(0));Rect roi_s = boundingRect(mask);Rect roi_d(p.x - roi_s.width / 2, p.y - roi_s.height / 2, roi_s.width, roi_s.height);

试想有下列三种情况(图像Size均为(128,128)):

(1)mask的点在图像的上下左右边界上出现

也就是通过Rect roi = cv::boundingRect(mask)得到的roi为(0,0,128,128),此时点p经过Point p = Point(int(roi.x + roi.width / 2), int(roi.y + roi.height / 2))为(64,64)。

将该mask和点p传入cv::seamlessClone中,mask经过copyMakeBorder处理后图像边界后的点都被清除,计算得到的roi_s为(1,1,126,126)。

根据roi_s和点p计算得到的roi_d为(64-126/2,64-126/2,126,126),即(1,1,126,126)。此时roi_s和roi_d一致,截取的部分一致。

(2)mask的点在图像的上左右三个边界上出现,mask下边界点在图像下边界内1个像素

在这里插入图片描述

此时通过Rect roi = cv::boundingRect(mask)得到的roi为(0,0,127,127),此时点p经过Point p = Point(int(roi.x + roi.width / 2), int(roi.y + roi.height / 2)),由于向下取整值为(63,63)。

将该mask和点p传入cv::seamlessClone中,mask经过copyMakeBorder处理后图像边界后的点都被清除,计算得到的roi_s为(1,1,126,126)。

根据roi_s和点p计算得到的roi_d为(63-126/2,63-126/2,126,126),即(0,0,126,126)。此时roi_s和roi_d不一致,截取的部分不一致。dst上从坐标(0, 0)开始截取,而src上从坐标(1, 1)开始截取。

当视频的前后帧融合mask在(1)和(2)类型间交替时,则融合的结果会出现抖动现象。人眼对这种微小的抖动特别敏感。

(3)mask的边界点均在图像的上下左右边界内1个像素

在这里插入图片描述

此时通过Rect roi = cv::boundingRect(mask)得到的roi为(1,1,126,126),此时点p经过Point p = Point(int(roi.x + roi.width / 2), int(roi.y + roi.height / 2))值为(64,64)。

将该mask和点p传入cv::seamlessClone中,mask经过copyMakeBorder处理后计算得到的roi_s为(1,1,126,126)。

根据roi_s和点p计算得到的roi_d为(64-126/2,64-126/2,126,126),即(1,1,126,126)。此时roi_s和roi_d一致,截取的部分一致。

总结:也就是说如果在src和dst拥有相同Size时,如果直接根据mask的外接矩形计算点p,那么可能因为(1)和(2)情形的间隔出现,导致视频连续帧融合结果抖动。

解决

在分析清楚了问题所在,需要想办法解决这一问题。

不过有一个比较疑惑的问题,就是对于这种1个像素的差异,人眼真的能如此敏感吗?

写个测试脚本来模拟一下这种抖动情况:

# coding: utf-8__author__ = 'TracelessLe'import os
import cv2def cv2_load_material(file_path):"""Load a full list of frames in memory."""cap = cv2.VideoCapture(file_path)frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))fps = cap.get(cv2.CAP_PROP_FPS)res = []while True:ret, frame = cap.read()if ret is False:breakres.append(frame)material = restotal_frames = len(res)cap.release()return material, frame_width, frame_height, fpsdef cv2_output_video(output_imgs, output_file_path, fps, frame_size):size = frame_sizefourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')vout = cv2.VideoWriter()vout.open(output_file_path, fourcc, fps, size, True)for img in output_imgs:vout.write(img)vout.release()# 模拟一个像素的抖动
def monidoudong_1pixel(img_bgr_list):for i, img_bgr in enumerate(img_bgr_list):bbox = img_bgr[250:660,200:460,::]if i%2 == 0:text = f'frame: {i}'cv2.putText(img_bgr, text, (int(100), int(100)),cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 1, cv2.LINE_AA)img_bgr[249:659,200:460,::] = bboxif __name__ == "__main__":####测试单个视频用#####file_path = 'test.mp4' #原视频大小720*1280img_bgr_list, frame_width, frame_height, fps = cv2_load_material(file_path)out_file_path = './out-1pixel.mp4'monidoudong_1pixel(img_bgr_list)cv2_output_video(img_bgr_list, out_file_path, fps, (frame_width, frame_height))

截取抖动区域如下,可以看到脸部存在上下抖动现象:
在这里插入图片描述
在弄清楚原因后,就能知道从什么角度入手解决这种抖动问题。通过保持与cv::seamlessClone函数内部一致的mask处理,避免roi_d和roi_s对应图像区域不一致的问题。
将求点p的代码

Rect roi = cv::boundingRect(mask)
Point p = Point(int(roi.x + roi.width / 2), int(roi.y + roi.height / 2))

改为:

Mat mask_inner = mask(Rect(1, 1, mask.cols - 2, mask.rows - 2));
copyMakeBorder(mask_inner, mask, 1, 1, 1, 1, BORDER_ISOLATED | BORDER_CONSTANT, Scalar(0));
Rect roi = cv::boundingRect(mask)
Point p = Point(int(roi.x + roi.width / 2), int(roi.y + roi.height / 2))
cv::seamlessClone(src_bgr, dst_bgr, mask, p, out_img, cv::NORMAL_CLONE)

其他

在OpenCV的泊松融合使用时,当目标图像特别大的情况下,如果mask不经过和cv::seamlessClone函数内部一致的边界处理,在某些情况下则可能会报如下错误:

terminate called after throwing an instance of 'std::length_error'what():  vector::_M_default_append
Aborted (core dumped)

在这里插入图片描述
在这里插入图片描述
对mask边界进行白边处理能够解决这一问题。

在这里插入图片描述

参考资料

[1] 泊松融合原理浅析
[2] OpenCV源码解读:泊松融合seamlessClone(normalClone)
[3] opencv/modules/photo/src/seamless_cloning.cpp
[4] 图像融合之泊松融合(Possion Matting)
[5] terminate called after throwing an instance of ‘std::length_error’
[6] seamlessClone bug #15294
[7] bug on bounds in OpenCV cv2.seamlessClone
[8] OpenCV cv2.seamlessClone中的错误
[9] 使用FFmpeg生成高清gif图

这篇关于OpenCV版泊松融合seamlessClone融合结果1像素偏差问题记录与分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/922139

相关文章

Python获取中国节假日数据记录入JSON文件

《Python获取中国节假日数据记录入JSON文件》项目系统内置的日历应用为了提升用户体验,特别设置了在调休日期显示“休”的UI图标功能,那么问题是这些调休数据从哪里来呢?我尝试一种更为智能的方法:P... 目录节假日数据获取存入jsON文件节假日数据读取封装完整代码项目系统内置的日历应用为了提升用户体验,

Go标准库常见错误分析和解决办法

《Go标准库常见错误分析和解决办法》Go语言的标准库为开发者提供了丰富且高效的工具,涵盖了从网络编程到文件操作等各个方面,然而,标准库虽好,使用不当却可能适得其反,正所谓工欲善其事,必先利其器,本文将... 目录1. 使用了错误的time.Duration2. time.After导致的内存泄漏3. jsO

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

Spring Boot 配置文件之类型、加载顺序与最佳实践记录

《SpringBoot配置文件之类型、加载顺序与最佳实践记录》SpringBoot的配置文件是灵活且强大的工具,通过合理的配置管理,可以让应用开发和部署更加高效,无论是简单的属性配置,还是复杂... 目录Spring Boot 配置文件详解一、Spring Boot 配置文件类型1.1 applicatio

opencv图像处理之指纹验证的实现

《opencv图像处理之指纹验证的实现》本文主要介绍了opencv图像处理之指纹验证的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录一、简介二、具体案例实现1. 图像显示函数2. 指纹验证函数3. 主函数4、运行结果三、总结一、

Spring事务中@Transactional注解不生效的原因分析与解决

《Spring事务中@Transactional注解不生效的原因分析与解决》在Spring框架中,@Transactional注解是管理数据库事务的核心方式,本文将深入分析事务自调用的底层原理,解释为... 目录1. 引言2. 事务自调用问题重现2.1 示例代码2.2 问题现象3. 为什么事务自调用会失效3

MySQL INSERT语句实现当记录不存在时插入的几种方法

《MySQLINSERT语句实现当记录不存在时插入的几种方法》MySQL的INSERT语句是用于向数据库表中插入新记录的关键命令,下面:本文主要介绍MySQLINSERT语句实现当记录不存在时... 目录使用 INSERT IGNORE使用 ON DUPLICATE KEY UPDATE使用 REPLACE

python+opencv处理颜色之将目标颜色转换实例代码

《python+opencv处理颜色之将目标颜色转换实例代码》OpenCV是一个的跨平台计算机视觉库,可以运行在Linux、Windows和MacOS操作系统上,:本文主要介绍python+ope... 目录下面是代码+ 效果 + 解释转HSV: 关于颜色总是要转HSV的掩膜再标注总结 目标:将红色的部分滤

SpringBoot启动报错的11个高频问题排查与解决终极指南

《SpringBoot启动报错的11个高频问题排查与解决终极指南》这篇文章主要为大家详细介绍了SpringBoot启动报错的11个高频问题的排查与解决,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一... 目录1. 依赖冲突:NoSuchMethodError 的终极解法2. Bean注入失败:No qu

Python 中的异步与同步深度解析(实践记录)

《Python中的异步与同步深度解析(实践记录)》在Python编程世界里,异步和同步的概念是理解程序执行流程和性能优化的关键,这篇文章将带你深入了解它们的差异,以及阻塞和非阻塞的特性,同时通过实际... 目录python中的异步与同步:深度解析与实践异步与同步的定义异步同步阻塞与非阻塞的概念阻塞非阻塞同步