OpenCV 像素操作—证件照换底色详细原理 C++纯手写实现
文章目录
- 总体步骤
- 1.RGB转HSV
- 2.找出要换的底色
- 3.取反,黑白颠倒
- 4.将原图像的非背景部分复制到新背景上
- 完整代码
- 1.C++纯手写版
- 2.官方API版本
总体步骤
1.RGB转HSV
为什么一定要转为HSV 颜色空间?
将图像从BGR颜色空间转换为HSV颜色空间是因为HSV颜色空间更适合进行颜色分割和提取操作。这是因为HSV颜色空间将颜色表示为色相(Hue)、饱和度(Saturation)和明度(Value),这使得颜色的分离更加直观和有效。具体原因如下:
色相(Hue)分离:
- 在HSV颜色空间中,色相(H)直接表示颜色的种类,例如红色、绿色、蓝色等。通过指定色相范围,可以非常容易地分离出特定的颜色。例如,**绿色的色相范围通常集中在一个较小的区间内,这使得提取绿色对象变得简单而精确。**这也是为什么特效制作的时候需要使用绿色幕布,更加方便提取图像。
饱和度(Saturation)和明度(Value)独立:
- 饱和度表示颜色的纯度,明度表示颜色的亮度。在BGR颜色空间中,这些属性是混合在一起的,不容易分离。但是在HSV颜色空间中,饱和度和明度是独立的,可以单独进行调节和阈值化,从而实现更好的颜色分割效果。
处理光照变化:
- HSV颜色空间对于光照变化更为鲁棒。由于明度和饱和度是独立的,即使在光照发生变化时,色相也相对稳定,使得颜色分割在不同光照条件下表现更好。
HSV范围取色表
例如在H:35 -77 S:43-255 V:46-255 之间的可以认为是绿色。 有了这张图参考,我们能够更容易地过滤掉某种颜色。
2.找出要换的底色
以将一张蓝底的证件照换成红底证件照为例:
转为HSV颜色空间之后,我们要找出蓝色的底色所在区域。可以通过遍历像素的方式实现
// 根据范围创建 掩码图像 二值化图像
void customInRange(const Mat& src, Scalar lowerBound, Scalar upperBound, Mat& dst) {// 创建与源图像大小相同的掩码图像dst = Mat::zeros(src.size(), CV_8UC1);// 遍历图像的每个像素for (int y = 0; y < src.rows; y++) {for (int x = 0; x < src.cols; x++) {Vec3b pixel = src.at<Vec3b>(y, x);bool inRange = true;// 检查像素值是否在指定范围内 通过HSV范围表来指定for (int c = 0; c < 3; c++) {if (pixel[c] < lowerBound[c] || pixel[c] > upperBound[c]) {inRange = false;break;}}// 如果在范围内,设置掩码图像中的对应位置为255if (inRange) {dst.at<uchar>(y, x) = 255;}}}
}
这里涉及到一个新概念 掩码图像
掩码(mask)在计算机视觉和图像处理领域中,是一种用于选择、过滤或操作图像中特定部分的工具。掩码本质上是一张与原图像大小相同的二值图像,其中每个像素要么是0,要么是1(在OpenCV中通常用0和255来表示)。掩码图像中值为1(或255)的部分表示感兴趣的区域(ROI,Region of Interest),而值为0的部分则表示不感兴趣的区域。
掩码的具体作用可以包括以下几种:
- 区域选择:
- 通过掩码,可以选择图像中的某个特定区域进行操作,而忽略其他部分。例如,只对图像中的某个颜色范围进行处理。
- 图像分割:
- 掩码可以用于将图像分割成前景和背景。前景是感兴趣的部分,背景则是其他部分。
- 图像修复:
- 掩码可以用于图像修复,指定需要修复的区域。
- 遮罩操作:
- 在图像合成时,掩码可以用作遮罩,控制哪些部分需要叠加或混合。
上述代码通过遍历可以找到蓝色底色的区域,并置为白色。其余的全部设置为黑色。
代码运行效果:(中间的白点,因为胖虎穿的偏蓝色领带,因此过滤出了一部分)
此时,基本上已经找出原来底色的区域,已经全部置成了白色。
3.取反,黑白颠倒
主要目的是找到除了背景色的其他元素,这一步是选择 非背景色的元素 置为白色,因为除了背景色,其他像素均要移到新的背景色上去, 非背景色成为了ROI(感兴趣区域),背景色置为黑色,代表不再关注原来的背景色。遍历每个像素,用255减去原来的像素便可反转颜色,黑白颠倒。
// bitwise_not(mask,mask);for (int y = 0; y < mask.rows; ++y) {for (int x = 0; x < mask.cols; ++x) {//对每个像素进行取反mask.at<uchar>(y, x) = 255 - mask.at<uchar>(y, x);}}
效果如图:
4.将原图像的非背景部分复制到新背景上
//将非背景色像素移到事先准备好的背景上
void customCopyTo(const Mat& src, Mat& dst, const Mat& mask) {// 确保源图像和掩码图像的大小一致if (src.size() != dst.size() || src.size() != mask.size()) {throw std::invalid_argument("Size of src, dst, and mask must be the same");}// 遍历图像的每个像素for (int y = 0; y < src.rows; y++) {for (int x = 0; x < src.cols; x++) {// 如果掩码中的值为非零(通常是255),则复制源图像的像素到目标图if (mask.at<uchar>(y, x) != 0) {dst.at<Vec3b>(y, x) = src.at<Vec3b>(y, x);}}}
}
遍历像素,与掩码图像对比,如果不是黑色,说明是ROI区域,将像素替换到准备好的纯色背景上面。就完成了背景换色
效果如下:
完整代码
1.C++纯手写版
//将非背景色像素移到事先准备好的背景上
void customCopyTo(const Mat& src, Mat& dst, const Mat& mask) {// 确保源图像和掩码图像的大小一致if (src.size() != dst.size() || src.size() != mask.size()) {throw std::invalid_argument("Size of src, dst, and mask must be the same");}// 遍历图像的每个像素for (int y = 0; y < src.rows; y++) {for (int x = 0; x < src.cols; x++) {// 如果掩码中的值为非零(通常是255),则复制源图像的像素到目标图像if (mask.at<uchar>(y, x) != 0) {dst.at<Vec3b>(y, x) = src.at<Vec3b>(y, x);}}}
}
// 根据范围创建 掩码图像 二值化图像
void customInRange(const Mat& src, Scalar lowerBound, Scalar upperBound, Mat& dst) {// 创建与源图像大小相同的掩码图像dst = Mat::zeros(src.size(), CV_8UC1);// 遍历图像的每个像素for (int y = 0; y < src.rows; y++) {for (int x = 0; x < src.cols; x++) {Vec3b pixel = src.at<Vec3b>(y, x);bool inRange = true;// 检查像素值是否在指定范围内for (int c = 0; c < 3; c++) {if (pixel[c] < lowerBound[c] || pixel[c] > upperBound[c]) {inRange = false;break;}}// 如果在范围内,设置掩码图像中的对应位置为255if (inRange) {dst.at<uchar>(y, x) = 255;}}}
}void inRange_dmeo(Mat &image){Mat hsv;//先将图片转为HSVcvtColor(image,hsv,COLOR_BGR2HSV);// 提取左上角像素的HSV值 ,也就是对应底色的值 有利于更好控制提取Vec3b hsvValue = hsv.at<Vec3b>(0, 0);cout<<hsvValue<<endl;Mat mask;//设置 下限 和上限根据设定的HSV颜色范围 生成一个二值化的掩码(mask),掩码中白色部分表示图像中符合设定颜色范围的部分,黑色部分表示不符合的部分。customInRange(hsv,Scalar(100,43,46),Scalar(115,225,227),mask);imshow("mask",mask);//准备好一个背景颜色 以红色为例Mat red_background = Mat::zeros(image.size(),image.type());red_background = Scalar (40,40,200);//对掩码取反, 黑白颠倒,选择除了背景色的区域 ,也就是欲复制的区域
// bitwise_not(mask,mask);for (int y = 0; y < mask.rows; ++y) {for (int x = 0; x < mask.cols; ++x) {//对每个像素进行取反mask.at<uchar>(y, x) = 255 - mask.at<uchar>(y, x);}}imshow("bitwise_not mask",mask);//使用掩码(mask)将输入图像中符合条件的部分复制到红色背景图像上customCopyTo(image,red_background,mask);imshow("roi",red_background);
}
2.官方API版本
OpenCV提供了生成掩码,掩码取反,复制非背景色元素到新背景的API 如下,
inRange 根据HSV范围 生成掩码
bitwise_not 掩码取反
copyTo 根据掩码复制到新背景色
void inRange_dmeo(Mat &image){Mat hsv;//先将图片转为HSVcvtColor(image,hsv,COLOR_BGR2HSV);// 提取左上角像素的HSV值 ,也就是对应底色的值 有利于更好控制提取Vec3b hsvValue = hsv.at<Vec3b>(0, 0);cout<<hsvValue<<endl;Mat mask;//设置 下限 和上限根据设定的HSV颜色范围 生成一个二值化的掩码(mask),掩码中白色部分表示图像中符合设定颜色范围的部分,黑色部分表示不符合的部分。inRange(hsv,Scalar(100,43,46),Scalar(115,225,227),mask);imshow("mask",mask);//准备好一个背景颜色 以红色为例Mat red_background = Mat::zeros(image.size(),image.type());red_background = Scalar (40,40,200);//对掩码取反, 黑白颠倒,选择除了背景色的区域 ,也就是欲复制的区域bitwise_not(mask,mask);imshow("bitwise_not mask",mask);//使用掩码(mask)将输入图像中符合条件的部分复制到红色背景图像上image.copyTo(red_background,mask);imshow("roi",red_background);
}
相关文章:

