当前位置: 首页 > news >正文

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的部分则表示不感兴趣的区域。

掩码的具体作用可以包括以下几种:

  1. 区域选择
    • 通过掩码,可以选择图像中的某个特定区域进行操作,而忽略其他部分。例如,只对图像中的某个颜色范围进行处理。
  2. 图像分割
    • 掩码可以用于将图像分割成前景和背景。前景是感兴趣的部分,背景则是其他部分。
  3. 图像修复
    • 掩码可以用于图像修复,指定需要修复的区域。
  4. 遮罩操作
    • 在图像合成时,掩码可以用作遮罩,控制哪些部分需要叠加或混合。

上述代码通过遍历可以找到蓝色底色的区域,并置为白色。其余的全部设置为黑色。

代码运行效果:(中间的白点,因为胖虎穿的偏蓝色领带,因此过滤出了一部分)

在这里插入图片描述

此时,基本上已经找出原来底色的区域,已经全部置成了白色。

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.取反&#xff0c;黑白颠倒4.将原图像的非背景部分复制到新背景上 完整代码1.C纯手写版2.官方API版本 总体步骤 1.RGB转HSV 为什么一定要转为HSV 颜色空间&#xff1f; 将图像从BGR颜色空间转换为HSV颜色空间是因为HSV颜色空间更…...

tinygrad框架简介;MLX框架简介

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

服务器重启了之后就卡在某个页面了,花屏,如何解决??

&#x1f3c6;本文收录于《CSDN问答解惑-专业版》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收…...

Hospital 14.6.0全开源医院管理预约系统源码

InfyHMS 具有 60 种功能和 9 种不同类型的用户类型&#xff0c; 他们可以登录系统并根据他们的角色访问他们的数据。 源码下载&#xff1a;https://download.csdn.net/download/m0_66047725/89580674 更多资源下载&#xff1a;关注我。...

C/C++樱花树代码

目录 写在前面 系列文章 C简介 完整代码 代码分析 写在后面 写在前面 C实现精美的樱花树&#xff0c;只需这100行代码&#xff01; 系列文章 序号目录直达链接1爱心代码https://want595.blog.csdn.net/article/details/1363606842李峋同款跳动的爱心https://want595.b…...

sklearn基础学习

1. 简介 1.1 什么是sklearn sklearn&#xff0c;或者更正式地称为scikit-learn&#xff0c;是一个基于Python的开源机器学习库。它建立在NumPy、SciPy和matplotlib之上&#xff0c;提供了简单而有效的工具用于数据挖掘和数据分析。sklearn支持监督学习和无监督学习算法&#…...

SpringBoot 自动配置原理

一、Condition Condition 是在 Spring 4.0 增加的条件判断功能&#xff0c;通过这个可以功能可以实现选择性的创建 Bean 操 作。 思考&#xff1a; SpringBoot 是如何知道要创建哪个 Bean 的&#xff1f;比如 SpringBoot 是如何知道要创建 RedisTemplate 的&#xff1f; …...

Redisson中RQueue的使用场景附一个异步的例子

RQueue 是一个基于 Redis 的分布式作业队列系统&#xff0c;它允许开发者在 Ruby 应用程序中实现异步任务处理和计划任务调度。由于 Redis 提供了高性能的内存数据结构存储&#xff0c;RQueue 可以快速地存储和检索队列中的任务&#xff0c;这使得它非常适合于高并发和低延迟的…...

SpringMVC 控制层框架-下

五、SpringMVC其他扩展 1. 异常处理机制 1.1 异常处理概念 开发过程中是不可避免地会出现各种异常情况&#xff0c;例如网络连接异常、数据格式异常、空指针异常等等。异常的出现可能导致程序的运行出现问题&#xff0c;甚至直接导致程序崩溃。因此&#xff0c;在开发过程中&a…...

(四)js前端开发中设计模式之工厂方法模式

工厂方法模式,通过对产品类的抽象&#xff0c;使其创建业务主要用于负责创建多类产品的实例 const Java function (content) {this.content content;(function () {let oDiv document.createElement(div)oDiv.innerHTML contentoDiv.style.color greendocument.getElement…...

新版GPT-4omini上线!快!真TM快!

大半夜&#xff0c;OpenAI突然推出了GPT-4o mini版本。 当我看到这条消息时&#xff0c;正准备去睡觉。mini版本质上是GPT-4o模型的精简版本&#xff0c;没有什么革命性的创新&#xff0c;因此我并没有太在意。 结果今天早上一觉醒来发现伴随GPT-4o mini上线&#xff0c;官网和…...

【Unity】RPG2D龙城纷争(十七)敌方常规AI(Normal)的实现

更新日期:2024年7月24日。 项目源码:第五章发布(正式开始游戏逻辑的章节) 索引 简介一、AI_Normal类二、AI调遣策略第一阶段:收集1.提供战场数据收集方法2.收集战场数据三、AI调遣策略第二阶段:评估四、AI调遣策略第三阶段:行动简介 AI_Normal定位为框架自带的最基础的…...

Tracy 小笔记:微信小程序 mpx 雷达图的实现

