在 Matlab 中从 .ravi 文件中提取温度
Extracting Temperatures from .ravi file in Matlab
我的问题
很像这里的 post:,我有一个 .ravi 文件(一个辐射视频文件,与 .avi 非常相似),我正在尝试提取温度在其中,将它们与其他传感器数据一起使用。
当您下载“PIX 连接软件”时,可以在文档 (http://infrarougekelvin.com/en/optris-logiciel-eng/) 中找到示例文件。不幸的是,根据文档,温度信息以 16 位格式存储,Matlab 似乎对此不太满意。
我是如何尝试解决问题的
我尝试按照前面提到的 post 中的说明进行操作,但不知何故我很难达到结果,甚至接近正确的温度。Original Picture with temperatures in the Optris Software
我尝试用不同的方法阅读视频:
起初我希望使用Matlab中的videorecorder
特性:
video = VideoReader(videoPath);
frame1 = video.read(1);
imagesc(frame1)
但这只会导致这张糟糕的画面,这正是我在尝试在像 vlc 这样的媒体播放器中播放 .ravi 文件时所看到的。
First try with videorecorder function
然后我尝试查看我的文件的二进制表示并注意到,我可以在某个标记处分隔帧 Beginning of a new frame in binary representation
所以我尝试用 matlab fread
函数读取文件:
fileID = fopen(videoPath);
[headerInfo,~] = fread(fileID,[1,123392],'uint8');
[imageMatrix,count] = fread(fileID,[video.width, video.height],'uint16', 'b');
imagesc(imageMatrix')
现在图像看起来更好了,你至少可以看到制动盘,但似乎较高的温度有某种偏移,仍然缺失,图像是正确的。
此外,我从文件中读取的值与实际温度相去甚远,正如其他 post 和文档所建议的那样。
Getting somewhere!
我的问题
我是不是漏掉了什么重要的东西?有人能给我指出正确的方向,在哪里看或如何从我的视频中获取实际温度吗?因为它与另一个 post 中的 cpp 代码一起工作,我猜这可能是一个 matlab 问题。
获取原始帧数据的一个相对简单的解决方案是将 RAVI 视频文件转换为原始视频文件格式。
您可以使用 FFmpeg(命令行工具)将 RAVI 格式转换为 RAW 格式。
示例:
ffmpeg -y -f avi -i "Sequence_LED_Holder.ravi" -vcodec rawvideo "Sequence_LED_Holder.yuv"
YUV(原始二进制数据)文件,可以通过 MATLAB 使用 fread
函数简单地读取。
注意:.yuv
只是原始视频文件的约定(FFmpeg 使用)- 实际像素格式不是 YUV,而是 int16 格式。
您可以尝试手动解析 RAVI 文件,但使用 FFmpeg 更简单。
原始文件格式由一个接一个的原始视频帧组成,没有headers。
在我们的例子中,每帧是 width*height*2
字节。
像素类型为 int16
(可能包含负值)。
红外视频帧没有颜色信息。
这些颜色只是使用调色板创建并用于可视化的“假色”。
代码示例使用来自不同 IR 相机制造商的调色板。
获取温度:
我找不到将像素值转换为等效温度的方法。
我没有阅读文档 - 有可能某处记录了转换。
MATLAB 代码示例应用以下阶段:
- 使用 FFmpeg 将 RAVI 文件格式转换为 RAW 视频文件格式。
- 将视频帧读取为
[cols, rows]
大小 int16
矩阵。
- 删除可能包含数据(不是像素)的第一行。
- 使用线性对比拉伸 - 用于可视化。
- 应用伪色 - 用于可视化。
- 显示处理后的视频帧。
这是代码示例:
%ravi_file_name = 'Brake disc.ravi';
%ravi_file_name = 'Combustion process.ravi';
%ravi_file_name = 'Electronic board.ravi';
%ravi_file_name = 'Sequence_carwheels.ravi';
%ravi_file_name = 'Sequence_drop.ravi';
ravi_file_name = 'Sequence_LED_Holder.ravi';
%ravi_file_name = 'Steel workpiece with hole.ravi';
yuv_file_name = strrep(ravi_file_name, '.ravi', '.yuv'); % Same file name with .yuv extension.
% Get video resolution.
vidinfo = mmfileinfo(ravi_file_name);
cols = vidinfo.Video.Width;
rows = vidinfo.Video.Height;
% Execute ffmpeg (in the system shell) for converting RAVI to raw data file.
% Remark: download FFmpeg if needed, and make sure ffmpeg executable is in the execution path.
if ~exist(yuv_file_name, 'file')
% Remark: For some of the video files, cmdout returns a string with lots of meta-data
[status, cmdout] = system(sprintf('ffmpeg -y -f avi -i "%s" -vcodec rawvideo "%s"', ravi_file_name, yuv_file_name));
if (status ~= 0)
fprintf(cmdout);
error(['Error: ffmpeg status = ', num2str(status)]);
end
end
% Get the number of frames according to file size.
filesize = getfield(dir(yuv_file_name), 'bytes');
n_frames = filesize / (cols*rows*2);
f = fopen(yuv_file_name, 'r');
% Iterate the frames (skip the last frame).
for i = 1:n_frames-1
% Read frame as cols x rows and int16 type.
% The data is signed (int16) and not uint16.
I = fread(f, [cols, rows], '*int16')';
% It looks like the first line contains some data (not pixels).
data_line = I(1, :);
I = I(2:end, :);
% Apply linear stretch - in order to "see something"...
J = imadjust(I, stretchlim(I, [0.02, 0.98]));
% Apply false colors - just for visualization.
K = ColorizeIr(J);
if (i == 1)
figure;
h = imshow(K, []); %h = imshow(J, []);
impixelinfo
else
if ~isvalid(h)
break;
end
h.CData = K; %h.CData = J;
end
pause(0.05);
end
fclose(f);
imwrite(uint16(J+2^15), 'J.tif'); % Write J as uint16 image.
imwrite(K, 'K.png'); % Write K image (last frame).
% Colorize the IR video frame with "false colors".
function J = ColorizeIr(I)
% The palette apply different IR manufacture - don't expect the result to resemble OPTRIS output.
% https://groups.google.com/g/flir-lepton/c/Cm8lGQyspmk
colormapIronBlack = uint8([...
255, 255, 255, 253, 253, 253, 251, 251, 251, 249, 249, 249, 247, 247,...
247, 245, 245, 245, 243, 243, 243, 241, 241, 241, 239, 239, 239, 237,...
237, 237, 235, 235, 235, 233, 233, 233, 231, 231, 231, 229, 229, 229,...
227, 227, 227, 225, 225, 225, 223, 223, 223, 221, 221, 221, 219, 219,...
219, 217, 217, 217, 215, 215, 215, 213, 213, 213, 211, 211, 211, 209,...
209, 209, 207, 207, 207, 205, 205, 205, 203, 203, 203, 201, 201, 201,...
199, 199, 199, 197, 197, 197, 195, 195, 195, 193, 193, 193, 191, 191,...
191, 189, 189, 189, 187, 187, 187, 185, 185, 185, 183, 183, 183, 181,...
181, 181, 179, 179, 179, 177, 177, 177, 175, 175, 175, 173, 173, 173,...
171, 171, 171, 169, 169, 169, 167, 167, 167, 165, 165, 165, 163, 163,...
163, 161, 161, 161, 159, 159, 159, 157, 157, 157, 155, 155, 155, 153,...
153, 153, 151, 151, 151, 149, 149, 149, 147, 147, 147, 145, 145, 145,...
143, 143, 143, 141, 141, 141, 139, 139, 139, 137, 137, 137, 135, 135,...
135, 133, 133, 133, 131, 131, 131, 129, 129, 129, 126, 126, 126, 124,...
124, 124, 122, 122, 122, 120, 120, 120, 118, 118, 118, 116, 116, 116,...
114, 114, 114, 112, 112, 112, 110, 110, 110, 108, 108, 108, 106, 106,...
106, 104, 104, 104, 102, 102, 102, 100, 100, 100, 98, 98, 98, 96, 96,...
96, 94, 94, 94, 92, 92, 92, 90, 90, 90, 88, 88, 88, 86, 86, 86, 84, 84,...
84, 82, 82, 82, 80, 80, 80, 78, 78, 78, 76, 76, 76, 74, 74, 74, 72, 72,...
72, 70, 70, 70, 68, 68, 68, 66, 66, 66, 64, 64, 64, 62, 62, 62, 60, 60,...
60, 58, 58, 58, 56, 56, 56, 54, 54, 54, 52, 52, 52, 50, 50, 50, 48, 48,...
48, 46, 46, 46, 44, 44, 44, 42, 42, 42, 40, 40, 40, 38, 38, 38, 36, 36,...
36, 34, 34, 34, 32, 32, 32, 30, 30, 30, 28, 28, 28, 26, 26, 26, 24, 24,...
24, 22, 22, 22, 20, 20, 20, 18, 18, 18, 16, 16, 16, 14, 14, 14, 12, 12,...
12, 10, 10, 10, 8, 8, 8, 6, 6, 6, 4, 4, 4, 2, 2, 2, 0, 0, 0, 0, 0, 9,...
2, 0, 16, 4, 0, 24, 6, 0, 31, 8, 0, 38, 10, 0, 45, 12, 0, 53, 14, 0,...
60, 17, 0, 67, 19, 0, 74, 21, 0, 82, 23, 0, 89, 25, 0, 96, 27, 0, 103,...
29, 0, 111, 31, 0, 118, 36, 0, 120, 41, 0, 121, 46, 0, 122, 51, 0, 123,...
56, 0, 124, 61, 0, 125, 66, 0, 126, 71, 0, 127, 76, 1, 128, 81, 1, 129,...
86, 1, 130, 91, 1, 131, 96, 1, 132, 101, 1, 133, 106, 1, 134, 111, 1,...
135, 116, 1, 136, 121, 1, 136, 125, 2, 137, 130, 2, 137, 135, 3, 137,...
139, 3, 138, 144, 3, 138, 149, 4, 138, 153, 4, 139, 158, 5, 139, 163,...
5, 139, 167, 5, 140, 172, 6, 140, 177, 6, 140, 181, 7, 141, 186, 7,...
141, 189, 10, 137, 191, 13, 132, 194, 16, 127, 196, 19, 121, 198, 22,...
116, 200, 25, 111, 203, 28, 106, 205, 31, 101, 207, 34, 95, 209, 37,...
90, 212, 40, 85, 214, 43, 80, 216, 46, 75, 218, 49, 69, 221, 52, 64,...
223, 55, 59, 224, 57, 49, 225, 60, 47, 226, 64, 44, 227, 67, 42, 228,...
71, 39, 229, 74, 37, 230, 78, 34, 231, 81, 32, 231, 85, 29, 232, 88,...
27, 233, 92, 24, 234, 95, 22, 235, 99, 19, 236, 102, 17, 237, 106, 14,...
238, 109, 12, 239, 112, 12, 240, 116, 12, 240, 119, 12, 241, 123, 12,...
241, 127, 12, 242, 130, 12, 242, 134, 12, 243, 138, 12, 243, 141, 13,...
244, 145, 13, 244, 149, 13, 245, 152, 13, 245, 156, 13, 246, 160, 13,...
246, 163, 13, 247, 167, 13, 247, 171, 13, 248, 175, 14, 248, 178, 15,...
249, 182, 16, 249, 185, 18, 250, 189, 19, 250, 192, 20, 251, 196, 21,...
251, 199, 22, 252, 203, 23, 252, 206, 24, 253, 210, 25, 253, 213, 27,...
254, 217, 28, 254, 220, 29, 255, 224, 30, 255, 227, 39, 255, 229, 53,...
255, 231, 67, 255, 233, 81, 255, 234, 95, 255, 236, 109, 255, 238, 123,...
255, 240, 137, 255, 242, 151, 255, 244, 165, 255, 246, 179, 255, 248,...
193, 255, 249, 207, 255, 251, 221, 255, 253, 235, 255, 255, 24]);
lutR = colormapIronBlack(1:3:end);
lutG = colormapIronBlack(2:3:end);
lutB = colormapIronBlack(3:3:end);
% Convert I to uint8
I = im2uint8(I);
R = lutR(I+1);
G = lutG(I+1);
B = lutB(I+1);
J = cat(3, R, G, B);
end
更新:
Python 使用 OpenCV 的代码示例(未着色):
使用Python和OpenCV,我们可以跳过FFmpeg转换部分。
我们可以从 RAVI 文件中获取未解码的 RAW 视频,而不是将 RAVI 文件转换为 YUV 文件。
- 打开视频文件并设置
CAP_PROP_FORMAT
属性 以获取未解码的 RAW 视频:
cap = cv2.VideoCapture(ravi_file_name)
cap.set(cv2.CAP_PROP_FORMAT, -1) # Format of the Mat objects. Set value -1 to fetch undecoded RAW video streams (as Mat 8UC1).
读取视频帧(使用 ret, frame = cap.read()
)时,未解码的帧被读取为 uint8
个元素的“长”行向量。
- 将帧转换为
int16
类型,并重塑为 cols
x rows
:
首先,我们必须将向量元素“查看”为 int16
个元素(相对于 uint8
个元素):frame.view(np.int16)
其次,我们必须将向量重塑为矩阵。
转换和整形代码:
frame = frame.view(np.int16).reshape(rows, cols)
完成 Python 代码示例:
import numpy as np
import cv2
ravi_file_name = 'Sequence_LED_Holder.ravi'
cap = cv2.VideoCapture(ravi_file_name) # Opens a video file for capturing
# Fetch undecoded RAW video streams
cap.set(cv2.CAP_PROP_FORMAT, -1) # Format of the Mat objects. Set value -1 to fetch undecoded RAW video streams (as Mat 8UC1). [Using cap.set(cv2.CAP_PROP_CONVERT_RGB, 0) is not working]
cols = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) # Get video frames width
rows = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) # Get video frames height
while True:
ret, frame = cap.read() # Read next video frame (undecoded frame is read as long row vector).
if not ret:
break # Stop reading frames when ret = False (after the last frame is read).
# View frame as int16 elements, and reshape to cols x rows (each pixel is signed 16 bits)
frame = frame.view(np.int16).reshape(rows, cols)
# It looks like the first line contains some data (not pixels).
# data_line = frame[0, :]
frame_roi = frame[1:, :] # Ignore the first row.
# Normalizing frame to range [0, 255], and get the result as type uint8 (this part is used just for making the data visible).
normed = cv2.normalize(frame_roi, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8U)
cv2.imshow('normed', normed) # Show the normalized video frame
cv2.waitKey(10)
cap.release()
cv2.destroyAllWindows()
注:
- 如果需要着色,您可以使用以下示例:Thermal Image Processing
每帧按像素保存 16 位值,低字节在前,高字节在后。要找到温度,您必须应用此公式:temp = (hi * 256.0 + lo) / 10.0 - 100.0
.
使用低值,您可以创建灰度图像。我成功地将这种方法用于旧的 Pi-160 Optris 相机。然而,对于新的 PI-450,由于 PI Connect 现在不支持二进制导出,因此更加困难。
我用 Ffmpeg 测试了解决方案,但没有成功。你得到一个16位的数据文件,但是实际数据偏移量不对,导致温度异常。
你成功了吗?
二进制读取示例:
在大多数使用Ffmpeg处理的ravi文件中,原始图像的第一行都有非像素值。
第一行存储一些冗余信息,例如图像宽度和高度。
我们必须跳过对应于图像宽度的这一行。由于数据值是 16 位,我们必须乘以 2,才能得到二进制数据的准确偏移量。我们还必须计算图像的确切大小:imageLength = Frame size - (image width * 2)。
在另一种情况下,数据来自文件的开头,我们可以使用帧大小 (w * h * 2) 来复制二进制数据并更新偏移量。
要知道是否有必要计算数据偏移量,我们只需查看图像高度即可。如果这个值是奇数,那意味着有一个补充的第一行,因此我们应用校正。如果值为偶数,则不对数据偏移量进行修正。
解析原始 ravi 文件时也是如此。首先我们要找到movi标签在文件中的偏移量。如果 movi 标签后面跟着 ix00 标签,这意味着我们在一系列值之后给出了偏移量和每个帧相对于 movi 标签偏移量的大小。真实数据在文件中的其他地方。如果 ix00 标签不存在,这意味着数据就在 movi chunck 内部,在 00db 标志之后,并且是逐帧的。在最后一种情况下,我们还可以查找 idx1 标签(在文件末尾),它可以访问每个帧的确切偏移量和大小。
这两种方法都允许以灰度或伪彩色表示相当正确的图像,但 libirimager 工具包提供的温度公式 (float t = (float)data[i] / 10.f - 100.f) 不正确,我不明白为什么,因为当我使用 PI-160 相机生成的原始数据时公式是正确的。
Fmmpeg test
我找到了另一种方法。在最近的 ravi Optris 文件中,我们可以在 INFO 块中获取温度范围。然后,很容易找到原始数据中的最小值和最大值,并根据温标进行插值。
with correct temperatures
我的问题
很像这里的 post:
当您下载“PIX 连接软件”时,可以在文档 (http://infrarougekelvin.com/en/optris-logiciel-eng/) 中找到示例文件。不幸的是,根据文档,温度信息以 16 位格式存储,Matlab 似乎对此不太满意。
我是如何尝试解决问题的
我尝试按照前面提到的 post 中的说明进行操作,但不知何故我很难达到结果,甚至接近正确的温度。Original Picture with temperatures in the Optris Software
我尝试用不同的方法阅读视频:
起初我希望使用Matlab中的videorecorder
特性:
video = VideoReader(videoPath);
frame1 = video.read(1);
imagesc(frame1)
但这只会导致这张糟糕的画面,这正是我在尝试在像 vlc 这样的媒体播放器中播放 .ravi 文件时所看到的。
First try with videorecorder function
然后我尝试查看我的文件的二进制表示并注意到,我可以在某个标记处分隔帧 Beginning of a new frame in binary representation
所以我尝试用 matlab fread
函数读取文件:
fileID = fopen(videoPath);
[headerInfo,~] = fread(fileID,[1,123392],'uint8');
[imageMatrix,count] = fread(fileID,[video.width, video.height],'uint16', 'b');
imagesc(imageMatrix')
现在图像看起来更好了,你至少可以看到制动盘,但似乎较高的温度有某种偏移,仍然缺失,图像是正确的。
此外,我从文件中读取的值与实际温度相去甚远,正如其他 post 和文档所建议的那样。
Getting somewhere!
我的问题
我是不是漏掉了什么重要的东西?有人能给我指出正确的方向,在哪里看或如何从我的视频中获取实际温度吗?因为它与另一个 post 中的 cpp 代码一起工作,我猜这可能是一个 matlab 问题。
获取原始帧数据的一个相对简单的解决方案是将 RAVI 视频文件转换为原始视频文件格式。
您可以使用 FFmpeg(命令行工具)将 RAVI 格式转换为 RAW 格式。
示例:
ffmpeg -y -f avi -i "Sequence_LED_Holder.ravi" -vcodec rawvideo "Sequence_LED_Holder.yuv"
YUV(原始二进制数据)文件,可以通过 MATLAB 使用 fread
函数简单地读取。
注意:.yuv
只是原始视频文件的约定(FFmpeg 使用)- 实际像素格式不是 YUV,而是 int16 格式。
您可以尝试手动解析 RAVI 文件,但使用 FFmpeg 更简单。
原始文件格式由一个接一个的原始视频帧组成,没有headers。
在我们的例子中,每帧是 width*height*2
字节。
像素类型为 int16
(可能包含负值)。
红外视频帧没有颜色信息。
这些颜色只是使用调色板创建并用于可视化的“假色”。
代码示例使用来自不同 IR 相机制造商的调色板。
获取温度:
我找不到将像素值转换为等效温度的方法。
我没有阅读文档 - 有可能某处记录了转换。
MATLAB 代码示例应用以下阶段:
- 使用 FFmpeg 将 RAVI 文件格式转换为 RAW 视频文件格式。
- 将视频帧读取为
[cols, rows]
大小int16
矩阵。 - 删除可能包含数据(不是像素)的第一行。
- 使用线性对比拉伸 - 用于可视化。
- 应用伪色 - 用于可视化。
- 显示处理后的视频帧。
这是代码示例:
%ravi_file_name = 'Brake disc.ravi';
%ravi_file_name = 'Combustion process.ravi';
%ravi_file_name = 'Electronic board.ravi';
%ravi_file_name = 'Sequence_carwheels.ravi';
%ravi_file_name = 'Sequence_drop.ravi';
ravi_file_name = 'Sequence_LED_Holder.ravi';
%ravi_file_name = 'Steel workpiece with hole.ravi';
yuv_file_name = strrep(ravi_file_name, '.ravi', '.yuv'); % Same file name with .yuv extension.
% Get video resolution.
vidinfo = mmfileinfo(ravi_file_name);
cols = vidinfo.Video.Width;
rows = vidinfo.Video.Height;
% Execute ffmpeg (in the system shell) for converting RAVI to raw data file.
% Remark: download FFmpeg if needed, and make sure ffmpeg executable is in the execution path.
if ~exist(yuv_file_name, 'file')
% Remark: For some of the video files, cmdout returns a string with lots of meta-data
[status, cmdout] = system(sprintf('ffmpeg -y -f avi -i "%s" -vcodec rawvideo "%s"', ravi_file_name, yuv_file_name));
if (status ~= 0)
fprintf(cmdout);
error(['Error: ffmpeg status = ', num2str(status)]);
end
end
% Get the number of frames according to file size.
filesize = getfield(dir(yuv_file_name), 'bytes');
n_frames = filesize / (cols*rows*2);
f = fopen(yuv_file_name, 'r');
% Iterate the frames (skip the last frame).
for i = 1:n_frames-1
% Read frame as cols x rows and int16 type.
% The data is signed (int16) and not uint16.
I = fread(f, [cols, rows], '*int16')';
% It looks like the first line contains some data (not pixels).
data_line = I(1, :);
I = I(2:end, :);
% Apply linear stretch - in order to "see something"...
J = imadjust(I, stretchlim(I, [0.02, 0.98]));
% Apply false colors - just for visualization.
K = ColorizeIr(J);
if (i == 1)
figure;
h = imshow(K, []); %h = imshow(J, []);
impixelinfo
else
if ~isvalid(h)
break;
end
h.CData = K; %h.CData = J;
end
pause(0.05);
end
fclose(f);
imwrite(uint16(J+2^15), 'J.tif'); % Write J as uint16 image.
imwrite(K, 'K.png'); % Write K image (last frame).
% Colorize the IR video frame with "false colors".
function J = ColorizeIr(I)
% The palette apply different IR manufacture - don't expect the result to resemble OPTRIS output.
% https://groups.google.com/g/flir-lepton/c/Cm8lGQyspmk
colormapIronBlack = uint8([...
255, 255, 255, 253, 253, 253, 251, 251, 251, 249, 249, 249, 247, 247,...
247, 245, 245, 245, 243, 243, 243, 241, 241, 241, 239, 239, 239, 237,...
237, 237, 235, 235, 235, 233, 233, 233, 231, 231, 231, 229, 229, 229,...
227, 227, 227, 225, 225, 225, 223, 223, 223, 221, 221, 221, 219, 219,...
219, 217, 217, 217, 215, 215, 215, 213, 213, 213, 211, 211, 211, 209,...
209, 209, 207, 207, 207, 205, 205, 205, 203, 203, 203, 201, 201, 201,...
199, 199, 199, 197, 197, 197, 195, 195, 195, 193, 193, 193, 191, 191,...
191, 189, 189, 189, 187, 187, 187, 185, 185, 185, 183, 183, 183, 181,...
181, 181, 179, 179, 179, 177, 177, 177, 175, 175, 175, 173, 173, 173,...
171, 171, 171, 169, 169, 169, 167, 167, 167, 165, 165, 165, 163, 163,...
163, 161, 161, 161, 159, 159, 159, 157, 157, 157, 155, 155, 155, 153,...
153, 153, 151, 151, 151, 149, 149, 149, 147, 147, 147, 145, 145, 145,...
143, 143, 143, 141, 141, 141, 139, 139, 139, 137, 137, 137, 135, 135,...
135, 133, 133, 133, 131, 131, 131, 129, 129, 129, 126, 126, 126, 124,...
124, 124, 122, 122, 122, 120, 120, 120, 118, 118, 118, 116, 116, 116,...
114, 114, 114, 112, 112, 112, 110, 110, 110, 108, 108, 108, 106, 106,...
106, 104, 104, 104, 102, 102, 102, 100, 100, 100, 98, 98, 98, 96, 96,...
96, 94, 94, 94, 92, 92, 92, 90, 90, 90, 88, 88, 88, 86, 86, 86, 84, 84,...
84, 82, 82, 82, 80, 80, 80, 78, 78, 78, 76, 76, 76, 74, 74, 74, 72, 72,...
72, 70, 70, 70, 68, 68, 68, 66, 66, 66, 64, 64, 64, 62, 62, 62, 60, 60,...
60, 58, 58, 58, 56, 56, 56, 54, 54, 54, 52, 52, 52, 50, 50, 50, 48, 48,...
48, 46, 46, 46, 44, 44, 44, 42, 42, 42, 40, 40, 40, 38, 38, 38, 36, 36,...
36, 34, 34, 34, 32, 32, 32, 30, 30, 30, 28, 28, 28, 26, 26, 26, 24, 24,...
24, 22, 22, 22, 20, 20, 20, 18, 18, 18, 16, 16, 16, 14, 14, 14, 12, 12,...
12, 10, 10, 10, 8, 8, 8, 6, 6, 6, 4, 4, 4, 2, 2, 2, 0, 0, 0, 0, 0, 9,...
2, 0, 16, 4, 0, 24, 6, 0, 31, 8, 0, 38, 10, 0, 45, 12, 0, 53, 14, 0,...
60, 17, 0, 67, 19, 0, 74, 21, 0, 82, 23, 0, 89, 25, 0, 96, 27, 0, 103,...
29, 0, 111, 31, 0, 118, 36, 0, 120, 41, 0, 121, 46, 0, 122, 51, 0, 123,...
56, 0, 124, 61, 0, 125, 66, 0, 126, 71, 0, 127, 76, 1, 128, 81, 1, 129,...
86, 1, 130, 91, 1, 131, 96, 1, 132, 101, 1, 133, 106, 1, 134, 111, 1,...
135, 116, 1, 136, 121, 1, 136, 125, 2, 137, 130, 2, 137, 135, 3, 137,...
139, 3, 138, 144, 3, 138, 149, 4, 138, 153, 4, 139, 158, 5, 139, 163,...
5, 139, 167, 5, 140, 172, 6, 140, 177, 6, 140, 181, 7, 141, 186, 7,...
141, 189, 10, 137, 191, 13, 132, 194, 16, 127, 196, 19, 121, 198, 22,...
116, 200, 25, 111, 203, 28, 106, 205, 31, 101, 207, 34, 95, 209, 37,...
90, 212, 40, 85, 214, 43, 80, 216, 46, 75, 218, 49, 69, 221, 52, 64,...
223, 55, 59, 224, 57, 49, 225, 60, 47, 226, 64, 44, 227, 67, 42, 228,...
71, 39, 229, 74, 37, 230, 78, 34, 231, 81, 32, 231, 85, 29, 232, 88,...
27, 233, 92, 24, 234, 95, 22, 235, 99, 19, 236, 102, 17, 237, 106, 14,...
238, 109, 12, 239, 112, 12, 240, 116, 12, 240, 119, 12, 241, 123, 12,...
241, 127, 12, 242, 130, 12, 242, 134, 12, 243, 138, 12, 243, 141, 13,...
244, 145, 13, 244, 149, 13, 245, 152, 13, 245, 156, 13, 246, 160, 13,...
246, 163, 13, 247, 167, 13, 247, 171, 13, 248, 175, 14, 248, 178, 15,...
249, 182, 16, 249, 185, 18, 250, 189, 19, 250, 192, 20, 251, 196, 21,...
251, 199, 22, 252, 203, 23, 252, 206, 24, 253, 210, 25, 253, 213, 27,...
254, 217, 28, 254, 220, 29, 255, 224, 30, 255, 227, 39, 255, 229, 53,...
255, 231, 67, 255, 233, 81, 255, 234, 95, 255, 236, 109, 255, 238, 123,...
255, 240, 137, 255, 242, 151, 255, 244, 165, 255, 246, 179, 255, 248,...
193, 255, 249, 207, 255, 251, 221, 255, 253, 235, 255, 255, 24]);
lutR = colormapIronBlack(1:3:end);
lutG = colormapIronBlack(2:3:end);
lutB = colormapIronBlack(3:3:end);
% Convert I to uint8
I = im2uint8(I);
R = lutR(I+1);
G = lutG(I+1);
B = lutB(I+1);
J = cat(3, R, G, B);
end
更新:
Python 使用 OpenCV 的代码示例(未着色):
使用Python和OpenCV,我们可以跳过FFmpeg转换部分。
我们可以从 RAVI 文件中获取未解码的 RAW 视频,而不是将 RAVI 文件转换为 YUV 文件。
- 打开视频文件并设置
CAP_PROP_FORMAT
属性 以获取未解码的 RAW 视频:
cap = cv2.VideoCapture(ravi_file_name)
cap.set(cv2.CAP_PROP_FORMAT, -1) # Format of the Mat objects. Set value -1 to fetch undecoded RAW video streams (as Mat 8UC1).
读取视频帧(使用 ret, frame = cap.read()
)时,未解码的帧被读取为 uint8
个元素的“长”行向量。
- 将帧转换为
int16
类型,并重塑为cols
xrows
:
首先,我们必须将向量元素“查看”为int16
个元素(相对于uint8
个元素):frame.view(np.int16)
其次,我们必须将向量重塑为矩阵。
转换和整形代码:
frame = frame.view(np.int16).reshape(rows, cols)
完成 Python 代码示例:
import numpy as np
import cv2
ravi_file_name = 'Sequence_LED_Holder.ravi'
cap = cv2.VideoCapture(ravi_file_name) # Opens a video file for capturing
# Fetch undecoded RAW video streams
cap.set(cv2.CAP_PROP_FORMAT, -1) # Format of the Mat objects. Set value -1 to fetch undecoded RAW video streams (as Mat 8UC1). [Using cap.set(cv2.CAP_PROP_CONVERT_RGB, 0) is not working]
cols = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) # Get video frames width
rows = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) # Get video frames height
while True:
ret, frame = cap.read() # Read next video frame (undecoded frame is read as long row vector).
if not ret:
break # Stop reading frames when ret = False (after the last frame is read).
# View frame as int16 elements, and reshape to cols x rows (each pixel is signed 16 bits)
frame = frame.view(np.int16).reshape(rows, cols)
# It looks like the first line contains some data (not pixels).
# data_line = frame[0, :]
frame_roi = frame[1:, :] # Ignore the first row.
# Normalizing frame to range [0, 255], and get the result as type uint8 (this part is used just for making the data visible).
normed = cv2.normalize(frame_roi, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8U)
cv2.imshow('normed', normed) # Show the normalized video frame
cv2.waitKey(10)
cap.release()
cv2.destroyAllWindows()
注:
- 如果需要着色,您可以使用以下示例:Thermal Image Processing
每帧按像素保存 16 位值,低字节在前,高字节在后。要找到温度,您必须应用此公式:temp = (hi * 256.0 + lo) / 10.0 - 100.0
.
使用低值,您可以创建灰度图像。我成功地将这种方法用于旧的 Pi-160 Optris 相机。然而,对于新的 PI-450,由于 PI Connect 现在不支持二进制导出,因此更加困难。
我用 Ffmpeg 测试了解决方案,但没有成功。你得到一个16位的数据文件,但是实际数据偏移量不对,导致温度异常。
你成功了吗?
二进制读取示例:
在大多数使用Ffmpeg处理的ravi文件中,原始图像的第一行都有非像素值。 第一行存储一些冗余信息,例如图像宽度和高度。 我们必须跳过对应于图像宽度的这一行。由于数据值是 16 位,我们必须乘以 2,才能得到二进制数据的准确偏移量。我们还必须计算图像的确切大小:imageLength = Frame size - (image width * 2)。 在另一种情况下,数据来自文件的开头,我们可以使用帧大小 (w * h * 2) 来复制二进制数据并更新偏移量。 要知道是否有必要计算数据偏移量,我们只需查看图像高度即可。如果这个值是奇数,那意味着有一个补充的第一行,因此我们应用校正。如果值为偶数,则不对数据偏移量进行修正。
解析原始 ravi 文件时也是如此。首先我们要找到movi标签在文件中的偏移量。如果 movi 标签后面跟着 ix00 标签,这意味着我们在一系列值之后给出了偏移量和每个帧相对于 movi 标签偏移量的大小。真实数据在文件中的其他地方。如果 ix00 标签不存在,这意味着数据就在 movi chunck 内部,在 00db 标志之后,并且是逐帧的。在最后一种情况下,我们还可以查找 idx1 标签(在文件末尾),它可以访问每个帧的确切偏移量和大小。
这两种方法都允许以灰度或伪彩色表示相当正确的图像,但 libirimager 工具包提供的温度公式 (float t = (float)data[i] / 10.f - 100.f) 不正确,我不明白为什么,因为当我使用 PI-160 相机生成的原始数据时公式是正确的。
Fmmpeg test
我找到了另一种方法。在最近的 ravi Optris 文件中,我们可以在 INFO 块中获取温度范围。然后,很容易找到原始数据中的最小值和最大值,并根据温标进行插值。 with correct temperatures