OpenCV 像素操作—证件照换底色详细原理 C++纯手写实现
文章目录 总体步骤1.RGB转HSV2.找出要换的底色3.取反,黑白颠倒4.将原图像的非背景部分复制到新背景上 完整代码1.C纯手写版2.官方API版本 总体步骤 1.RGB转HSV 为什么一定要转为HSV 颜色空间? 将图像从BGR颜色空间转换为HSV颜色空间是因为HSV颜色空间更…...

tinygrad框架简介;MLX框架简介
目录 tinygrad框架简介 MLX框架简介 LLaMA编辑 Stable Diffusion编辑 tinygrad框架简介 极简主义与易扩展性 tinygrad 的设计理念是极简主义。与 XLA 类比,如果 XLA 是复杂指令集计算 (CISC),那么 tinygrad 就是精简指令集计算 (RISC)。这种简约的设计使得它成为添加…...

服务器重启了之后就卡在某个页面了,花屏,如何解决??
🏆本文收录于《CSDN问答解惑-专业版》专栏,主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收…...

Hospital 14.6.0全开源医院管理预约系统源码
InfyHMS 具有 60 种功能和 9 种不同类型的用户类型, 他们可以登录系统并根据他们的角色访问他们的数据。 源码下载:https://download.csdn.net/download/m0_66047725/89580674 更多资源下载:关注我。...

