macs_io

A minimal C++ library for reading and writing MACS images. The latest version can be found here. You can also download a test dataset.

Installation

To build the library you need:

Make based systems

$ cmake <path-to-macs_io> -DCMAKE_INSTALL_PREFIX=<optional_install_location>
$ make
$ make install

Visual Studio

$ cmake <path-to-macs_io>^
    -G"Visual Studio 15 2017 Win64"^
    -DCMAKE_PREFIX_PATH=C:\Qt\5.11.3\msvc2017_64\lib\cmake
# open macs_io.sln

Usage

To use the library, you can either use cmake

CMakeLists.txt
cmake_minimum_required(VERSION 2.8.12)
project(my_sample C CXX)
find_package(macs_io REQUIRED)
add_executable(my_sample main.cpp)
target_link_libraries(my_sample macs_io)

or simply add the source files macs_io.cpp and MacsMetaData.cpp to your project.

Examples

There are three examples included:

macs_decode

The sample macs_decode shows how to use macs_io to read a MACS image and write a jpg/tif/png. It takes the following options:

$ macs_decode -h
Usage:
macs_decode [options] in.macs out.jpg
where options are:

Stretch:
                stretch_min=0.0           stretch factor min
                stretch_max=1.0           stretch factor max
                gamma=1.0                 gamma correction

Devignetting:
                devignette_a=0.0          model based devignetting parameter
                devignette_b=0.0          model based devignetting parameter
                devignette_c=0.0          model based devignetting parameter
                devignette_offset=0       model based devignetting parameter
                devignette_factor=1.0     model based devignetting parameter

Color balance:
                color_balance_r=1.0       color balance parameter
                color_balance_g=1.0       color balance parameter
                color_balance_b=1.0       color balance parameter

Distortion:
                cx=w/2                    principal point
                cy=h/2                    principal point
                k1=0.0                    radial lens distortion parameter
                k2=0.0                    radial lens distortion parameter
                k3=0.0                    radial lens distortion parameter
$ macs_decode helgoland.macs helgoland.tif
$  macs_decode stretch_min=0 stretch_max=0.4
               helgoland.macs helgoland_stretch.tif
$ macs_decode stretch_min=0.1 stretch_max=0.53 gamma=0.5
              helgoland.macs helgoland_gamma.tif
$ macs_decode stretch_min=0.08 stretch_max=0.53 gamma=0.5
              devignette_a=-0.313252 devignette_b=-2.59249 devignette_c=2.2651
              helgoland.macs helgoland_devignetted.tif
_images/helgoland.jpg
_images/helgoland_stretch.jpg
_images/helgoland_gamma.jpg
_images/helgoland_devignetted.jpg
$ macs_decode img1.macs img1.jpg

macs_encode

which reads a jpg/tif/png and writes a macs image, with metadata. You need to pass the information if the input image is a single channel (gray) image or should be interpreted as a bayer mosaic.

$ macs_encode color=BGGR poster_raw.tif poster.macs
$ macs_decode poster.macs poster.tif
$ macs_decode poster.macs poster_cc.tif
          color_balance_g=0.9 color_balance_r=1.0 color_balance_b=1.3
_images/poster_raw.jpg
_images/poster.jpg
_images/poster_cc.jpg

macs_info

which prints the basic metadata of a macs image

$ macs_info ./20180815-235854_YukonCoast_MACS/33552_Cam-NIR-50/02135_032746170_600.macs
MACS Image info:
  File name:  "02135_032746170_600.macs"
  Dimensons:  4864 x 3232
  Format:  "Mono12Packed"
  Pose event from "2018-08-15 22.14.24.127"
    Lat: 69.5177
    Lon: -139.093
    Alt: 391.843
    Roll: 0.875793 degrees.
    Pitch: 2.57359 degrees.
    Yaw: 23.8121 degrees.
    Vel: 229.041 km/h

Meta Data:
  camVendor "DLR-OS"
  camModel "hr16070MFLGEC"
  camName "Cam-NIR-50"
  camSerial "33552"
  camMAC "ac:4f:fc:00:51:f4"
  camIP "192.168.101.236"
  camFirmware "1.6.5"
  affix ""

Theory

Lens distortion

We use the radial lens distortion model commonly used in computer vision.

\[\begin{split}{\displaystyle x_{\mathrm {u} }=x_{\mathrm {c} }+{\frac {x_{\mathrm {d} }-x_{\mathrm {c} }}{1+K_{1}r^{2}+K_{2}r^{4}+K_{3}r^{6} }}} \\ \\ {\displaystyle y_{\mathrm {u} }=y_{\mathrm {c} }+{\frac {y_{\mathrm {d} }-y_{\mathrm {c} }}{1+K_{1}r^{2}+K_{2}r^{4}+K_{3}r^{6} }}}\end{split}\]

Where

  • \((x_{\mathrm {d} },\ y_{\mathrm {d} }) =\) distorted image point as projected on image plane using specified lens,

  • \((x_{\mathrm {u} },\ y_{\mathrm {u} }) =\) undistorted image point as projected by an ideal pinhole camera,

  • \((x_{\mathrm {c} },\ y_{\mathrm {c} }) =\) distortion center,

  • \(K_{\mathrm {n} } =\) radial distortion coefficient, and

  • \({\displaystyle {r = \sqrt {(x_{\mathrm {d} }-x_{\mathrm {c} })^{2}+(y_{\mathrm {d} }-y_{\mathrm {c} })^{2}}}}\)

