iOS 隐形水印之 LSB 实现
Python实战社群
Java实战社群
长按识别下方二维码,按需求添加
扫码关注添加客服
进Python社群▲
扫码关注添加客服
进Java社群▲
作者丨千帆直播-陈奇辉
来源丨搜狐技术产品(sohu-tech)
在音视频的领域里,其涵盖的知识点繁多,学习方向也很多。
而本篇就是一篇比较入门的文章
它简单地介绍如何在 iOS 上读取图片 RGB 数据,并通过修改最后一位 bit 来记录数字水印的信息
下面就介绍《隐形水印之 iOS 实现》
欢迎阅读
技术
背景
本文实现的两个技术点
水 印
水印是经常出现在图片 & 视频等多媒体文件上是一种很常见的元素,它一般以单一的图片或者文字的形式显示在一个角落。
它主要用于声明版权 & 防盗,以及查看来源等。
而我们千帆直播在主播直播后,我们的后端会为生成的 点播 & 回放 打上「千帆直播」水印,如下图的右上角:
compare
这种水印是可见的,添加水印的方式可以是直接将水印图片的原数据memcpy 到载体图片原数据上(这里只是简单描述啦,对齐这些各位自己了解下)。还有可以使用第三方库,如 ffmpeg等。
L S B
英文 least significant bit,中文译最低有效位。
用个例子来描述下,我们知道一个数字,如11,把它转换为二进制的话就是 0000 1011,取最右边的 1 即为最低位。
而本文是针对图片的,图片的原数据构成是 RBG,那我们的操作就是:
(255, 255, 255) -取255-> 1111 1111 -> 把最后一位(1 => 0) -> 1111 1110 -放回-> (254, 255, 255)
这也是本文实现的核心。
而上面的操作会让原本的白色在 R值 上变小,然后再转换回颜色上,人眼是很难看出修改之后与之前的差别。
因此我们这样的修改对原本的影响是很小的。
这就是我们通过获取 LSB,修改它来实现本次技术点。
数字
水印
结合我们上面说的两个技术点,就是我们的实现了。
这里介绍数字水印,一种不易被发现的水印,也可以叫它,隐形水印。顾名思义,它不会像一般的水印一样显示在图片 & 视频帧上,而是通过其他技术原理将信息附带在载体上,最后通过反计算来获取信息内容。
数字水印的实现其实不止 LSB,还有其他改进的 LSB,或者其他算法,本文使用 标准LSB 来实现。
但算法的大致都是建立在对原数据的修改不会在视觉上被察觉,将信息加入到原数据里面,有点像藏头诗。
通过以上大概了解原理 & 技术背景之后,本篇将着重使用 标准的LSB 来为 iOS 的截图上添加隐形水印 & 获取隐形水印内容。
应用
场景
在Demo App上通过系统的截图后,将已经加入隐形水印的 App 截图展示,通过直接分享到QQ或者保存到相册,再由相册分享到QQ,然后客服获取到图片之后将图片保存或者直接复制,转发给处理人员。
然后通过相应的工具或者App将图片的隐形水印信息读取出来。
至于信息内容,可以是当时用户操作App的点击行为,或者部分接口的响应结果,从而来协助定位、追踪问题,等等。
QFImageMaskMan
这里的是整个Demo & Demo的操作展示,我在iPhone截图上将一首诗加入进去,然后分享出去或者保存本地。
QFImageMaskDemo[1]????见文末
-Demo-
实 现
static let markBin: [QFLSBMarkBin] = [.key, .lengthBit, .length, .info]
读者看完Demo之后尝试添加 version 记录
加 密 头
字段 |
说明 |
内容 |
key |
标识 |
“QianFan”(本库使用) |
lengthBit |
记录使⽤的 length |
1 byte |
length |
全部信息的总长度,如果减去固定的信息头长度就是加密的信息⻓度 |
2 bytes |
加 密 体
加密头 |
加密内容 |
key.cout + 1 + 2 |
length - (key.cout + 1 + 2) |
encode & decode
都是通过CGContext获取位图数据,encode3使用了CVPixelBuffer,方便后续应用到视频帧,然后对 data[n] 二进制化的尾数进行比较 & 操作。
class func baseEncode(data: UnsafeMutablePointer<UInt8>, infoData: Data, width w: Int, height h: Int) { let kQHKeyData = kQFkey.data(using: .utf8)! let kQHKeyDataCount = kQHKeyData.count let kLenghtCount = kLengthBit let markCount = kQHKeyDataCount + kLengthBitCount + kLenghtCount + infoData.count var bStop = false var markIndex = -1 var markIndexText: Int = 0 var markBin: QFLSBMarkBin? var markLengthBitCount = 0 // 1、读取原数据 for y in 0..<h { if bStop { break; } for x in 0..<w { let index = (y * w + x) let markIndexTemp = index / 8 if markIndexTemp >= markCount { bStop = true break }// 2、水印信息的计算 if markIndex != markIndexTemp { markIndex = markIndexTemp if markIndex < kQHKeyDataCount { markBin = .key markIndexText = Int(kQHKeyData[markIndex]) } else if markIndex < kQHKeyDataCount + kLengthBitCount { markBin = .lengthBit markIndexText = kLengthBit } else if markIndex < kQHKeyDataCount + kLengthBitCount + kLenghtCount { markLengthBitCount += 1 markBin = .length if markLengthBitCount == 1 { markIndexText = markCount%256 } else if markLengthBitCount == 2 { markIndexText = Int(markCount>>8) } else { bStop = true break } } else if markIndex < markCount { markBin = .info markIndexText = Int(infoData[markIndex - (kQHKeyDataCount + kLengthBitCount + kLenghtCount)]) } } if markBin == nil { bStop = true break } // 3、读取原数据 & 获取最低有效位 let offset = 4 * index let red = data[offset+1] let redBinary = red % 2 let markIndexTextBinaryIndex = index % 8 let markIndexTextBinary = Int((markIndexText / (1<<Int(markIndexTextBinaryIndex))) % 2) // 4、最低有效位记录 if redBinary != markIndexTextBinary { if markIndexTextBinary == 0 { if red == 255 { data[offset+1] = 254 } else { data[offset+1] = red + 1 } } else { if red == 0 { data[offset+1] = 1 } else { data[offset+1] = red - 1 } } } } }}
// 逻辑上与 baseEncode 相同,读取原数据后记录到对应的水印数据信息上。class func baseDecode(data: UnsafeMutablePointer<UInt8>, width w: Int, height h: Int) -> (result: Bool, info: String) {
// ......return (false, "解密");}
具体可以看:QFLSBMan.swift[2]????见文末(代码没有注释,也比较乱,抱歉了哈)。
补 充
1、这里需要注意在截图类的逻辑那部分,有如下的函数 & 执行(加密后的图片再执行)
将加密后将图片先缓存本地,再获从缓存本地的图片获取出来进行分享和保存相册,这样加密的数才不会由于保存相册或者直接分享而导致数据会丢失。目前测试分享QQ和钉钉数据解码正常,而微信会丢失的。
compare
以上图片都是不使用 toPNG 操作后,图片的加密像素部分,都发生变化,所以已无法解密出原来的信息。
而缓存本地的作用就是为了解决该情况(再考察其他办法解决方案)。
private func toPng() -> Bool
if !toPng() {print("showScreenshot toPng 失败")return}
2、还有每张截图都加入QFScreenshot 的普通水印,可简单区分系统截图还是加密截图。
let text = "QFScreenshot" as NSStringlet p = CGPoint(x: 20, y: 160)text.draw(at: p, withAttributes: [NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16), NSAttributedString.Key.foregroundColor: UIColor(white: 0.5, alpha: 1)])
总 结
LSB 的抗干扰比较差,原因在于假如使用的第三方分享,其在传送过程对图片进行二次处理或者优化,很可能会导致信息的丢失。
当然目前的Demo实践上,只要用户使用截图后展示的截图图片通过保存到相册,再QQ分享 或者 直接QQ分享 都能保证信息的完整性。
应该会有人问为什么不直接上传到自家的服务器,并且直接将信息一并传过去,无须这样加密,确实也有道理。
但其实加隐形水印这种方式也很直接,让用户直接将截图分享与QQ的客服或者支撑人员,然后进行沟通。
并且本文只是隐形水印在iOS的实现,其用途的扩展就由各位脑补。
其他
用途
1、将信息重复布满整张图片(可以使用重复文案,如 QianFan),加入在类似 抽奖 或者 大转盘 等具有敏感信息的结果截图上,以防止有可能的 PS 做假。
2、用在视频帧中,在视频里面传输指令或者信息(帧丢失的话偶就不管了哈),等等。
改进的
隐形水印
1、该实现的原理是通过计算像素指定通道(例如 R通道)的 所有 bit 的 0 / 1 ,通过奇偶数 来替换标准的LSB的最低位 进行比较,这样做更具有隐蔽性。
一种改进的LSB信息隐藏算法研究与实现[3]????见文末
2、加密的信息可以是二维码,可以另一张小图片(具体看图片是灰度图还是彩色图)。
????文中备注
[1]????QFImageMaskMan:
https://gitee.com/chenqihui/QFImageMaskDemo
[2]????QFLSBMan.swift:
Chuangkit.com/sj-pi2-si13-or0-pt0-us382-pn1.html
[3]????一种改进的LSB信息隐藏算法研究与实现:
http://www.doc88.com/p-0814918905734.html
程序员专栏 扫码关注填加客服 长按识别下方二维码进群
近期精彩内容推荐:
有个程序员老公,是种什么样的感觉
程序员写了一款手游,全公司被抓!
厌倦了if.else的你,考虑下换种选择结构?
Spring Boot 项目脚本
在看点这里好文分享给更多人↓↓
iOS 隐形水印之 LSB 实现相关推荐
- 水印,数字水印,频域水印(隐形水印)很麻烦!用这个分分钟搞定!
水印,你在很多公司图片都会看到,里面都会加入图片都会有显式水印,或者半隐形水印.平常加水印,只要将两张图片色值混合就没问题了 import cv2 import numpy as np import ...
- what???日本大蒜销量激增;剑桥大学『机器学习与贝叶斯推理』最新课程;黑客工具速查清单;图片隐形水印添加工具;前沿论文 | ShowMeAI资讯日报
- iOS:制作简易的 AAC 播放器 —— 了解音频的播放流程
????????关注后回复 "进群" ,拉你进程序员交流群???????? 作者丨千帆直播 来源丨搜狐技术产品(ID:sohu-tech) 本文字数:1872字 预计阅读时间:8分 ...
- 【周年福利Round1】一文看破Swift枚举本质
本文字数:6139字 预计阅读时间:16分钟 前言 随着Swift5.0版本release之后,abi也变得逐渐稳定起来,加上Swift语法简洁.安全.语法糖丰富等特性,越来越多的开发者们开始拥抱.学 ...
- 《鲲鹏》MV,160万开发者的集结号
撰文:康翔 编辑:阿由 设计:紫菜 我生在神秘东方 血液已热的发烫 百千万脉络扩张 汇聚到我的胸膛 凝望已久的远方 埋在深处的力量 唤醒沉睡的渴望 如今我就要登场 2020年3月27日上午十点整,在 ...
- 【周年福利Round2】都0202年了,您还不会Elasticsearch?
本文字数:8775字 预计阅读时间:22分钟 什么是 Elasticsearch?它是一个分布式的开源搜索和分析引擎,适用于所有类型的数据,包括文本.数字.地理空间.结构化和非结构化数据. 无论在开源 ...
- 隐形数字水印_Android 实现图片水印与隐形数字水印
在使用知乎,微博的时候,我们经常可以看到自己上传的图片被加上了文字水印,在实际的应用开发过程中,很多客户端都需要开发者自己编写 Canvs 绘制图形水印的方法,今天我想在这里介绍一个轻量级的开源 An ...
- 隐形数字水印_轻量级安卓水印框架,支持隐形数字水印 AndroidWM
AndroidWM 一个轻量级的 Android 图片水印框架,支持隐形水印和加密水印. English version 下载与安装 Gradle: implementation 'com.huang ...
- 实战28:数字图像可视化水印系统的设计与实现(LSB算法、DCT算法、随机间隔算法、区域校验位算法、图像降级算法、图像降级算法改进等6种数字水印算法的实现)
基于数字图像的可视化水印系统按照水印算法的不同主要分为空间域水印和变换域水印两大类.空间域水印以 LSB 算法--最低有效位算法为代表,变换域水印以 DCT 算法--离散余弦变换算法为代表[10]. ...
最新文章
- 国际基因编辑科技发展报告
- Spring IoC — 基于注解的配置
- 对比学习可以使用梯度累积吗?
- DAO(Data Access Object ,数据访问对象)设计模式
- 华为P40渲染图再曝光:果然是年度真旗舰
- git rebase branch内部调整 调整commit顺序
- PyQt5学习笔记2-GUI编程基础-2
- 博客访问量,有没有可能是系统所为?
- UG NX二次开发(C#)-同步建模-删除倒圆(圆角)
- tempo.js模板引擎:通过tempo将Json串填充到html页面中
- c语言 q15格式,DSP 数据 Q格式
- 不动点迭代 开平方 Excel演示
- Kotlin开发利器之协程
- workflow实例
- scratch项目:自制电子画板(Scratch画笔类积木、事件类积木中消息广播的应用)
- 深度学习04 -模型管理
- 用Arduino和esp8266检测WIFI信号强度
- 深圳码农买房记4:踩点篇
- 基于51单片机的水质水位PH值溶解率电导率水温浊度检测proteus仿真原理图PCB
- 前进中不能迷失方向--Java程序员职业发展路线
热门文章
- Failed to execute goal org.apache.maven.plugins,clean failed: org/apache/maven/shared/utils/Os
- php 转换 html code,PHP 将 HTML 代码 转换到 UBB 论坛代码
- java代驾业务信息管理系统_基于jsp的代驾平台-JavaEE实现代驾平台 - java项目源码...
- CSDN问答——测评
- 短视频寒冬,抖音、美拍、快手等将何去何从?
- oracle中每月调用一次,Oracle Job的使用(定时执行)
- mx-link无线打印服务器,TP-LINK USB Printer Controller(TP-LINK打印服务器)
- Failed to convert a NumPy array to a Tensor (Unsupported object type numpy.int64).
- 宁夏理工学院计算机是专科吗,宁夏理工学院是本科还是专科
- 电机调速制动matlab,鼠笼式三相异步电机:起动、调速、制动(原理与Simulink仿真)...