图像隐写术

图像隐写术

本文为图像的隐写提供了一种思路。还有更多的思路,这里不做讲述。

项目源代码在:jeefies/jimg-ivs 中。

原理

利用了像素近似用肉眼难以察觉的前提(这就是为什么 jpeg 会存在……

以及 png 图片会原滋原味的保留像素数据,而不是像 jpeg 一样神秘的变化。

而这里并没有采用利用 8bit 像素最后几位来保存数据的方式,而是采用字体渲染,只在某一个颜色通道中展现文本(只读)。

所在通道中的每一个 8bit 位中,有 64 个取值表示被渲染的像素。

也就是说,要做一个近似处理。

至于如何渲染……利用了 SDL_ttf 来完成这件事。

实现

这次我没有继续使用 dlang 写这个小小的项目。而是纯纯只用了 C++,主要是比较方便,因为不仅使用了 SDL,还使用了 opencv,实在是不想封装一层。

Canvas

这是用来渲染文本的东西。由于一行能显示的文本有限,所以需要适当的裁剪。

一一裁剪太慢了,所以考虑倍增求解。

学算法的好处……仅此一点

int len = 0, lg = (int)log2(s.size()) + 1;
for (int step = 1 << lg; step; step >>= 1) {
    if (len + step <= s.size()) {
        int clen = len + step;
        while (clen > len && !checkCompleteUTF8(s.substr(0, clen)))
            --clen;
        TTF_SizeUTF8(font, s.substr(0, clen).c_str(), &sw, &sh);
        if (x + sw < w) len = clen;
    }
}

但是,你可能关注到了 checkCompleteUTF8,这是用于解决中文被截断无法完整渲染的问题(这个参考 utf8 编码方式)

// Important when showing Chinese!
bool checkCompleteUTF8(const std::string &s) {
	char nBytes = 0;

	for (unsigned char c : s) {
		if (nBytes == 0 && c >= 0x80) {
			if (0xFC <= c && c <= 0xFD) nBytes = 6;
			else if (0xF8 <= c) nBytes = 5;
			else if (0xF0 <= c) nBytes = 4;
			else if (0xE0 <= c) nBytes = 3;
			else if (0xC0 <= c) nBytes = 2;
			else return false;
			--nBytes;
		} else if (nBytes) {
			if ((c & 0xC0) != 0x80) return false;
			--nBytes;
		}
	}

	if (nBytes > 0) return false;
	return true;
}

注意,必须要用 unsigned char

近似处理

核心部分。

void invisibleWrite(SDL_Surface *sur, cv::Mat &mat) {
	SDL_LockSurface(sur);

	int w = sur->w, h = sur->h;
	Uint32 * pixels = (Uint32 *)sur->pixels;

	auto threeSumPixel = [](Uint32 cl) {
		return ((cl & 0xFF) + ((cl >> 8) & 0xFF) + ((cl >> 16) & 0xFF)) / 3;
	};

	auto discrete = [](unsigned char x) -> unsigned char { return (unsigned char)((round((double)x / 8)) * 8 - 1); };

	for (int i = 0; i < h; ++i) {
		int base = i * w;
		unsigned char* data = mat.ptr<unsigned char>(i);
		for (int j = 0; j < w; ++j) {
			if (threeSumPixel(pixels[base + j]) > 128) 
				data[j] = discrete(data[j]);
			else if (data[j] == discrete(data[j])) 
				data[j] = data[j] == 0 ? 1 : data[j] - 1;
		}
	}

	SDL_UnlockSurface(sur);
}

其中呢,mat 表示图像分离后的一个通道。使用的 cv::split 函数完成。

相信我写的代码还是不难读明白的……

读入处理

cv::Mat encode(int channel_id = GREEN) {
	cv::Mat newImage;
	if (!_open) return newImage;
	std::vector<cv::Mat> channels;
	cv::split(image, channels);

	showRendered(cvs.getSurface(), 10);
	invisibleWrite(cvs.getSurface(), channels[channel_id]);
	cv::merge(channels, newImage);

	return newImage;
}

这是从 JIVS_Encoder 中截出来的一段代码。


其他部分的代码与上文代码类似……只要理解的上文中的代码,其他部分也就很好写了。

反正我写了这么多封装也就写了 294 行。不多,1 天就可以再写一遍 QwQ


其实吧

这种隐写术很难看破,因为没有对图像二进制文件做出任何的变动,并且像素的近似也是肉眼难以察觉,所以完全可以成为和神秘的人说神秘的话的一种途径吧(就是只能传输文本信息)。

如果要二进制,可能还是利用掩码或者神秘的异或来完成更好。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值