Convert C++ pointer to Python numpy array - python

I am working on a C/C++ DLL which used OpenCV, and in this one I perform some operations. In this example, I change the contrast of an image I read on Python, transfer to the DLL to perform the operation, and get back the result on Python to display it. I am doing this using pointers on the first pixel of each image, but in Python I don't find the way to recreate correctly the image using this pointer.
I already verified the Mat object in C++ is continuous, and I check the result saved from the DLL which is correct. The problem is in Python for me, but I don't see where I do something wrong.
The C++ class and function :
#pragma once
#include <vector>
#include <string>
#include <fstream>
#include <opencv2/core/core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <thread>
using namespace cv;
using namespace std;
class EpsImageProcessing
{
// -------------- Methods --------------
public:
EpsImageProcessing();
~EpsImageProcessing();
unsigned short * imAdjustContrast(void * ptrImg, int width, int height, int contrastValue);
// -------------- Atributes --------------
Mat imgResult;
unsigned short *imgAdress;
};
unsigned short * EpsImageProcessing::imAdjustContrast(void * ptrImg, int width, int height, int contrastValue)
{
// Get image and reshape it as Mat object
Mat imgTemp = Mat(height, width, CV_8UC1, (uchar*)ptrImg);
// Convert to double to perform calculations
imgTemp.convertTo(imgTemp, CV_32FC1);
// Calculate the contrast coefficient
float coeff = (259*((float)contrastValue+255)) / (255*(259 - (float)contrastValue));
// Change contrast
imgTemp = coeff * (imgTemp - 128) + 128;
// Convert image to original type
imgTemp.convertTo(imgTemp, CV_8UC1);
// Return result
imgResult= imgTemp.clone(); // imgTmp is an attribute of the class of my DLL
imwrite("imgAfter.jpg", imgResult);
bool test = imgResult.isContinuous(); // return true
imgAdress = imgResult.ptr<ushort>();
return imgAdress; //imgResult.ptr<ushort>(); // (unsigned short *)imgResult.data;
}
Then the C wrapper to do the link between C++ and others langages like Python :
__declspec(dllexport) unsigned short* __stdcall imAdjustContrast(void* handle, void* imgPtr, int width, int height, int contrastValue)
{
if (handle)
{
EpsImageProcessing* data = (EpsImageProcessing*)handle;
return data->imAdjustContrast(imgPtr, width, height, contrastValue);
}
return false;
}
And the Python code :
from ctypes import *
import numpy, os, cv2
import matplotlib.pyplot as plt
dirpath = os.environ['PATH']
os.environ['PATH'] = dirpath + ";C:/x64/Debug/" # include of opencv_world.dll
mydll = cdll.LoadLibrary("MyDll.dll")
class mydllClass(object):
def __init__(self, width, height, nFrame, path, filename):
mydll.AllocateHandleImg.argtypes = []
mydll.AllocateHandleImg.restype = c_void_p
mydll.imAdjustContrast.argtypes = [c_void_p, c_void_p, c_int, c_int, c_int]
mydll.imAdjustContrast.restype = POINTER(c_ushort)
self.obj = mydll.AllocateHandleImg()
def imAdjustContrast(self, ptrImg, width, height, contrast):
return mydll.imAdjustContrast(self.obj, ptrImg, width, height, contrast)
img0 = cv2.imread("C:\\Users\\mg\\Downloads\\imgInit.jpg", 0)
imgC = myclass.imAdjustContrast(img0.__array_interface__['data'][0], img0.shape[1], img0.shape[0], -127)
imgAfter = cv2.imread("C:\\Users\\mg\\Downloads\\imgAfter.jpg", 0)
image = numpy.zeros((img0.shape[0],img0.shape[1]), dtype=numpy.dtype(numpy.uint8))
for i in range(img0.shape[0]):
for j in range(img0.shape[1]):
indice = i*img0.shape[1]+j
image[i,j] = numpy.uint8(imgC[indice])
newImg = numpy.ctypeslib.as_array(cast(imgC, POINTER(c_uint8)), shape=(img0.shape))
plt.figure()
plt.subplot(221)
plt.imshow(imgAfter)
plt.gray()
plt.colorbar()
plt.title('image saved from C++ DLL')
plt.subplot(222)
plt.imshow(image)
plt.gray()
plt.colorbar()
plt.title('image recreated in Python (for loop)')
plt.subplot(223)
plt.imshow(newImg)
plt.gray()
plt.colorbar()
plt.title('image recreated in Python (cast)')
plt.show()
And the final result on Python is :

