System designHow-to

OpenCV Mat int64 data type: two ways to store 64-bit values

OpenCV has no int64 Mat depth. Store 64-bit integer data as CV_64F or CV_32SC2, with the exactness limits and tested code for each type.

LumaSpec Vision8 min read Share on LinkedIn

On this page 8 sections
  1. 01Why can't a cv::Mat store int64 directly?
  2. 02Where does 64-bit integer data come from in a vision system?
  3. 03Which Mat type keeps your int64 values exact?
  4. 04How do you copy int64 data into a CV_64F Mat?
  5. 05How do you keep all 64 bits with CV_32SC2 and cv::extractChannel?
  6. 06How do you prove no bits were lost?
  7. 07Which method should you use for your data?
  8. 08Questions

A frame ID, a device timestamp and an encoder count all arrive as 64-bit integers. Sooner or later one of those lists has to cross a cv::Mat for sorting, indexing or a custom kernel. OpenCV has no 64-bit signed integer depth, so the compiler rejects the obvious int64 Mat declaration. There are exactly two reliable routes: copy the values into CV_64F doubles, which is exact up to 2^53, or wrap the same bytes as CV_32SC2 and pull a 32-bit lane out with cv::extractChannel, which is exact for every value. This guide gives working code for both, the arithmetic that proves the round trip on a real timestamp, and the decision rule that picks between them.

Why can't a cv::Mat store int64 directly?

The Mat depth list in OpenCV 4.x has eight entries: CV_8U, CV_8S, CV_16U, CV_16S, CV_32S, CV_32F, CV_64F and CV_16F. None of them is a 64-bit integer. The only 64-bit depth, CV_64F, is a double, so a CV_64SC1 type does not exist to declare.

The compiler rejects an int64 element type outright, and a hand-written type constant has no valid depth to build on. Forcing the bytes into a smaller element is worse: it splits or truncates the values with no error at compile time. That leaves two sound routes: convert the values to a supported depth, or reinterpret the bytes as a supported type. The sections below give both.

Where does 64-bit integer data come from in a vision system?

Most of it arrives as 64-bit GenICam integer nodes and 64-bit device timestamps. The GenICam standard defines integer nodes as 64-bit signed, GigE Vision devices stamp every frame with a 64-bit counter, and frame grabbers pass the same frame IDs and timestamps through with each image.

Your own code adds the same widths: encoder tallies, part-serial hashes and the int64 tensors that inference frameworks hand to the processing stage. Whenever one of those lists has to cross a cv::Mat, the missing depth bites. Multi-camera systems collect the timestamp and frame-ID lists through frame grabbers, which pass each camera's chunk fields through unchanged. Reading GenICam integer values by their numeric codes is its own topic: Set a GenICam enumeration by integer value: map numeric to symbolic.

AccessoriesFrame grabbers

CoaXPress, Camera Link and GigE cards

58 models · from $189.00

Which Mat type keeps your int64 values exact?

Three options work. CV_64F reproduces every int64 up to 2^53 (9,007,199,254,740,992) exactly. CV_32S holds any value up to 2,147,483,647. CV_32SC2, two 32-bit lanes per element, carries the full 64 bits of any value with no range limit at all.

The CV_64F ceiling comes from IEEE 754: a double carries a 53-bit mantissa, so every integer from -2^53 to +2^53 maps to a unique double and nothing beyond it does. Microsecond Unix timestamps sit near 1.75e15 today, about five times inside the ceiling. Nanosecond timestamps near 1.75e18 land about 195 times outside it. CV_32S fits only when the largest value stays inside 2,147,483,647: encoder counts on short gates often do, epoch times never. A wrong depth downstream throws the same errors as any wrong Mat type: Template matching incompatible image type: fix bit depth mismatch is the classic case.

