Add Plotting Support For OpenCV C++

Description

Matlab Plotting Library, known as MPL, is widely used in areas like scientific research and industrial simulation. It's especially useful when it comes to plotting diagrams side by side for purposes like comparison analysis. While MPL is written in Python, Matplotlib for C++ translates a set of MPL in way of corresponding wrappers in C++ APIs, making it possible for C++ developers to draw MPL style diagrams in C++.

This article describes the overall steps required to add MPL capability in C++ code.

Prerequisites

  • macOS Catalina version 10.15.7 (19H524)
  • miniConda3 version 4.10.1 (optional)

Notes: MiniConda is used for packages management, it's suggested but not mandatory if you have your preferred package management system, such as brew. So going forward, almost all the dependant libraries will be downloaded in and referred from the conda environment. In addition, two env variables CONDA_HOME and OPENCV_DIR are set and exported, you will see them throughout the article, change them as required to fit your needs. Note also that Conda will create and export an env variable named CONDA_PREFIX, which points to the active Conda environment.

export OPENCV_DIR=/Users/nling/opencv/installation/OpenCV-master-debug
export CONDA_HOME=/Users/nling/opt/miniconda3

Environments

  1. Install Annoconda3 (skip if done)
    Some useful Conda commands:
  • Check Conda version.
conda --version
  • Check created Conda environments.
    below are some sample outputs, the asterisk sign (*) indicates the current environment you are working on, base is the default environment.
base                  *  /Users/nling/opt/miniconda3
cling                    /Users/nling/opt/miniconda3/envs/cling
kotlin                   /Users/nling/opt/miniconda3/envs/kotlin
xeus-cling               /Users/nling/opt/miniconda3/envs/xeus-cling
xeus-cling-dev           /Users/nling/opt/miniconda3/envs/xeus-cling-dev
  • Work on or exit a Conda env.
conda activate xeus-cling-dev
conda deactivate
  • Check available Jupyter virtual kernels (required Jupyter being installed in advance)
// conda install jupyterlab -c conda-forge
jupyter kernelspec list
  1. Install matplotlib and numpy (in the default base environment, changed as necessary)
(base) ➜  ~ conda install matplotlib numpy -c conda-forge

Run the below command to double-check they are properly installed.

(base) ➜  ~ conda list | egrep "numpy|matplotlib"

Sample output

matplotlib                3.4.2            py39h6e9494a_0    conda-forge
matplotlib-base           3.4.2            py39hb07454d_0    conda-forge
matplotlib-inline         0.1.2              pyhd8ed1ab_2    conda-forge
numpy                     1.20.2           py39h4b4dc7a_0
numpy-base                1.20.2           py39he0bd621_0
  1. Download source code of matplotlibcpp.h and (optionally) place it somewhere typically in the search path for library headers, for example.
cp matplotlibcpp.h ${OPENCV_DIR}/include/opencv4
  1. Write demo code to plot a simple line figure.
#include "matplotlibcpp.h"
#include <vector>

namespace plt = matplotlibcpp;

int main() {
  std::vector<double> y = {1, 3, 2, 4};
  plt::plot(y);
  plt::show();
}
  1. Compile the demo code with clang
/usr/bin/clang++  MatplotDemo.cpp                                       \
-o MatplotDemo                                                          \
-std=c++17                                                              \
-stdlib=libc++                                                          \
-g                                                                      \
-I${OPENCV_DIR}/include/opencv4                                         \
-I${CONDA_HOME}/include/python3.9                                       \
-I${CONDA_HOME}/lib/python3.9/site-packages/numpy/core/include          \
-L${OPENCV_DIR}/lib                                                     \
-L${CONDA_HOME}/lib                                                     \
-lpython3.9                                                             \
-lopencv_core                                                           \
-lopencv_imgproc                                                        \
-lopencv_imgcodecs                                                      \
-lopencv_highgui                                                        \
-Wl,-rpath,${OPENCV_DIR}/lib                                            \
-Wl,-rpath,${CONDA_HOME}/lib  

Options:

  • -std=c++17, compile for standard ISO C++ 2017.
  • -stdlib=libc++, specify c++ standard library, options can be libstdc++ and libc++.
  • -g, generate and control debug info outputs.
  • -o, specify the name and location of the executable file.
  • -I, specify the location to search for header files.
  • -L, specify the location to search for libraries to be linked with.
  • -l, specify library name to be linked with.
  • -W, pass comma-separated arguments to the linker. Here -rpath,/Users/nling/opt/anaconda3/lib is passed to the linker so the final executable is able to load and link with the target libraries at run-time.
  1. Run the demo executable and you should be able to see onscreen the plotted demo figure.


    MatplotDemo

Show cv::Mat-formmetd OpenCV image

The matplotlibcpp.h tool comes with support for displaying Matlab-style images, see the sample code imshow for reference. Moreover, it provides supports for displaying images represented as cv::Mat data structure (requires a macro named WITH_OPENCV be defined first).

Here is a simple example that categorizes and extracts each individual character, using CCA(Connected Component Analysis) algorithm, from the "TRUTH" image shown as follows.

Original TRUTH Image

The sample code that employees connectedComponents to detect labels and plots the connected labels in a single figure.

#define WITH_OPENCV

#include <iostream>

#include <matplotlibcpp.h>

namespace plt = matplotlibcpp;

using namespace cv;
using namespace std;

