如何从 'ravi' 文件中获取数据?

How can I get data from 'ravi' file?

ravi 文件是什么:

A RAVI file is a video file created by thermal imaging software, such as Micro-Epsilon TIM Connect or Optris PIX Connect. It contains video captured by thermal cameras and is saved in a format similar to the Audio Video Interleave (.AVI) format. RAVI files also store radiometric information, such as temperature and measurement area information collected by the thermal camera.

我的问题:

我必须处理 ravi 文件中的数据。我需要像素的温度值(或者帧的最高温度对我来说足够了)。我想检查某个框架的最高温度。最终结果将是一份报告,其中包含帧上的最大温度值(这将是一个图表)。使用 Micro-Epsilon TIM ConnectOptris PIX Connect 工具很容易检查和处理,但我无法使用它们(我必须自己编写一个)。

我的问题:

注:

如果我用 OpenCV 解析它或用媒体播放器播放,我可以看到一些流。但是我不确定如何获得温度...

CV2代码:

import cv2

cap = cv2.VideoCapture("my_test.ravi")

if not cap.isOpened():
    print("Error opening video stream or file")

while cap.isOpened():
    ret, frame = cap.read()
    if ret:
        cv2.imshow('Frame', frame)
    if cv2.waitKey(25) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()

获取流:

(我在简单的媒体播放器中也看到相同的 "pink and green" 流。)

官方软件中的流:

ravi HexEditor 中的文件:

我找到了一个关于 AVI video format 的网站。您可以在下面看到我的文件中的一些行,也许它可以提供帮助。