I found that the small difference between the two "good images" (image saved in C++ and recreate in Python with cast method) are from the compression of the image (.jpg) which is different between Python and C++. Dealing with a png and the image created in Python with the C++ pointer is okay with the cast method.
So the problem now is about the two for loops which don't create the image from the pointer in a good way. Any idea?

Related

Convert python with numpy to c++ with opencv

I'm working on some optimazation and want to convert some parts from python to c++
Is it possible to convert this code to c++ with opencv?
The python code uses numpy
import numpy as np
from PIL import Image
pil_img = Image.open(input_filename)
img = np.array(pil_img)
pixels = img.reshape((-1, 3))
num_pixels = pixels.shape[0]
num_samples = int(num_pixels*5)
idx = np.arange(num_pixels)
np.random.shuffle(idx)
samples = pixels[idx[:num_samples]]
update
std::vector<uchar> sample_pixels(const cv::Mat& m, int sample_percent=5){
assert(m.isContinuous());
const auto* input = m.ptr<uchar>();
int
num_pixels = m.total(),
num_samples = num_pixels * sample_percent;
std::cout
<< "num pixels: " << num_pixels << '\n'
<< "num samples: " << num_samples << '\n';
std::vector<uchar> samples(num_samples);
// Fills idx with sequentially increasing values
std::vector<int> idx(num_pixels);
std::iota(idx.begin(), idx.end(), 0);
// Shuffle idx
std::mt19937 engine(0);
std::shuffle(idx.begin(), idx.end(), engine);
for(int i = 0; i < num_samples; i++){
//samples[i] = input[idx[i]];
}
//auto output_mat = cv::Mat(samples, false);
//cv::imwrite("enhance-samples.png", output_mat);
return samples;
}
This is the equivalent code in C++11. This should be several times faster than your python code.
#include <random>
#include <numeric>
#include <opencv2/opencv.hpp>
void shuffling(const std::string &input_filename, const std::string &output_filename) {
// ========== UPDATE ==========
const cv::Mat plain_input_mat = cv::imread(input_filename, -1);
// Equivalent to img.reshape((-1, 3))
const cv::Mat input_mat = plain_input_mat.reshape(3);
// ============================
// By doing this, you can access the pixels without any extra checks.
assert(input_mat.isContinuous());
const auto *input = input_mat.ptr<cv::Vec3b>();
const auto num_samples = input_mat.total();
std::vector<cv::Vec3b> output(num_samples);
std::vector<int> idx(input_mat.total());
std::iota(idx.begin(), idx.end(), 0); // Equivalent to arange.
// Note: numpy uses PCG64 which does not exist in the std library.
std::mt19937 engine(0);
std::shuffle(idx.begin(), idx.end(), engine);
for (int i = 0; i < num_samples; i++) {
output[i] = input[idx[i]];
}
// Save as an image if necessary.
auto output_mat = cv::Mat(output, false);
cv::imwrite(output_filename, output_mat);
}
There are a couple of additional notes.
Note1: Due to the difference in the shuffle algorithm between python and std, the results are not exactly the same.
Note2: With your code, num_samples cannot be larger than the number of pixels in the input image, which seems to be a bug. Please check the length of the samples.
Note3: In both implementations, the most expensive part is shuffle. 60% for python and more than 80% for C++ is spent here. If you want to optimize further, this is definitely where you should exploit.

Running Tensorflow Classifier Model in C++/Obj-C++ results in different result than Python