int main(int argc, char const *argv[])
{
    Mat img = imread("truth.png", IMREAD_GRAYSCALE);
    Mat imgThreshed;
    threshold(img, imgThreshed, 127, 255, THRESH_BINARY);

    Mat imgLabels;
    int nComponents = connectedComponents(imgThreshed, imgLabels);
    cout << "nComponents:" << nComponents << endl;

    double minVal, maxVal;
    Point minLoc, maxLoc;
    minMaxLoc(imgLabels, &minVal, &maxVal, &minLoc, &maxLoc);
    cout << "minVal:" << minVal << ", loc:" << minLoc  << endl;
    cout << "maxVal:" << maxVal << ", loc:" << maxLoc  << endl;
    imgLabels.convertTo(imgLabels, CV_8U);

    Mat coloredLabels = imgLabels.clone();
    coloredLabels = 255 * (coloredLabels - minVal) / (maxVal - minVal);
    applyColorMap(coloredLabels, coloredLabels, COLORMAP_JET);
    plt::figure();
    plt::title("Colored Image Segments");
    plt::imshow(coloredLabels);
    // plt::save("colored_image_segments.png");

    plt::figure();
    plt::title("Plot Connected Labels");
    plt::subplot(2, 3, 1);
    plt::imshow(imgLabels == 0);
    plt::subplot(2, 3, 2);
    plt::imshow(imgLabels == 1);
    plt::subplot(2, 3, 3);
    plt::imshow(imgLabels == 2);
    plt::subplot(2, 3, 4);
    plt::imshow(imgLabels == 3);
    plt::subplot(2, 3, 5);
    plt::imshow(imgLabels == 4);
    plt::subplot(2, 3, 6);
    plt::imshow(imgLabels == 5);

    // auto-adjust spacing between subplots
    plt::tight_layout();    
    // map<string, double> params = {
    //     {"left",   0.125},
    //     {"bottom", 0.11 },
    //     {"right",  0.9  },
    //     {"top",    0.88 },
    //     {"wspace", 0.2  },
    //     {"hspace", 0.8  }
    // };
    // plt::subplots_adjust(params);
    // plt::subplot_tool();

    plt::show();

    // save after being shown to avoid spacing issue.
    // plt::save("plot_connected_labels.png");

    return 0;
}

colored_image_segments.png
Plot Connected Labels

Possible Problems and Solutions

  1. Incompatible library version
(base) ➜  MatplotDemo MatplotDemo
dyld: Library not loaded: /usr/local/opt/glib/lib/libglib-2.0.0.dylib
  Referenced from: /usr/local/opt/harfbuzz/lib/libharfbuzz.0.dylib
  Reason: Incompatible library version: libharfbuzz.0.dylib requires version 6801.0.0 or later, but libglib-2.0.0.dylib provides version 6601.0.0
  1. Non-GUI backend issue
    “UserWarning: Matplotlib is currently using agg, which is a non-GUI backend, so cannot show the figure.” when plotting figure with pyplot on Pycharm
    • Solution1: change backend via a call to plt::backend("tkagg");
    • Solution2: Add backend config backend : tkAgg in file ~/.matplotlib/matplotlibrc.

Solution1 solved the non-GUI backend issue meanwhile created following Error loading module, Solution2 didn't work.

By the way, MPL works fine in pure Python script.

(base) ➜  MatplotDemo workon OpenCV-master-py3
(OpenCV-master-py3) (base) ➜  MatplotDemo python
Python 3.9.5 (default, May  4 2021, 03:33:11)
[Clang 12.0.0 (clang-1200.0.32.29)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import matplotlib.pyplot as plt
>>> y = [1, 3, 2, 4]
>>> plt.plot(y)
>>> plt.show()
  1. Error loading module
(base) ➜  MatplotDemo MatplotDemo
libc++abi.dylib: terminating with uncaught exception of type std::runtime_error: Error loading module matplotlib.pyplot!
[1]    98996 abort      MatplotDemo

The above issues were ALL caused due to below mistakenly exported library path /Users/nling/opt/anaconda3/lib, remove or comment out the DYLD_LIBRARY_PATH sovled the issues.

# export DYLD_LIBRARY_PATH=$OPENCV_DIR/lib:/Users/nling/opt/anaconda3/lib

The DYLD_LIBRARY_PATH was there for linker issues where OpenCV-related libraries could not be found at run-time.

  1. Image not found at run time
(base) ➜  MatplotDemo MatplotDemo
dyld: Library not loaded: @rpath/libpython3.8.dylib
  Referenced from: /Users/nling/opencv/samples/MatplotDemo/MatplotDemo
  Reason: image not found
[1]    3813 abort      MatplotDemo

Solution: Pass the required runtime paths (represented as @rpath in the compiled executable) to the linker through -Wl compile option.

-Wl,-rpath,/Users/nling/opt/anaconda3/lib

inspired by this issue clang fails with -Wl,rpath=....

As a workaround mentioned in @rpath what?, @rpath can be specified using the install_name_tool command.

install_name_tool -add_rpath /Users/nling/opt/anaconda3/lib MatplotDemo

otool can be used to check if the compiled executable contains LC_RPATH entries whose value will be used in searching for dylib specified with @rpath. Again, the LC_RPATH entries are added via the -Wl,-rpath,/Users/nling/opt/anaconda3/lib linker options.

otool -l MatplotDemo
...
Load command 13
          cmd LC_LOAD_DYLIB
      cmdsize 56
         name @rpath/libpython3.8.dylib (offset 24)
   time stamp 2 Thu Jan  1 08:00:02 1970
      current version 3.8.0
compatibility version 3.8.0
...
Load command 16
          cmd LC_RPATH
      cmdsize 72
         path /Users/nling/opencv/installation/OpenCV-master-debug/lib (offset 12)
Load command 17
          cmd LC_RPATH
      cmdsize 48
         path /Users/nling/opt/anaconda3/lib (offset 12)

References

  1. Matplotlib for C++
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容