英语网站的翻译稿。
大多数人听到“JPEG解码”时,通常会觉得这是很困难的事,需要很强的处理能力以及复杂的数学运算,并认为在相对便宜且速度较慢的8位处理器平台(比如Arduino)上是不可能实现的,或者说至少是不切实际的。在本文中,我们将学习如何使用基于Arduino控制的相机拍摄JPEG照片,以及如何将照片转换成像素点矩阵,并将所有像素通过串行端口传输到我们的PC端或者任何我们想要的平台上!
硬件- • Arduino Mega
- • VC0706 串口摄像头
- • 带SPI接口的SD卡模块
软件- • Arduino IDE
- • Processing (3.3.2 或更高版本)
- • Adafruit VC0706 库 (可从 GitHub上获取)
- • Bodmer 的 JPEGDecoder 库 (同样可从 GitHub上获取)
虽然说上面描述的内容是完全可以实现的,但是仍然有必要解释一下为什么我们在解码JPEG照片时会遇到麻烦。毕竟,在上面的硬件要求中列有一个SD模块,您会问:“我们直接把照片以photo.jpeg 的格式存储到SD卡里不就行了吗?”当然,这确实是整个过程中的重要一步,但是现在请从不同的角度来考虑这个问题:如果我们想通过速度慢、有些不稳定的连接来发送照片怎么办?如果我们只是把JPEG照片分割成不同的包并通过慢速连接发送,那么就有部分数据损坏或丢失的风险。发生这种情况时,我们很可能无法用损坏的数据还原原始数据。
但是,当我们将JPEG解码为位图,然后发送实际像素时,不会有任何风险。如果某些数据在传输的过程中损坏或丢失,我们仍然可以获取整张图像,只有数据损坏的地方会出现失色,错位或像素丢失的情况。当然,它与我们的原始图像并不相同,但是仍然包含了大多数原始信息,并且仍然是“可读的”。既然已经知道了为什么要这样做,接下来让我们看一下如何实施这种方法。
拍摄照片在开始解码JPEG照片之前,首先我们需要拍摄照片。我们最终的目标是拍摄一张照片,将照片存储到SD卡中,然后发送到某个地方。那我们按照这个思路先从一个简单的设置开始吧。
图1:可以使用Arduino拍摄和存储照片的设置
您可能已经注意到了,相机RX线上有一个简单的电阻分压器。这是因为VC0706芯片的逻辑电平为3.3V(即使电源电压为5V),但Arduino Mega的逻辑电平为5V。所以在这里有个善意忠告:当将5V的Arduino和3.3V模块进行接合时,在RX线上始终至少使用一个分压器。这比换一个新的模块要快得多。SD卡读卡器通过SPI接口直接连接。
既然硬件已经设置好了,那我们就需要开始解决代码部分了。标准Arduino IDE安装已经包含了用于SD卡的库,因此我们从列表中对SD卡进行查看即可。
现在,Arduino将每10秒左右拍摄一张照片,直到SD卡上的空间用完为止。但是,由于照片通常约为48kB,并且我目前使用的是2GB的SD卡,因此足够容纳超过43000张的照片。理论上来说我们不需要那么多的照片。但是既然已经拍摄了一些照片,我们现在可以继续进行下一个有趣环节了:将它们从JPEG压缩后的难以管理的杂乱数据变成简单的像素阵列!
解码和发送照片在开始解码前,让我们快速地看一下图片数据在JPEG文件中究竟是如何存储的。如果您对这部分不太感兴趣,可以跳过下面三段内容。如果您确切地对图形和压缩方面的知识了解一二(不像我这样),您也可以跳过这一部分。以下内容进行了一定程度的简化。
对任何类型的图片数据进行存储时,有两种基本方法:无损和有损压缩。两者的区别很明显:当使用无损压缩(例如PNG)对图像进行编码时,处理之后图像的每个像素都与开始时完全相同。这非常适合于诸如计算机图形学之类的工作,但是不幸的是,这是以增加文件大小为代价的。另一方面,对于像JPEG这样的有损压缩,我们丢失了一些细节,但是生成的文件大小要小得多。
JPEG压缩方式在理解上可能会有点困难,因为会涉及到一些“离散余弦变换”,不过主要原理实际上是非常简单的。首先,将图片从RGB颜色空间转换为YCbCr。我们都知道RGB颜色空间—它存储了红色(R)、绿色(G)和蓝色(B)的颜色值。YCbCr有很大的不同—它使用亮度(Y—基本是原始图像的灰度图),蓝色差分量(Cb—图片中的“蓝色”)和红色差分量(Cr—图片中的“红色”)。
图2:JPEG照片以及其分离出的色差分量。左上角为原始图像,左下角为Y分量,右上角为Cb分量,右下角为Cr分量
JPEG减小文件大小的方法实际上与人眼处理颜色的方式密切相关。看一下上图中的Y、Cb和Cr分量图。哪一个看起来更像是原始图片?是的,灰度图!这是因为人眼对亮度的敏感度要比对其它两个分量的敏感度高得多。JPEG压缩就非常聪明地利用了这一点,在保留原始Y分量的同时减少Cb和Cr分量中的信息量。如此一来,生成的图片就比原始文件小得多,并且由于大多数压缩信息都位于人眼不太敏感的分量中,因此与未压缩的图片相比,您几乎看不到压缩图片的区别。
现在,让我们开始运行真正实现将JPEG转换为像素阵列的代码吧。幸运的是,有一个库可以做到这一点—Bodmer的JPEGDecoder(可在
GitHub上获得),该库基于Rich Geldreich(也可在
GitHub)上获取)提供的出色的picojpeg库。虽然最初编写JPEGDecoder的目的是在TFT显示器上显示图像,但是将其进行一些细微调整后就可以用于我们的工作了。
答题有礼:因为我们需要大量的RAM来对照片进行解码,所以我们将使用Arduino Mega。此外,Mega上还有一个额外的有利设计:有 个单独的硬件串行端口,这样我们就可以使用Serial1端口与相机进行通信,并使用Serial端口与PC进行通信。回答正确可获得5ROHM金币(阅读原文可快速获取答案)