Mat type Element Largest exact int64 Buffer
CV_64F 8-byte double ±9,007,199,254,740,992 (2^53) fresh copy, N × 8 bytes
CV_32S 4-byte int32 ±2,147,483,647 fresh copy, N × 4 bytes
CV_32SC2 2 × 4-byte int32 lanes every 64-bit value wraps the source, no copy

How do you copy int64 data into a CV_64F Mat?

Cast each element to double into a std::vector of doubles, then build a one-row Mat over that buffer. For the ten-value array 1, 2, 3, 4, 5, 7, 8, 9, 10, 11 the result is a 1 × 10 CV_64F Mat. The cast loses nothing because every value sits below 2^53.

  1. Put the values in a std::vector of int64_t, with the type declared.
  2. Cast each element to double in a second vector.
  3. Build the Mat over that buffer: one row, N columns, CV_64F.
  4. Read back with at as double, cast to int64_t, and compare against the source.
#include <opencv2/core.hpp>
#include <cstdint>
#include <vector>

// Source values: declared type and initial values.
std::vector<int64_t> src = {1, 2, 3, 4, 5, 7, 8, 9, 10, 11};

// Convert every element; exact below 2^53.
std::vector<double> buf(src.size());
for (size_t i = 0; i < src.size(); ++i)
    buf[i] = static_cast<double>(src[i]);

// One row, N columns, CV_64F, built over the converted buffer.
cv::Mat m(1, static_cast<int>(src.size()), CV_64F, buf.data());

// Test: each element must read back equal to the source.
for (int i = 0; i < m.cols; ++i)
    CV_Assert(static_cast<int64_t>(m.at<double>(0, i)) == src[static_cast<size_t>(i)]);

The three-argument constructor wraps buf without copying, so keep buf alive as long as m is in use; call m.clone() when the Mat must own its bytes.

How do you keep all 64 bits with CV_32SC2 and cv::extractChannel?

Wrap the int64 buffer in place as a single-column CV_32SC2 Mat, then read a lane out with cv::extractChannel and rebuild the value exactly. One source value becomes two int32 lanes: the low 32 bits in channel 0 and the high 32 bits in channel 1 on little-endian hardware.

The wrap works because a little-endian two's-complement machine stores an int64 as its low 4 bytes first. So channel 0 of element i holds the low 32 bits and channel 1 the high 32 bits. x86-64 and the ARM cores in vision controllers are little-endian, so the layout holds; a big-endian core swaps the lane order. One row per value keeps element i aligned across both channels. This is the same zero-copy pattern as Wrap an SDK frame buffer in an OpenCV cv::Mat without copying, and element-size mistakes are the failure family behind Why your 16-bit image buffer has half the expected pixels.

low = value mod 4294967296 high = floor(value / 4294967296) value = high × 4294967296 + low
#include <opencv2/core.hpp>
#include <cstdint>

// Source values: five 64-bit device timestamps in microseconds.
int64_t ts[5] = {1752145234567890, 1752145234568000,
                 1752145234568256, 1752145234568512,
                 1752145234568768};

// Wrap the same bytes as 5 x 1 CV_32SC2: two int32 lanes per value, no copy.
cv::Mat packed(5, 1, CV_32SC2, ts);

cv::Mat low, high;
cv::extractChannel(packed, low, 0);   // channel 0: low 32 bits (little-endian)
cv::extractChannel(packed, high, 1);  // channel 1: high 32 bits

// Test: rebuild each value and compare with the source.
for (int i = 0; i < packed.rows; ++i) {
    int64_t v = (static_cast<int64_t>(high.at<int32_t>(i)) << 32)
              | static_cast<uint32_t>(low.at<int32_t>(i));
    CV_Assert(v == ts[i]);
}

How do you prove no bits were lost?

Rebuild every value and compare it with the source in a loop guarded by CV_Assert. For the timestamp 1,752,145,234,567,890 µs the lane split is 407,953 × 4,294,967,296 + 441,262,802, which recomputes to the identical number. The same value also passes the below-2^53 check for CV_64F.