Ive retrained an InceptionV3 model via the Tensorflow for Poets tutorials and can successfully run label_image.py on my trained data and on new data and get correct labels with good accuracy. Awesome!
If I run my model through my Mac Obj-C++ app my resulting labels are wildly different.
For example - my training is to classify which 'shot type' a frame of video is, (extreme close up, close up, medium, long, extreme long) for classifying video editing content.
label_image.py classifies a frame from a video as 85% likely close up.
My C++ / Obj-C App run with the same frame classifies it as Extreme Long with 60%
Both are running the same version of Tensorflow (1.1) on Mac OS X CPU compiled with AVX/SIMD/FMA optimizations.
My Apps pipeline :
I have a BGR ordered OpenCV Mat image which I can use successfully elsewhere and get sane results from. I create this CV Mat from an OS X CVPixelBufferRef mapped to a BGRA CV MAT like so:
cv::cvtColor(BGRAImage, frameMat, cv::COLOR_BGRA2BGR);
I feed that BGR CV Mat (named frameMat) into a Tensor via code borrowed from the iOS contrib example, like so :
void* baseAddress = (void*)frameMat.datastart;
size_t width = (size_t) frameMat.cols;
size_t height = (size_t) frameMat.rows;
size_t bytesPerRow = (size_t) frameMat.cols * 3; // (BGR)
const int wanted_input_width = 299;
const int wanted_input_height = 299;
const int wanted_input_channels = 3;
const float input_mean = 128.0f;
const float input_std = 128.0f;
resized_tensor = tensorflow::Tensor( tensorflow::DT_FLOAT, tensorflow::TensorShape({1, wanted_input_height, wanted_input_width, wanted_input_channels}));
auto image_tensor_mapped = resized_tensor.tensor<float, 4>();
tensorflow::uint8 *in = sourceStartAddr;
float *out = image_tensor_mapped.data();
for (int y = 0; y < wanted_input_height; ++y)
{
float *out_row = out + (y * wanted_input_width * wanted_input_channels);
for (int x = 0; x < wanted_input_width; ++x)
{
const int in_x = (y * (int)width) / wanted_input_width;
const int in_y = (x * image_height) / wanted_input_height;
tensorflow::uint8 *in_pixel = in + (in_y * width * (image_channels)) + (in_x * (image_channels));
float *out_pixel = out_row + (x * wanted_input_channels);
// Interestingly the iOS example uses BGRA and DOES NOT re-order tensor channels to RGB <-> BGR
// Matching that.
out_pixel[0] = ((float)in_pixel[0] - (float)input_mean) / (float)input_std;
out_pixel[1] = ((float)in_pixel[1] - (float)input_mean) / (float)input_std;
out_pixel[2] = ((float)in_pixel[2] - (float)input_mean) / (float)input_std;
}
}
My session creation code:
tensorflow::Status load_graph_status = ReadBinaryProto(tensorflow::Env::Default(), [inception2015GraphPath cStringUsingEncoding:NSUTF8StringEncoding], &inceptionGraphDef);
if (load_graph_status.ok())
{
tensorflow::SessionOptions options;
inceptionSession = std::unique_ptr<tensorflow::Session>(tensorflow::NewSession(options));
tensorflow::Status session_create_status = inceptionSession->Create(inceptionGraphDef);
}
Running the graph:
tensorflow::Status run_status = inceptionSession->Run({ {input_layer, resized_tensor} }, {feature_layer, final_layer}, {}, &outputs);
And pulling out the labels / feature vector (penultimate layer)
NSMutableArray* outputLabels = [NSMutableArray arrayWithCapacity:self.labelsArray.count];
NSMutableArray* outputScores = [NSMutableArray arrayWithCapacity:self.labelsArray.count];
// 1 = labels and scores
auto predictions = outputs[1].flat<float>();
for (int index = 0; index < predictions.size(); index += 1)
{
const float predictionValue = predictions(index);
NSString* labelKey = self.labelsArray[index % predictions.size()];
NSNumber* currentLabelScore = self.averageLabelScores[labelKey];
NSNumber* incrementedScore = #([currentLabelScore floatValue] + predictionValue );
self.averageLabelScores[labelKey] = incrementedScore;
[outputLabels addObject:labelKey];
[outputScores addObject:#(predictionValue)];
}
// 0 is feature vector
tensorflow::Tensor feature = outputs[0];
int64_t numElements = feature.NumElements();
tensorflow::TTypes<float>::Flat featureVec = feature.flat<float>();
NSMutableArray* featureElements = [NSMutableArray arrayWithCapacity:numElements];
for(int i = 0; i < numElements; i++)
{
[featureElements addObject:#( featureVec(i) ) ];
}
if(self.averageFeatureVec == nil)
{
self.averageFeatureVec = featureElements;
}
else
{
// average each vector element with the prior
for(int i = 0; i < featureElements.count; i++)
{
float a = [featureElements[i] floatValue];
float b = [self.averageFeatureVec[i] floatValue];
self.averageFeatureVec[i] = #( MAX(a,b)) ;
}
}
return #{ kSynopsisStandardMetadataFeatureVectorDictKey : featureElements ,
#"Labels" : outputLabels,
#"Scores" : outputScores,
};
I've attempted to look into the tensor ordering (NHWC), and have checked the tensor creation code but I might be missing something obvious to others. Ive also tried changing channel order, to no avail.
Any insight would be greatly helpful. Thank you!
My usual method for debugging issues like this is:
First save out a raw C array of values from an example input that I know works. For example, make sure that label_image works with your newly-trained model, and then write out the float* array you get from input_layer->flat<float>().data(), using pseudo-code like this:
float* input_data = input_layer->flat<float>().data();
int input_data_count = input_layer->flat<float>().size();
printf("float g_test_input[]={\n");
for (int i = 0; i < input_data_count; ++i) {
printf(" %f,\n", input_data[i]);
}
printf("};\n");
You should end up with a big array that you can copy into your new code. Overwrite whatever input you have in the code you want to test. Now run it, and you should see the same output that you saw from label_image. If you don't, you know there's something different about the model you're loading. If the output is identical, then you know that the input preprocessing is different.
Assuming that it's the preprocessing that's wrong, my next step is to try loading an image from disk. The iOS example code does that in the simple example. Save out some of your expected input into an image file, and then make sure that both label_image and your code produce the same result.
So this one is tricky.
I failed to mention I was running the graph_transform tool on my retrained graph - and was running quantize weights to lower my graphs size. In the past, I've not had an issue with this messing up classification scores at all, but apparently that caused an issue.
Running the above code with a graph transform call without quantize weights fixed the issue.

How do I pass a bitmap of bytes to Cython function? I'm getting garbage values

I am doing a subimage search in python and it's obviously too slow. So in the midsts of converting it to Cython, I found out that it's not so straightforward. The python side of things shows the bytes as correct (when doing a debugPrint) and the subimage search has been tested and works perfectly. However, upon doing my first py to Cy conversion, I've hit stumbling blocks. The only thing I'm doing to convert some python bytes (I think they're bytes.... :o) to a char* pointer is assigning. Am I losing ownership of the data chunk somehow?
Here is the relevant code:
cdef struct PixelsBMP:
char* data
int width, height, bands
# Here's how I create the bytes:
cpdef grabPixelsBMP(img=None):
if img is None:
img = ImageGrab.grab()
elif isinstance(img, str):
img = Image.open(img)
with io.BytesIO() as bytes_io:
img.save(bytes_io, 'BMP')
data = bytes_io.getvalue()
offset = int.from_bytes(data[10:14], byteorder='little', signed=False)
data = data[offset:] # pixels start here
cdef PixelsBMP px;
px.data = data
px.width = img.width
px.height = img.height
px.bands = 3 if img.mode == 'RGB' else 4
return px
# Here's how I access the bytes:
cpdef debugPrintPixels(PixelsBMP px):
import sys
cdef char* d = px.data
print('width:', px.width)
print('height:', px.height)
print('bands (alpha=>4):', px.bands)
cdef:
int pad_sum = 0
int pad = nextMult4Pad(px.width * px.bands)
int x, y, offs
for y in range(0, px.height):
for x in range(0, px.width):
offs = px.width * px.bands * y + px.bands * x + pad_sum
sys.stdout.write('(' + str(hex(d[offs])) + ',' + str(hex(d[offs + 1])) + ',' + \
str(hex(d[offs + 2])) + ((',' + str(hex(d[offs + 3]))) if px.bands == 4 else '') + ')')
print()
pad_sum += pad
What it prints:
('width:', 7)
('height:', 3)
('bands (alpha=>4):', 4)
(0x0,0x0,0x0,0x0)(0x0,0x0,0x0,0x0)(0x1,0x0,0x0,0x0)(0x30,-0x3f,0x4a,0x5c)(0x2,0x0,0x0,0x0)(-0x1,-0x1,-0x1,-0x1)(0x0,0x7,0x0,0x0)()
(0x0,0x0,0x0,0x0)(0x1,0x0,0x0,0x0)(0x30,-0x3f,0x4a,0x5c)(0x2,0x0,0x0,0x0)(-0x1,-0x1,-0x1,-0x1)(0x0,0x4,0x0,0x0)(0x0,0x0,0x0,0x0)()
(0x1,0x0,0x0,0x0)(0x30,-0x3f,0x4a,0x5c)(0x4,0x0,0x0,0x0)(-0x1,-0x1,-0x1,-0x1)(0x64,0x1,0x0,0x53)(0x0,0x0,0x0,0x0)(0x1,0x0,0x0,0x0)()
Where it should be an image of black pixels with an alpha channel = 255.
Am I accessing the bytes correctly using Cython?
This is a memory ownership issue. Your bytes_io owns the data that px_data points to. You need to allocate some memory and copy over the data:
# at the top of your file
from libc.stdlib cimport malloc
from libc.string cimport memcpy
#replacing px.data = data
cdef char* data_str = data
px.data = <char*>malloc(sizeof(char)*len(data))
memcpy(px.data,data_str, len(data))
When you're done with the struct you then need to free the data. Note that if you pass the struct to Python (e.g. by calling cpdef grabPixelsBMP from Python) it generates a dictionary copy of it. You must free the data held by the struct, not the dictionary copy. Memory management in C can be hard...

Fast calculation of v-disparity with OpenCV-Function calcHist

Based on a disparity matrix from a passive stereo-camera system i need to calculate a v-disparity representation for obstacle detection with OpenCV.
A working implementation is not the problem. The problem is to do it fast...
(One) Reference for v-Disparity: Labayrade, R. and Aubert, D. and Tarel, J.P.
Real time obstacle detection in stereovision on non flat road geometry through v-disparity representation
The basic in short, to get the v-disparity (figure 1), is to analyze the rows of the disparity-matrix (figure 2) an represent the result as a histogram for each row over the disparity values. u-disparity (figure 3) is the same on the columns of the disparity-matrix. (All figures are false-colored.)
I have implement the "same" in Python and C++. The speed in Python is acceptable but in C++ i get for the u- and v-disparity a time round about a half second (0.5 s).
(1. edit: due to the separate time measurement, only the calculation of the u-histogram takes a big amount of time...)
This leads me to following questions:
Is it possible to avoid the loops for the line-wise calculation of the histogram? Is there a "trick" to do it with one call of calcHist-Function from OpenCV? Perhaps with the dimensions?
Is it in C++ just bad-coded and the runtime-issue are not related to the loops used for calculation?
Thanks, all
Working implementation in Python:
#!/usr/bin/env python2
#-*- coding: utf-8 -*-
#
# THIS SOURCE-CODE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED. IN NO EVENT WILL THE AUTHOR BE HELD LIABLE FOR ANY DAMAGES ARISING FROM
# THE USE OF THIS SOURCE-CODE. USE AT YOUR OWN RISK.
import cv2
import numpy as np
import time
def draw_object(image, x, y, width=50, height=100):
color = image[y, x]
image[y-height:y, x-width//2:x+width//2] = color
IMAGE_HEIGHT = 600
IMAGE_WIDTH = 800
while True:
max_disp = 200
# create fake disparity
image = np.zeros((IMAGE_HEIGHT, IMAGE_WIDTH), np.uint8)
for c in range(IMAGE_HEIGHT)[::-1]:
image[c, ...] = int(float(c) / IMAGE_HEIGHT * max_disp)
draw_object(image, 275, 175)
draw_object(image, 300, 200)
draw_object(image, 100, 350)
# calculate v-disparity
vhist_vis = np.zeros((IMAGE_HEIGHT, max_disp), np.float)
for i in range(IMAGE_HEIGHT):
vhist_vis[i, ...] = cv2.calcHist(images=[image[i, ...]], channels=[0], mask=None, histSize=[max_disp],
ranges=[0, max_disp]).flatten() / float(IMAGE_HEIGHT)
vhist_vis = np.array(vhist_vis * 255, np.uint8)
vblack_mask = vhist_vis < 5
vhist_vis = cv2.applyColorMap(vhist_vis, cv2.COLORMAP_JET)
vhist_vis[vblack_mask] = 0
# calculate u-disparity
uhist_vis = np.zeros((max_disp, IMAGE_WIDTH), np.float)
for i in range(IMAGE_WIDTH):
uhist_vis[..., i] = cv2.calcHist(images=[image[..., i]], channels=[0], mask=None, histSize=[max_disp],
ranges=[0, max_disp]).flatten() / float(IMAGE_WIDTH)
uhist_vis = np.array(uhist_vis * 255, np.uint8)
ublack_mask = uhist_vis < 5
uhist_vis = cv2.applyColorMap(uhist_vis, cv2.COLORMAP_JET)
uhist_vis[ublack_mask] = 0
image = cv2.applyColorMap(image, cv2.COLORMAP_JET)
cv2.imshow('image', image)
cv2.imshow('vhist_vis', vhist_vis)
cv2.imshow('uhist_vis', uhist_vis)
cv2.imwrite('disparity_image.png', image)
cv2.imwrite('v-disparity.png', vhist_vis)
cv2.imwrite('u-disparity.png', uhist_vis)
if chr(cv2.waitKey(0)&255) == 'q':
break
Working implementation in C++:
#include <iostream>
#include <stdlib.h>
#include <ctime>
#include <opencv2/opencv.hpp>
using namespace std;
void draw_object(cv::Mat image, unsigned int x, unsigned int y, unsigned int width=50, unsigned int height=100)
{
image(cv::Range(y-height, y), cv::Range(x-width/2, x+width/2)) = image.at<unsigned char>(y, x);
}
int main()
{
unsigned int IMAGE_HEIGHT = 600;
unsigned int IMAGE_WIDTH = 800;
unsigned int MAX_DISP = 250;
unsigned int CYCLE = 0;
//setenv("QT_GRAPHICSSYSTEM", "native", 1);
// === PREPERATIONS ==
cv::Mat image = cv::Mat::zeros(IMAGE_HEIGHT, IMAGE_WIDTH, CV_8U);
cv::Mat uhist = cv::Mat::zeros(IMAGE_HEIGHT, MAX_DISP, CV_32F);
cv::Mat vhist = cv::Mat::zeros(MAX_DISP, IMAGE_WIDTH, CV_32F);
cv::Mat tmpImageMat, tmpHistMat;
float value_ranges[] = {(float)0, (float)MAX_DISP};
const float* hist_ranges[] = {value_ranges};
int channels[] = {0};
int histSize[] = {MAX_DISP};
struct timespec start, finish;
double elapsed;
while(1)
{
CYCLE++;
// === CLEANUP ==
image = cv::Mat::zeros(IMAGE_HEIGHT, IMAGE_WIDTH, CV_8U);
uhist = cv::Mat::zeros(IMAGE_HEIGHT, MAX_DISP, CV_32F);
vhist = cv::Mat::zeros(MAX_DISP, IMAGE_WIDTH, CV_32F);
// === CREATE FAKE DISPARITY WITH OBJECTS ===
for(int i = 0; i < IMAGE_HEIGHT; i++)
image.row(i) = ((float)i / IMAGE_HEIGHT * MAX_DISP);
draw_object(image, 200, 500);
draw_object(image, 525 + CYCLE%100, 275);
draw_object(image, 500, 300 + CYCLE%100);
clock_gettime(CLOCK_MONOTONIC, &start);
// === CALCULATE V-HIST ===
for(int i = 0; i < IMAGE_HEIGHT; i++)
{
tmpImageMat = image.row(i);
vhist.row(i).copyTo(tmpHistMat);
cv::calcHist(&tmpImageMat, 1, channels, cv::Mat(), tmpHistMat, 1, histSize, hist_ranges, true, false);
vhist.row(i) = tmpHistMat.t() / (float) IMAGE_HEIGHT;
}
clock_gettime(CLOCK_MONOTONIC, &finish);
elapsed = (finish.tv_sec - start.tv_sec);
elapsed += (finish.tv_nsec - start.tv_nsec) * 1e-9;
cout << "V-HIST-TIME: " << elapsed << endl;
clock_gettime(CLOCK_MONOTONIC, &start);
// === CALCULATE U-HIST ===
for(int i = 0; i < IMAGE_WIDTH; i++)
{
tmpImageMat = image.col(i);
uhist.col(i).copyTo(tmpHistMat);
cv::calcHist(&tmpImageMat, 1, channels, cv::Mat(), tmpHistMat, 1, histSize, hist_ranges, true, false);
uhist.col(i) = tmpHistMat / (float) IMAGE_WIDTH;
}
clock_gettime(CLOCK_MONOTONIC, &finish);
elapsed = (finish.tv_sec - start.tv_sec);
elapsed += (finish.tv_nsec - start.tv_nsec) * 1e-9;
cout << "U-HIST-TIME: " << elapsed << endl;
// === PREPARE AND SHOW RESULTS ===
uhist.convertTo(uhist, CV_8U, 255);
cv::applyColorMap(uhist, uhist, cv::COLORMAP_JET);
vhist.convertTo(vhist, CV_8U, 255);
cv::applyColorMap(vhist, vhist, cv::COLORMAP_JET);
cv::imshow("image", image);
cv::imshow("uhist", uhist);
cv::imshow("vhist", vhist);
if ((cv::waitKey(1)&255) == 'q')
break;
}
return 0;
}
Figure 1: v-disparity
Figure 2: Fake disparity matrix
Figure 3: u-disparity
edit:
correct name for u- and v-disparity and separate time measurement in c++ example
small typo
Today i had the possibility to reinvestigate the problem. Remembering the OpenCV basics (1) for the Mat-structure and the fact that only one calculation takes a huge amount of time, i had the solution.
In OpenCV, each row of an image could be reached by a row-pointer. For iterating columns (done in u-disparity calculation) i suspect, that OpenCV needs to resolve every row-pointer + column-offset for building the histogram.
Changing the Code in a way, that OpenCV is able to use row-pointer, solves the problem for me.
| old code [s] | changed [s]
------------+--------------+-------------
V-HIST-TIME | 0.00351909 | 0.00334152
U-HIST-TIME | 0.600039 | 0.00449285
So for the u-hist-loop i transpose the image and reverse the operation after the loop. The line wise access for calculation could now be done via the row-pointer.
Changed Codelines:
// === CALCULATE U-HIST ===
image = image.t();
for(int i = 0; i < IMAGE_WIDTH; i++)
{
tmpImageMat = image.row(i);
uhist.col(i).copyTo(tmpHistMat);
cv::calcHist(&tmpImageMat, 1, channels, cv::Mat(), tmpHistMat, 1, histSize, hist_ranges, true, false);
uhist.col(i) = tmpHistMat / (float) IMAGE_WIDTH;
}
image = image.t();
Finally my second question takes effect, the runtime-issue belongs not to the loop. A time less than 5 ms is (for now) fast enough.
Very nice code and very illustrative. It helped me understand u-disparity. However, your C/C++ code is broken. I fixed him with this code:
cv::Mat uhist = cv::Mat::zeros(MAX_DISP, IMAGE_WIDTH, CV_32F);
cv::Mat vhist = cv::Mat::zeros(IMAGE_WIDTH, MAX_DISP, CV_32F);

C++ conversion from NumPy array to Mat (OpenCV)

I am writing a thin wrapper around ArUco augmented reality library (which is based on OpenCV). An interface I am trying to build is very simple:
Python passes image to C++ code;
C++ code detects markers and returns their locations and other info to Python as tuple of dicts.
However, I couldn't figure out how to represent an image in Python to pass it to C++. For GUI and camera management I am going to use PyQt, so initially it is going to be QImage, but I can't simply pass it to OpenCV (or I can?). At first, I tried to use nested tuples to represent row, column and color of each pixel, so I ended up with this sample code:
using namespace cv;
namespace py = boost::python;
void display(py::tuple pix)
{
/*
Receive image from Python and display it.
*/
Mat img(py::len(pix), py::len(pix[0]), CV_8UC3, Scalar(0, 0, 255));
for (int y = 0; y < py::len(pix); y++)
for (int x = 0; x < py::len(pix[y]); x++)
{
Vec3b rgb;
for (int i = 0; i < 3; i++)
rgb[i] = py::extract<int>(pix[y][x][i]);
img.at<Vec3b>(Point(x, y)) = rgb;
}
imshow("Image", img);
waitKey(0);
}
BOOST_PYTHON_MODULE(aruco)
{
py::def("display", display);
}
It turned out to be painfully slow (a few seconds for a single frame), so I went googling and found solution that should be much faster: use NumPy arrays, so the code would look something like that:
void display(py::object array)
{
Mat img;
// ... some magic here to convert NumPy array to Mat ...
imshow("Image", img);
waitKey(0);
}
However, I have no idea how to convert NumPy Array (which in C++ level is just a Python Object) to OpenCV Mat. I would appreciate any help here.
Alternatively, maybe NumPy is not really needed, so I could just pass QImage Python object directly to C++ layer? Or maybe there is a different approach to this problem? Any advice is appreciated!
The best solution in your situation is using custom boost::python converter for cv::Mat object. OpenCV has Python wrapper and when you are using this wrapper you are operating on Numpy arrays - you don't even need to know that those arrays are converted to cv::Mat objects while "crossing the c++ <-> python border". Writing such converter for simple type is quite easy, however creating converter for cv::Mat isn't simple. Fortunetely someone else already did this - here is version for OpenCV 2.x and here for 3.x. If you are not familiar with boost::python converters, this article should help you.
Hope it helps, if you wil have any problems, let us know.
I wrote this example for who didn't know there is Boost Numpy module. You can see how to convert Mat to NDArray and vice versa. it will gives you idea the way of convert ndarray.
#define BOOST_PYTHON_STATIC_LIB
#define BOOST_LIB_NAME "boost_numpy35"
//#include <boost/config/auto_link.hpp>
#include <boost/python.hpp>
#include <boost/python/numpy.hpp>
#include <iostream>
#include <opencv2/opencv.hpp>
namespace py = boost::python;
namespace np = boost::python::numpy;
void Init() {
// set your python location.
wchar_t str[] = L"D:\\Anaconda3\\envs\\tensorflow_vision";
Py_SetPythonHome(str);
Py_Initialize();
np::initialize();
}
np::ndarray ConvertMatToNDArray(const cv::Mat& mat) {
py::tuple shape = py::make_tuple(mat.rows, mat.cols, mat.channels());
py::tuple stride = py::make_tuple(mat.channels() * mat.cols * sizeof(uchar), mat.channels() * sizeof(uchar), sizeof(uchar));
np::dtype dt = np::dtype::get_builtin<uchar>();
np::ndarray ndImg = np::from_data(mat.data, dt, shape, stride, py::object());
return ndImg;
}
cv::Mat ConvertNDArrayToMat(const np::ndarray& ndarr) {
//int length = ndarr.get_nd(); // get_nd() returns num of dimensions. this is used as a length, but we don't need to use in this case. because we know that image has 3 dimensions.
const Py_intptr_t* shape = ndarr.get_shape(); // get_shape() returns Py_intptr_t* which we can get the size of n-th dimension of the ndarray.
char* dtype_str = py::extract<char *>(py::str(ndarr.get_dtype()));
// variables for creating Mat object
int rows = shape[0];
int cols = shape[1];
int channel = shape[2];
int depth;
// you should find proper type for c++. in this case we use 'CV_8UC3' image, so we need to create 'uchar' type Mat.
if (!strcmp(dtype_str, "uint8")) {
depth = CV_8U;
}
else {
std::cout << "wrong dtype error" << std::endl;
return cv::Mat();
}
int type = CV_MAKETYPE(depth, channel); // CV_8UC3
cv::Mat mat = cv::Mat(rows, cols, type);
memcpy(mat.data, ndarr.get_data(), sizeof(uchar) * rows * cols * channel);
return mat;
}
int main()
{
using namespace std;
try
{
// initialize boost python and numpy
Init();
// import module
py::object main_module = py::import("__main__");
py::object print = main_module.attr("__builtins__").attr("print"); // this is for printing python object
// get image
cv::Mat img;
img = cv::imread("Lenna.jpg", cv::IMREAD_COLOR);
if (img.empty())
{
std::cout << "can't getting image" << std::endl;
return -1;
}
// convert Mat to NDArray
cv::Mat cloneImg = img.clone(); // converting functions will access to same data between Mat and NDArray. so we should clone Mat object. This may important in your case.
np::ndarray ndImg = ConvertMatToNDArray(cloneImg);
// You can check if it's properly converted.
//print(ndImg);
// convert NDArray to Mat
cv::Mat matImg = ConvertNDArrayToMat(ndImg); // also you can convert ndarray to mat.
// add 10 brightness to converted image
for (int i = 0; i < matImg.rows; i++) {
for (int j = 0; j < matImg.cols; j++) {
for (int c = 0; c < matImg.channels(); c++) {
matImg.at<cv::Vec3b>(i, j)[c] += 10;
}
}
}
// show image
cv::imshow("original image", img);
cv::imshow("converted image", matImg);
cv::waitKey(0);
cv::destroyAllWindows();
}
catch (py::error_already_set&)
{
PyErr_Print();
system("pause");
}
system("pause");
return 0;
}
Optionally, if you don't like to use wrappers, and want to use native python extension module, you can do it like this.
python3:
my_image = cv.imread("my_image.jpg", 1) # reads colorfull image in python
dims = my_image.shape # get image shape (h, w, c)
my_image = my_image.ravel() # flattens 3d array into 1d
cppextenionmodule.np_to_mat(dims, my_image)
c++:
static PyObject *np_to_mat(PyObject *self, PyObject *args){
PyObject *size;
PyArrayObject *image;
if (!PyArg_ParseTuple(args, "O!O!", &PyTuple_Type, &size, &PyArray_Type, &image)) {
return NULL;
}
int rows = PyLong_AsLong(PyTuple_GetItem(size ,0));
int cols = PyLong_AsLong(PyTuple_GetItem(size ,1));
int nchannels = PyLong_AsLong(PyTuple_GetItem(size ,2));
char my_arr[rows * nchannels * cols];
for(size_t length = 0; length<(rows * nchannels * cols); length++){
my_arr[length] = (*(char *)PyArray_GETPTR1(image, length));
}
cv::Mat my_img = cv::Mat(cv::Size(cols, rows), CV_8UC3, &my_arr);
... whatever with the image
}
Here is a pybind11 version of afewthings/DomQ's answer. I found pybind11 was better for my project than boost::python (both libraries are quite nice)
// convert a cv::Mat to an np.array
py::array to_array(const cv::Mat& im) {
const ssize_t channels = im.channels();
const ssize_t height = im.rows;
const ssize_t width = im.cols;
const ssize_t dim = sizeof(uchar) * height * width * channels;
auto data = new uchar[dim];
std::copy(im.data, im.data + dim, data);
return py::array_t<uchar>(
py::buffer_info(
data,
sizeof(uchar), //itemsize
py::format_descriptor<uchar>::format(),
channels, // ndim
std::vector<ssize_t> { height, width, channels }, // shape
std::vector<ssize_t> { width * channels, channels, sizeof(uchar) } // strides
),
py::capsule(data, [](void* f){
// handle releasing data
delete[] reinterpret_cast<uchar*>(f);
})
);
}
// convert an np.array to a cv::Mat
cv::Mat from_array(const py::array& ar) {
if (!ar.dtype().is(py::dtype::of<uchar>())) {
std::cout << "ERROR unsupported dtype!" << std::endl;
return cv::Mat();
}
auto shape = ar.shape();
int rows = shape[0];
int cols = shape[1];
int channels = shape[2];
int type = CV_MAKETYPE(CV_8U, channels); // CV_8UC3
cv::Mat mat = cv::Mat(rows, cols, type);
memcpy(mat.data, ar.data(), sizeof(uchar) * rows * cols * channels);
return mat;
}

Categories

Resources