C/C++樱花树代码
目录 写在前面 系列文章 C简介 完整代码 代码分析 写在后面 写在前面 C实现精美的樱花树,只需这100行代码! 系列文章 序号目录直达链接1爱心代码https://want595.blog.csdn.net/article/details/1363606842李峋同款跳动的爱心https://want595.b…...
sklearn基础学习
1. 简介 1.1 什么是sklearn sklearn,或者更正式地称为scikit-learn,是一个基于Python的开源机器学习库。它建立在NumPy、SciPy和matplotlib之上,提供了简单而有效的工具用于数据挖掘和数据分析。sklearn支持监督学习和无监督学习算法&#…...

SpringBoot 自动配置原理
一、Condition Condition 是在 Spring 4.0 增加的条件判断功能,通过这个可以功能可以实现选择性的创建 Bean 操 作。 思考: SpringBoot 是如何知道要创建哪个 Bean 的?比如 SpringBoot 是如何知道要创建 RedisTemplate 的? …...
Redisson中RQueue的使用场景附一个异步的例子
RQueue 是一个基于 Redis 的分布式作业队列系统,它允许开发者在 Ruby 应用程序中实现异步任务处理和计划任务调度。由于 Redis 提供了高性能的内存数据结构存储,RQueue 可以快速地存储和检索队列中的任务,这使得它非常适合于高并发和低延迟的…...