Sometimes there is a need to convert from a different photogrammetric parametrization (like Inpho, or Pictran). Here are some code snippets to perform that conversion.

Converting the Inpho distortion model to the CV model

# Input in Inpho format

w = 7920
h = 6002

# pixel size in mm
ps_mm = 4.6/1000

# principal point in mm
cx_mm = 0.306176
cy_mm = 0.160448

inpho_k1 = -1.476649e-05
inpho_k2 = -3.085708e-08
inpho_k3 = 0
# Inpho to CV
cx_px = w/2 + cx_mm/ps_mm
cy_px = h/2 - cy_mm/ps_mm

k1 = inpho_k1*ps_mm**2
k2 = inpho_k2*ps_mm**4
k3 = inpho_k3*ps_mm**6

(cx_px, cy_px), (k1,k2,k3)

((4026.56, 2966.12), (-3.124589284e-10, -1.3816121798848e-17, 0.0))

Converting the Pictran distortion model to the CV model

# Input in Pictran format
w = 7920
h = 6002

# pixel size in mm
ps_mm = 4.6/1000

# principal point in mm
x0 = 0.306176
y0 = 0.160448

A1 = -1.476649e-05
A2 = -3.085708e-08
#Pictran to CV
cx_px = w/2 + x0/ps_mm;
cy_px = h/2 - y0/ps_mm;

k1 = A1*ps_mm**2
k2 = A2*ps_mm**4
k3 = 0.0

(cx_px, cy_px), (k1,k2,k3)

((4026.56, 2966.12), (-3.124589284e-10, -1.3816121798848e-17, 0.0))

Devignetting

We use a parametric devignetting function which multiplies each pixel by a gain function which is defined as follows:

\[{\displaystyle {g(r) = 1 + a r^2 + b r^4 + cr^6 }}\]

where

\[{\displaystyle {r = \frac{\sqrt {(x-\bar{x})^{2}+(y-\bar{y})^{2}}}{\sqrt {\bar{x}^{2}+\bar{y}^{2}}}}}\]

is the normalized radius of \((x, y)\), i.e., the Euclidean distance of \((x, y)\) from the image center \((\bar{x}, \bar{y})\), divided by the image diagonal. Thus, \(r = 0\) in the image center and \(r = 1\) in each of the four corners. [Rohlfing]

API

The entire interface is contained in macs_io.h:

namespace MACS
{
    enum class PixelFormat {
        Mono12Packed    = 0x010C0047,
        BayerGR12Packed = 0x010C0057,
        Mono16          = 0x01100007,
        BayerGR16       = 0x0110002E,
        INVALID         = 0
    }; // see GenICam SFNC V2.2 #4.30 / PFNC V2.0  (http://www.emva.org/wp-content/uploads/PFNC.h)
    
    QString pixelformatToString(PixelFormat format) ;

    struct PoseEvent
    {
        QDateTime time;
        double roll=0, pitch=0, yaw=0;
        double lat=0, lon=0, alt=0;
        double veln=0, vele=0, velup=0; // Velocity north east up m/s

        bool isValid()const{return time.isValid();}
    };
    QDataStream &operator>>(QDataStream &in, PoseEvent &poseEvent);
    QDataStream &operator<<(QDataStream &out, const PoseEvent &poseEvent);
    
    struct CorrectionOptions
    {
        struct {
            double gamma = 1.0;
            double min = 0.0, max = 1.0;
        }stretch;

        struct{
            // Formula (12) in http://www.stanford.edu/~rohlfing/2012-rohlfing-single_image_vignetting_correction.pdf
            uint16_t offset=0;
            double factor = 1.0;
            double a = 0.0, b = 0.0, c = 0.0;
            double cx = 0.0, cy = 0.0;
        }devignetting;
        
        struct{
            double r = 1.0, g = 1.0, b = 1.0;
        }color_balance;

        struct {
            double cx_px = -1.0, cy_px = -1.0;
            double k1 = 0.0, k2 = 0.0, k3 = 0.0;
        }distortion;
        
        bool convertTo8bit = true;
    };
    struct Image
    {
        QSize size;

        QByteArray data;
        PixelFormat pixelFormat = PixelFormat::INVALID;
        int pitch = 0;

        QString fileName;
        MacsMetaData meta;
        PoseEvent georef;
        
        bool isValid()const;
        int bitDepth()const; //<! 12 or 16

        cv::Mat getCorrectedImage(const CorrectionOptions& options = {}) const; //<! returns a 16 bit 1 or 3 channel image
        QImage to8Bit(const CorrectionOptions& options = {}) const;
        bool exportImage(const QString& fileName, const CorrectionOptions& options = {}) const;

        QString errorMsg;

        bool load(const QString& fileName);
        bool save(const QString& fileName, bool preview = false, bool compression = false) const;
    };
}

Bibliography

Rohlfing

T. Rohlfing, Single-Image Vignetting Correction by Constrained Minimization of log-Intensity Entropy