表征我的RAW相机输出
我遇到了一个很奇怪的问题,跟我的Leopard Imaging M021 摄像头有关。这个公司的产品对 Linux 的支持不太好,所以摄像头只能输出原始数据。我需要在 Beagleboard 上使用它,所以我才花时间去搞定它。
他们的一位员工告诉我,这个摄像头的数据是YUY2 格式(通常每个像素16位),但高4位总是0,低12位则包含一些信息,像这样:

我用这个命令:
fswebcam --device /dev/video0 --resolution 1280x720 --dumpframe test.raw
得到的文件大小是1,843,200字节,这意味着它是一张1280x720的图片,每个像素2字节(16位每像素)。
但是,我唯一能让图像正确显示的方法是使用IrfanView的RAW图像显示功能,并设置为每像素12位,不归一化,使用GR的拜耳模式,并进行垂直翻转。我不知道为什么需要垂直翻转,因为其他设置会显示出扭曲的奇怪图像,但不翻转。然后我用12 BPP设置,它就翻转了。
我猜因为高4位总是0,这实际上使得每个像素只有12位,而不是16位?
我需要搞清楚文件中的字节到底发生了什么,才能自己写转换算法(除非有人知道有开源程序能做和IrfanView一样的事情)。
我用Python写了一个简单的脚本,只提取Y分量并查看(期待得到图像的灰度版本),但得到的却是一个非常扭曲的版本。我的代码有什么问题吗?我提取数据的顺序错了吗?IrfanView中的“未归一化”是什么意思?为什么要使用GR拜耳模式设置才能看到RGB图像?
with open('test.raw', 'r+b') as f:
Y0 = []
U = []
Y1 = []
V = []
vals = [Y0, U, Y1, V]
val = f.read(1)
pixel = int.from_bytes(val, byteorder='big')
i = 0
vals[i].append(pixel)
while val:
i += 1
val = f.read(1)
if val != "":
pixel = int.from_bytes(val, byteorder='big')
vals[i % 4].append(pixel)
k = 0
with open("1.test", "w") as f:
for i in range(720):
for j in range(640):
f.write(str(Y0[k]))
f.write(" ")
f.write(str(Y1[k]))
f.write(" ")
k += 1
f.write("\n")
得到的奇怪图像:

我希望能得到任何人的帮助或建议。
编辑:
进一步可能有帮助的证据。如果我在matlab中运行这个,直接把每2个字节当作确切的像素值:
fid = fopen('test.raw', 'r');
[I, count] = fread(fid , [1280, 720], 'uint16');
imagesc(I')
colormap(gray);
我得到的图像是:

我仍然缺少颜色信息,因为我只是忽略了它。而且图像还是有点扭曲。但看起来好一些。如果你放大,图像扭曲的模式是好像素、黑像素、好像素、黑像素,等等。有没有更懂相机和颜色的人知道这意味着什么?
编辑2:
在Mark Ransom的专家帮助下,我写了一个不错的OpenCV脚本来读取数据,利用CV_BayerGR2RGB转换为RGB,并查看图像。它成功了!
#include <vector>
#include <iostream>
#include <stdio.h>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
int main() {
// Each pixel is made up of 16 bits, with the high 4 bits always equal to 0
unsigned char bytes[2];
// Hold the data in a vector
std::vector<unsigned short int> data;
// Read the camera data
FILE *fp = fopen("test.raw","rb");
while(fread(bytes, 2, 1, fp) != 0) {
// The data comes in little-endian, so shift the second byte right and concatenate the first byte
data.push_back(bytes[0] | (bytes[1] << 8));
}
// Make a matrix 1280x720 with 16 bits of unsigned integers
cv::Mat imBayer = cv::Mat(720, 1280, CV_16U);
// Make a matrix to hold RGB data
cv::Mat imRGB;
// Copy the data in the vector into a nice matrix
memmove(imBayer.data, data.data(), data.size()*2);
// Convert the GR Bayer pattern into RGB, putting it into the RGB matrix!
cv::cvtColor(imBayer, imRGB, CV_BayerGR2RGB);
cv::namedWindow("Display window", cv::WINDOW_AUTOSIZE);
// *15 because the image is dark
cv::imshow("Display window", 15*imRGB);
cv::waitKey(0);
return 0;
}
1 个回答
你手上的证据,首先是每个字节的前4位都是0,还有你能从Irfanview看到正常的彩色图片,这些都说明那个员工说错了,格式其实不是YUY2,而是GRGB。
这段代码的作用是提取绿色像素,并把它们加倍,这样你就能大概了解图片的样子。注意,偶数行和奇数行会交替,偶数行以G开头,奇数行则以R或B开头。
with open('test.raw', 'r+b') as f:
raw_g = []
for y in range(720):
for x in range(1280//4):
if y % 2:
r1, r0, g1, g0, b1, b0, g3, g2 = f.read(8)
else:
g1, g0, r1, r0, g3, g2, b1, b0 = f.read(8)
g0 = g0 << 8 | g1
g2 = g2 << 8 | g3
raw_g.extend([g0, g0, g2, g2])
在把这些值写入灰度文件之前,你可能想要把它们右移4位,>> 4
。
编辑:代码中还有一个小错误(多读了一次),我也没意识到这些值是小端格式,而不是大端格式。以上的代码示例已经做了这些修改。
因为这些值看起来有点暗,我还决定做一个伽马校正——我用的不是n >> 4
,而是int(255.999 * (n / 4095.0)**(1/2.2))
。
我怕去除贝叶斯滤波的过程有点复杂,不太适合在短短的StackOverflow回答中讲解。