00000000  52 49 46 46  F8 B1 C6 3F   41 56 49 20  4C 49 53 54     RIFF...?AVI LIST
00000010  CC 7F 00 00  68 64 72 6C   61 76 69 68  38 00 00 00     ....hdrlavih8...
00000020  12 7A 00 00  44 FF DD 00   00 02 00 00  10 08 00 00     .z..D...........
00000030  44 6D 00 00  00 00 00 00   01 00 00 00  08 65 09 00     Dm...........e..
00000040  80 02 00 00  E1 01 00 00   00 00 00 00  00 00 00 00     ................
00000050  00 00 00 00  00 00 00 00   4C 49 53 54  74 7E 00 00     ........LISTt~..
00000060  73 74 72 6C  73 74 72 68   38 00 00 00  76 69 64 73     strlstrh8...vids
00000070  59 55 59 32  00 00 00 00   00 00 00 00  00 00 00 00     YUY2............
00000080  B4 C4 04 00  80 96 98 00   00 00 00 00  A4 50 00 00     .............P..
00000090  08 65 09 00  00 00 00 00   00 00 00 00  00 00 00 00     .e..............
000000A0  00 00 00 00  73 74 72 66   28 00 00 00  28 00 00 00     ....strf(...(...
000000B0  80 02 00 00  E1 01 00 00   01 00 10 00  59 55 59 32     ............YUY2
000000C0  00 65 09 00  60 00 00 00   60 00 00 00  00 00 00 00     .e..`...`.......
000000D0  00 00 00 00  69 6E 64 78   F8 7D 00 00  04 00 00 00     ....indx.}......
000000E0  06 00 00 00  30 30 64 62   00 00 00 00  00 00 00 00     ....00db........

测试资料:

如果您从 http://infrarougekelvin.com/en/optris-logiciel-eng/ 站点下载 PIX Connect Rel. 3.6.3046.0 Software,您可以在 zip 内的 "Samples" 文件夹中找到几个 ravi 文件。

来自官方文档的附加信息:

Software for thermoIMAGER TIM Infrared camera documentation

视频序列既可以保存为辐射文件 (RAVI) 也可以保存为非辐射文件 (AVI)。 RAVI文件 包含所有温度以及测量区域信息。

如果是辐射记录,请参阅第 1 章。 5.6.2、未激活图片将保存为标准AVI文件 只包含颜色信息。稍后将 RAVI 文件转换为 AVI 文件,反之亦然 可能

更新:

我试过使用PyAV模块获取数据。该模块能够处理 yuyv422 格式。我得到了相同的 "green-pink" 流,但无法从中获取温度...

使用代码:

# coding=utf-8
import av
import os


ravi_path = "Brake disc.ravi"
container = av.open(ravi_path)
stream = container.streams.video[0]
stream.codec_context.skip_frame = 'NONKEY'
tgt_path = "frames"
if not os.path.isdir(tgt_path):
    os.makedirs(tgt_path)
for frame in container.decode(stream):
    tgt_filename = os.path.join(tgt_path, 'frame-{:09d}.jpg'.format(frame.pts))
    print(frame, tgt_filename)
    frame.to_image().save(tgt_filename, quality=80)

脚本输出:

>>> python ravi_test2.py 
(<av.VideoFrame #0, pts=0 yuyv422 160x121 at 0x7f501bfa8598>, 'frames/frame-000000000.jpg')
(<av.VideoFrame #1, pts=1 yuyv422 160x121 at 0x7f501bfa8600>, 'frames/frame-000000001.jpg')
(<av.VideoFrame #2, pts=2 yuyv422 160x121 at 0x7f5018e0fdb8>, 'frames/frame-000000002.jpg')
(<av.VideoFrame #3, pts=3 yuyv422 160x121 at 0x7f501bfa8598>, 'frames/frame-000000003.jpg')
(<av.VideoFrame #4, pts=4 yuyv422 160x121 at 0x7f501bfa8600>, 'frames/frame-000000004.jpg')
(<av.VideoFrame #5, pts=5 yuyv422 160x121 at 0x7f5018e0fdb8>, 'frames/frame-000000005.jpg')

我不知道你的相机品牌,但希望视频文件包含原始传感器值作为 16 位无符号整数,可能只是在视频中命名为 YUV422 header,因为它们符合同样每像素 16 位。

您可以通过特定的 non-linear 校准曲线将这些值转换为实际温度值。如果 RAVI-format 是单一文件格式(与某些带有 raw-AVI + 校准 table 的遗留 IR-cameras 相对),那么您应该找到几个浮点常量的位置 and/or table,组成等式

可以对逻辑进行逆向工程,但最好向制造商询问正确的方程式。例如,您在互联网上找到的可能只是校准曲线的旧版本。大多数制造商都随设备一起提供校准库。有些 out-of-product 自行车设备可能很难协商,但您至少应该获得有关该主题的白皮书。

如果您使用 OpenCV,您需要读取原始的 YUV422 帧(16bpp,而不是 24bpp)并在应用查找之前将它们的上下文重新解释为 uint16 table。

// sample C++ code employing private content of OpenCV library
// Particularly container_avi.private.hpp and container_avi.cpp
void mainactual()
{
    cv::AVIReadContainer reader;
    reader.initStream(cv::String("C:/tello/intro2.avi"));
    cv::frame_list frames;
    // initializes the stream
    reader.parseRiff( frames );
    std::cout << "Number of frames: " << frames.size() << std::endl;
    int w=reader.getWidth();
    int h=reader.getHeight();
    std::cout << "size " << cv::Size(w,h) << std::endl;
    // a frame in the middle
    cv::frame_iterator it=frames.begin() + frames.size()/2;

    std::vector< char> data = reader.readFrame( it );
    // In your case, data here is supposed to be 
    // uncompressed YUV422 which is w * h * 2 bytes per frame
    // You might need to modify 
    // bool AVIReadContainer::parseStrl(char stream_id, Codecs codec_)
    // to accept your FCC
    //
    //
    //if ( data.size()!=w*h*2 )
    //{
    //  // error
    //}

    // My video is MJPEG, so I'm confident to just to decode it
    cv::Mat img = cv::imdecode( data, cv::IMREAD_UNCHANGED );
    cv::imshow("image", img ); // looks fine
    cv::waitKey( 0 );
    reader.close();
}

编辑:经过测试的制动器 disk.ravi,如下所示。修改解析器以接受未压缩的 YUV2 格式并根据 https://docs.microsoft.com/en-us/previous-versions/windows/desktop/api/Aviriff/ns-aviriff-avioldindex

添加 hack

dwOffset

Specifies the location of the data chunk in the file. The value should be specified as an offset, in bytes, from the start of the 'movi' list; however, in some AVI files it is given as an offset from the start of the file.

不确定拼字游戏是什么,但看起来像刹车盘。

    cv::Mat img;
    if ( data.size()==w*h*2 )
    {
        std::cout << data.size() << " " << w*h*2 << std::endl;
        cv::Mat t( h, w, CV_16UC1, &data[0] );
        // img(y,x) = (float)t(y,x)/10.0 - 100.0
        t.convertTo( img, CV_32F, 0.1, -100.0 );
    }else
        return;
    double mi,ma;
    cv::minMaxLoc( img, &mi, &ma );
    std::cout << "range: [" << mi << ", " << ma << "]" << std::endl;
    cv::Mat gray;
    img.convertTo( gray, CV_8U ); // [0, 255] range saturated
    cv::Mat bigger;
    cv::resize(gray,bigger,cv::Size(4*w,4*h),0,0,cv::INTER_LINEAR );
    cv::Mat jet;
    cv::applyColorMap( bigger, jet, cv::COLORMAP_JET );
    cv::imshow("image", jet ); // looks fine
    cv::waitKey( 0 );
    reader.close();

我从仅红外摄像机的一些镜头中发现了什么:

  • 帧有一个额外的第一行,用于保存每帧元数据。它应该被丢弃以供展示。
  • 图像数据缓冲区帧包含与温度相关的 16 位值(每个像素两个字节,低字节在前)。我可以用 <0.01C 残差拟合一个 4 阶多项式,但它不是完全线性的。它可能是对数的,而不是多项式的。
  • 请注意,根据 micro-epsilon 文档,最多可能有 3 行元数据。该格式可能与相机有关,我无法解释第一行元数据。

我不知道来自同一个 .ravi 的所有帧是否具有相同的校准。 校准元数据行从一帧到下一帧非常相似,只有三个 16 位值略有不同(采用两个不同的值),并且每帧一个递增 1。

float t = (float)data[i] / 10.f - 100.f 公式并没有真正产生匹配的温度。

结论:只有使用提供的libirimager才能计算出标定元数据。它也可能依赖于供应商...