Work the arithmetic once by hand and let the assert loop check every element after that. The timestamp 1,752,145,234,567,890 µs is mid-2025 in Unix time. Dividing by 2^32 = 4,294,967,296 gives 407,953 with remainder 441,262,802, so channel 1 stores 407,953 and channel 0 stores 441,262,802. Rebuilding: 407,953 × 4,294,967,296 = 1,752,144,793,305,088, then 1,752,144,793,305,088 + 441,262,802 = 1,752,145,234,567,890, the original value bit for bit. The CV_64F route passes the same input because 1,752,145,234,567,890 is below 9,007,199,254,740,992. Switch the unit to nanoseconds and the value becomes 1,752,145,234,567,890,123, roughly 195 times the CV_64F ceiling. There the double rounds and the CV_32SC2 wrap becomes the only exact option.

Which method should you use for your data?

Pick by downstream use. Copy to CV_64F when OpenCV math runs on the values and all of them stay below 2^53. Wrap as CV_32SC2 when the values must come back bit-exact or exceed 2^53. Use a plain CV_32S when everything fits inside 2,147,483,647.

  • Values feed OpenCV math and all sit below 2^53: copy to CV_64F — sums, scaling and minMaxLoc run on 64F directly.
  • Values must return bit-exact, exceed 2^53, or arrive as uint64: wrap as CV_32SC2 and rebuild on read-back.
  • Every value fits inside 2,147,483,647: a plain CV_32S cast is the shortest route.

Downstream depth requirements decide as much as storage does: cv::applyColorMap demands an 8-bit source, as in applyColorMap src.dims == 2 error: feed it a valid 8-bit Mat, so plan the conversion where the Mat is built. In Python the storage problem mostly disappears because NumPy carries native int64, but OpenCV's Python binding does not convert data types for you, and DNN inputs are a known place where an int64 array is rejected, so convert explicitly and accept the same 2^53 ceiling on astype(numpy.float64).

If this bit you on a multi-camera line, the fix usually lives at the acquisition end: vision controllers keep the chunk data, the Mat code and the light timing on one machine, and the system configurator starts you at the extras step where the frame-grabber add-ins sit.

ComputersVision controllers

Vision computers and fanless industrial PCs

190 models · from $1,119.00

The systems topic page collects the rest of the Mat and buffer fixes.

Questions

How do I read one element back out of a packed CV_32SC2 Mat?

Skip extractChannel for single values: read element i of the one-column Mat as a Vec2i, where lane 0 is the low 32 bits and lane 1 the high 32 bits. Rebuild with (int64_t(high) << 32) | uint32_t(low), or extract both channels once when every element is needed.

Can I store uint64 buffers the same way?

Yes. The zero-copy CV_32SC2 wrap is identical: channel 0 holds the low 32 bits and channel 1 the high 32 bits on little-endian hardware. Treat the high lane as uint32_t when you rebuild values above 2^63 minus 1, because a signed cast would read them as negative.

Do any OpenCV builds support a CV_64S depth?

No released build has one. The depth set has stayed at 8-, 16- and 32-bit integers plus the float depths through every OpenCV 4.x release, and declaring a made-up depth number fails at compile time or misreads memory. Plan on the two workarounds in this guide for any 4.x version.

How far off can a nanosecond timestamp get in a CV_64F Mat?

Near 1.75e18 ns, the 2025 range in Unix time, neighbouring doubles are 256 ns apart. The value sits between 2^60 and 2^61, so the 53-bit mantissa leaves only 8 fractional bits there. The stored value can differ from the original by up to 128 ns; CV_32SC2 keeps it exact.

Why does my rebuilt value come out negative or wrong for large counts?

The low lane is stored as int32_t, and for values at or above 2^31 its bit pattern reads as negative. Casting that lane straight to int64_t sign-extends it and corrupts the rebuild. Cast the low lane to uint32_t first, as the loop in this guide does, and the OR assembles the correct 64-bit value.