SpringMVC 控制层框架-下
五、SpringMVC其他扩展 1. 异常处理机制 1.1 异常处理概念 开发过程中是不可避免地会出现各种异常情况,例如网络连接异常、数据格式异常、空指针异常等等。异常的出现可能导致程序的运行出现问题,甚至直接导致程序崩溃。因此,在开发过程中&a…...
(四)js前端开发中设计模式之工厂方法模式
工厂方法模式,通过对产品类的抽象,使其创建业务主要用于负责创建多类产品的实例 const Java function (content) {this.content content;(function () {let oDiv document.createElement(div)oDiv.innerHTML contentoDiv.style.color greendocument.getElement…...

新版GPT-4omini上线!快!真TM快!
大半夜,OpenAI突然推出了GPT-4o mini版本。 当我看到这条消息时,正准备去睡觉。mini版本质上是GPT-4o模型的精简版本,没有什么革命性的创新,因此我并没有太在意。 结果今天早上一觉醒来发现伴随GPT-4o mini上线,官网和…...
【Unity】RPG2D龙城纷争(十七)敌方常规AI(Normal)的实现
更新日期:2024年7月24日。 项目源码:第五章发布(正式开始游戏逻辑的章节) 索引 简介一、AI_Normal类二、AI调遣策略第一阶段:收集1.提供战场数据收集方法2.收集战场数据三、AI调遣策略第二阶段:评估四、AI调遣策略第三阶段:行动简介 AI_Normal定位为框架自带的最基础的…...
Tracy 小笔记:微信小程序 mpx 雷达图的实现
使用文档: https://www.kancloud.cn/xchhhh/wx-chart/399337 https://github.com/xiaolin3303/wx-charts https://gitee.com/mirrors/wx-charts/#wx-charts 参数说明: https://github.com/xiaolin3303/wx-charts/issues/56 下载 dist 里的 wx-charts-…...

Unity UGUI 之 Input Field
本文仅作学习笔记与交流,不作任何商业用途 本文包括但不限于unity官方手册,唐老狮,麦扣教程知识,引用会标记,如有不足还请斧正 1.Input Field是什么? 给玩家提供输入的输入框 2.重要参数 中英文对照着看…...
SpringBoot接入mongodb例子,并有增删改查功能
1,首先,在pom.xml中添加依赖: <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId></dependency><!--上面这…...

类和对象(三)
目录 一. 构造函数初始化列表 二. 类型转换 三. static成员 四. 友元 五. 内部类 六. 匿名对象 七. 对象拷贝时的编译器优化 一. 构造函数初始化列表 1. 之前我们实现构造函数时,初始化成员变量主要使用函数体内赋值,构造函数初始化还有一种方式&…...
Android SurfaceFlinger——GraphicBuffer初始化(二十九)
在 SurfaceFlinger 中,GraphicBuffer 是一个关键的数据结构,用于封装和管理图形数据的内存缓冲区。它不仅在 SurfaceFlinger 内部使用,也被其他组件如 GPU 驱动、摄像头服务、视频解码器等广泛利用,以实现高效的数据交换和图形渲染。 一、概述 GraphicBuffer 对象封装了一…...

pytest:4种方法实现 - 重复执行用例 - 展示迭代次数
简介:在软件测试中,我们经常需要重复执行测试用例,以确保代码的稳定性和可靠性。在本文中,我们将介绍四种方法来实现重复执行测试用例,并显示当前迭代次数和剩余执行次数。这些方法将帮助你更好地追踪测试执行过程&…...

一文入门SpringSecurity 5
目录 提示 Apache Shiro和Spring Security 认证和授权 RBAC Demo 环境 Controller 引入Spring Security 初探Security原理 认证授权图示编辑 图中涉及的类和接口 流程总结 提示 Spring Security源码的接口名和方法名都很长,看源码的时候要见名知意&am…...
IPython的HTML魔法:%%html_header命令全解析
IPython的HTML魔法:%%html_header命令全解析 在IPython和Jupyter Notebook中,%%html_header是一个魔术命令,它允许用户在Notebook的单元格中添加HTML头部(head)内容。这个功能特别有用,当你需要定制Notebo…...
Java 语言特性(面试系列2)
一、SQL 基础 1. 复杂查询 (1)连接查询(JOIN) 内连接(INNER JOIN):返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...

黑马Mybatis
Mybatis 表现层:页面展示 业务层:逻辑处理 持久层:持久数据化保存 在这里插入图片描述 Mybatis快速入门  {if (numFriends 1) {return word;}string res;for (int i 0;i &…...
稳定币的深度剖析与展望
一、引言 在当今数字化浪潮席卷全球的时代,加密货币作为一种新兴的金融现象,正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而,加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下,稳定…...

什么是VR全景技术
VR全景技术,全称为虚拟现实全景技术,是通过计算机图像模拟生成三维空间中的虚拟世界,使用户能够在该虚拟世界中进行全方位、无死角的观察和交互的技术。VR全景技术模拟人在真实空间中的视觉体验,结合图文、3D、音视频等多媒体元素…...

DeepSeek源码深度解析 × 华为仓颉语言编程精粹——从MoE架构到全场景开发生态
前言 在人工智能技术飞速发展的今天,深度学习与大模型技术已成为推动行业变革的核心驱动力,而高效、灵活的开发工具与编程语言则为技术创新提供了重要支撑。本书以两大前沿技术领域为核心,系统性地呈现了两部深度技术著作的精华:…...

【深度学习新浪潮】什么是credit assignment problem?
Credit Assignment Problem(信用分配问题) 是机器学习,尤其是强化学习(RL)中的核心挑战之一,指的是如何将最终的奖励或惩罚准确地分配给导致该结果的各个中间动作或决策。在序列决策任务中,智能体执行一系列动作后获得一个最终奖励,但每个动作对最终结果的贡献程度往往…...
面试高频问题
文章目录 🚀 消息队列核心技术揭秘:从入门到秒杀面试官1️⃣ Kafka为何能"吞云吐雾"?性能背后的秘密1.1 顺序写入与零拷贝:性能的双引擎1.2 分区并行:数据的"八车道高速公路"1.3 页缓存与批量处理…...
React从基础入门到高级实战:React 实战项目 - 项目五:微前端与模块化架构
React 实战项目:微前端与模块化架构 欢迎来到 React 开发教程专栏 的第 30 篇!在前 29 篇文章中,我们从 React 的基础概念逐步深入到高级技巧,涵盖了组件设计、状态管理、路由配置、性能优化和企业级应用等核心内容。这一次&…...