使用文档&#xff1a; https://www.kancloud.cn/xchhhh/wx-chart/399337 https://github.com/xiaolin3303/wx-charts https://gitee.com/mirrors/wx-charts/#wx-charts 参数说明&#xff1a; https://github.com/xiaolin3303/wx-charts/issues/56 下载 dist 里的 wx-charts-…...

Unity UGUI 之 Input Field

本文仅作学习笔记与交流&#xff0c;不作任何商业用途 本文包括但不限于unity官方手册&#xff0c;唐老狮&#xff0c;麦扣教程知识&#xff0c;引用会标记&#xff0c;如有不足还请斧正 1.Input Field是什么&#xff1f; 给玩家提供输入的输入框 2.重要参数 中英文对照着看…...

SpringBoot接入mongodb例子,并有增删改查功能

1&#xff0c;首先&#xff0c;在pom.xml中添加依赖&#xff1a; <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId></dependency><!--上面这…...

类和对象(三)

目录 一. 构造函数初始化列表 二. 类型转换 三. static成员 四. 友元 五. 内部类 六. 匿名对象 七. 对象拷贝时的编译器优化 一. 构造函数初始化列表 1. 之前我们实现构造函数时&#xff0c;初始化成员变量主要使用函数体内赋值&#xff0c;构造函数初始化还有一种方式&…...

Android SurfaceFlinger——GraphicBuffer初始化(二十九)

在 SurfaceFlinger 中,GraphicBuffer 是一个关键的数据结构,用于封装和管理图形数据的内存缓冲区。它不仅在 SurfaceFlinger 内部使用,也被其他组件如 GPU 驱动、摄像头服务、视频解码器等广泛利用,以实现高效的数据交换和图形渲染。 一、概述 GraphicBuffer 对象封装了一…...

pytest:4种方法实现 - 重复执行用例 - 展示迭代次数

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

一文入门SpringSecurity 5

目录 提示 Apache Shiro和Spring Security 认证和授权 RBAC Demo 环境 Controller 引入Spring Security 初探Security原理 认证授权图示​编辑 图中涉及的类和接口 流程总结 提示 Spring Security源码的接口名和方法名都很长&#xff0c;看源码的时候要见名知意&am…...

IPython的HTML魔法:%%html_header命令全解析

IPython的HTML魔法&#xff1a;%%html_header命令全解析 在IPython和Jupyter Notebook中&#xff0c;%%html_header是一个魔术命令&#xff0c;它允许用户在Notebook的单元格中添加HTML头部&#xff08;head&#xff09;内容。这个功能特别有用&#xff0c;当你需要定制Notebo…...

TDengine 快速体验(Docker 镜像方式)

简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能&#xff0c;本节首先介绍如何通过 Docker 快速体验 TDengine&#xff0c;然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker&#xff0c;请使用 安装包的方式快…...

利用ngx_stream_return_module构建简易 TCP/UDP 响应网关

一、模块概述 ngx_stream_return_module 提供了一个极简的指令&#xff1a; return <value>;在收到客户端连接后&#xff0c;立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量&#xff08;如 $time_iso8601、$remote_addr 等&#xff09;&a…...

逻辑回归:给不确定性划界的分类大师

想象你是一名医生。面对患者的检查报告&#xff08;肿瘤大小、血液指标&#xff09;&#xff0c;你需要做出一个**决定性判断**&#xff1a;恶性还是良性&#xff1f;这种“非黑即白”的抉择&#xff0c;正是**逻辑回归&#xff08;Logistic Regression&#xff09;** 的战场&a…...

在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:

在 HarmonyOS 应用开发中&#xff0c;手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力&#xff0c;既支持点击、长按、拖拽等基础单一手势的精细控制&#xff0c;也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档&#xff0c…...

Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; 目前2025年06月05日更新到&#xff1a; AI炼丹日志-28 - Aud…...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)

骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术&#xff0c;它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton)&#xff1a;由层级结构的骨头组成&#xff0c;类似于人体骨骼蒙皮 (Mesh Skinning)&#xff1a;将模型网格顶点绑定到骨骼上&#xff0c;使骨骼移动…...

聊一聊接口测试的意义有哪些?

目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开&#xff0c;首…...

Rapidio门铃消息FIFO溢出机制

关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系&#xff0c;以下是深入解析&#xff1a; 门铃FIFO溢出的本质 在RapidIO系统中&#xff0c;门铃消息FIFO是硬件控制器内部的缓冲区&#xff0c;用于临时存储接收到的门铃消息&#xff08;Doorbell Message&#xff09;。…...

网站指纹识别

网站指纹识别 网站的最基本组成&#xff1a;服务器&#xff08;操作系统&#xff09;、中间件&#xff08;web容器&#xff09;、脚本语言、数据厍 为什么要了解这些&#xff1f;举个例子&#xff1a;发现了一个文件读取漏洞&#xff0c;我们需要读/etc/passwd&#xff0c;如…...

处理vxe-table 表尾数据是单独一个接口,表格tableData数据更新后,需要点击两下,表尾才是正确的

修改bug思路&#xff1a; 分别把 tabledata 和 表尾相关数据 console.log() 发现 更新数据先后顺序不对 settimeout延迟查询表格接口 ——测试可行 升级↑&#xff1a;async await 等接口返回后再开始下一个接口查询 ________________________________________________________…...