Pass python IplImage object as simple struct to shared C library using ctypes
我正在尝试为用 C/C 编写的应用程序创建一个 python package器,它广泛使用了 OpenCV C API。我想为此使用 ctypes,因为我在以前的程序中成功使用了它。但是当我尝试将 IplImage 从 Python 作为参数传递给 c 库中的函数时遇到了问题。
我创建了一个示例测试库来演示该问题。以下是我想使用的库中的函数:
1 2 3 4 5 6 7 8 9 10 11 12 | // ImageDll.h #include"opencv2/opencv.hpp" extern"C" //Tells the compile to use C-linkage for the next scope. { // Returns image loaded from location __declspec(dllexport) IplImage* Load(char* dir); // Show image __declspec(dllexport) void Show(IplImage* img); } |
还有一个cpp文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // ImageDll.cpp // compile with: /EHsc /LD #include"ImageDll.h" using namespace std; extern"C" //Tells the compile to use C-linkage for the next scope. { IplImage* Load(char* dir) { return cvLoadImage(dir, CV_LOAD_IMAGE_COLOR); } void Show(IplImage* img) { cvShowImage("image", img); cvWaitKey(0); } } |
这是python的初步尝试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | from time import sleep from ctypes import * from modules.acquisition import InitCamera, GetImage from modules.utils import struct import cv2.cv as cv # load DLL containing image functions print"Loading shared library with genetic algorithm...", image_lib = cdll.LoadLibrary("OpenCV_test_DLL.dll") print"Done." # get function handles print"Loading functions of library...", image_load = image_lib.Load image_show = image_lib.Show # set return type for functions (because ctypes default is int) image_load.restype = c_void_p image_show.restype = None print"Done." # initialize source print"Initializing camera", source = struct() InitCamera(source) print"Done." # show video while (1): # get image as PIL image img = GetImage(source) # transform image to OpenCV IplImage cv_img = cv.CreateImageHeader(img.size, cv.IPL_DEPTH_8U, 3) cv.SetData(cv_img, img.tostring()) # show image using OpenCV highgui lib image_show(pointer(cv_img)) |
如您所见,我从相机获取图像作为 PIL 图像,然后将其转换为 python IplImage。这适用于 100%,因为当我用来自 cv2.cv 模块的 python 绑定替换最后一行 image_show(pointer(cv_img)) 时:
1 2 | cv.ShowImage("image", cv_img) cv.WaitKey(20) |
然后我得到正确的输出。
所以问题出在 image_show(pointer(cv_img)) 上,它因 TypeError 失败:type must have storage info。这是因为 cv_img 需要是有效的 ctypes IplImage 结构。我试图用 ctypes 模仿它,但收效甚微:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | from ctypes import * from cv2 import cv # ctypes IplImage class cIplImage(Structure): _fields_ = [("nSize", c_int), ("ID", c_int), ("nChannels", c_int), ("alphaChannel", c_int), ("depth", c_int), ("colorModel", c_char * 4), ("channelSeq", c_char * 4), ("dataOrder", c_int), ("origin", c_int), ("align", c_int), ("width", c_int), ("height", c_int), ("roi", c_void_p), ("maskROI", c_void_p), ("imageID", c_void_p), ("tileInfo", c_void_p), ("imageSize", c_int), ("imageData", c_char_p), ("widthStep", c_int), ("BorderMode", c_int * 4), ("BorderConst", c_int * 4), ("imageDataOrigin", c_char_p)] |
这是进行转换的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | # convert Python PIL to ctypes Ipl def PIL2Ipl(input_img): # mode dictionary: # (pil_mode : (ipl_depth, ipl_channels) mode_list = { "RGB" : (cv.IPL_DEPTH_8U, 3), "L" : (cv.IPL_DEPTH_8U, 1), "F" : (cv.IPL_DEPTH_32F, 1) } if not mode_list.has_key(input_img.mode): raise ValueError, 'unknown or unsupported input mode' result = cIplImage() result.imageData = c_char_p(input_img.tostring()) result.depth = c_int(mode_list[input_img.mode][0]) result.channels = c_int(mode_list[input_img.mode][1]) result.height = c_int(input_img.size[0]) result.width = c_int(input_img.size[1]) return result ("imageData", c_char_p), ("widthStep", c_int), ("BorderMode", c_int * 4), ("BorderConst", c_int * 4), ("imageDataOrigin", c_char_p)] |
视频循环然后变为
1 2 3 4 5 6 7 8 9 | # show video while (1): # get image as PIL image img = GetImage(source) # transform image to OpenCV IplImage cv_img = cIplImage() cv_img = PIL2Ipl(img) # show image using OpenCV highgui lib image_show(pointer(cv_img)) |
通过这种方式,数据被传递到库,但随后它在未知函数中为 OpenCV 错误而哭泣:错误标志(参数或结构字段)(无法识别或不支持的数组类型)。因此创建的 ctypes 结构无效。有谁知道如何正确实施它?当它使我能够将 python IplImage 传递给 c 库时,我什至会接受其他不使用 ctypes 的解决方案。谢谢。
注意:过去 2 天我一直试图找到这个问题的答案,但没有任何成功。有针对 OpenCV 1.0 的解决方案,但最近使用 numpy 数组的 Python 的 OpenCV 绑定几乎不可能使 Python 和 C 应用程序之间的接口正常工作。 :(
我终于找到了解决问题的方法。而不是使用默认的python函数
1 2 | cv.CreateImageHeader() cv.SetData() |
我使用了从 OpenCV C 库导出的 C 函数。 :) 我什至设法将颜色从 PILs RGB 转换为 IplImage BGR 格式。这是完整的来源:
ImageDll.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #include"opencv2/opencv.hpp" extern"C" //Tells the compile to use C-linkage for the next scope. { // Returns image loaded from location __declspec(dllexport) IplImage* Load(char* dir); // Show image __declspec(dllexport) void Show(IplImage* img); // Auxiliary functions __declspec(dllexport) void aux_cvSetData(CvArr* arr, void* data, int step); __declspec(dllexport) IplImage* aux_cvCreateImageHeader(int width, int height, int depth, int channels); __declspec(dllexport) IplImage* aux_cvCvtColor(const IplImage* src, int code); __declspec(dllexport) void aux_cvCopy(const CvArr* src, CvArr* dst); __declspec(dllexport) void aux_cvReleaseImage(IplImage** image); __declspec(dllexport) void aux_cvReleaseImageHeader(IplImage** image); } |
ImageDll.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | #include"ImageDll.h" using namespace std; extern"C" //Tells the compile to use C-linkage for the next scope. { IplImage* Load(char* dir) { return cvLoadImage(dir, CV_LOAD_IMAGE_COLOR); } void Show(IplImage* img) { cvShowImage("image", img); cvWaitKey(5); } void aux_cvSetData(CvArr* arr, void* data, int step) { cvSetData(arr,data,step); } IplImage* aux_cvCreateImageHeader(int width, int height, int depth, int channels) { return cvCreateImageHeader(cvSize(width,height), depth, channels); } IplImage* aux_cvCvtColor(const IplImage* src, int code) { IplImage* dst = cvCreateImage(cvSize(src->width,src->height),src->depth,src->nChannels); cvCvtColor(src, dst, code); return dst; } void aux_cvCopy(const CvArr* src, CvArr* dst) { cvCopy(src, dst, NULL); } void aux_cvReleaseImage(IplImage** image) { cvReleaseImage(image); } void aux_cvReleaseImageHeader(IplImage** image) { cvReleaseImageHeader(image); } } |
run.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | # This Python file uses the following encoding: utf-8 from time import sleep from ctypes import * from modules.acquisition import InitCamera, GetImage from modules.utils import struct import cv2.cv as cv from modules.ipl import * # load DLL containing image functions print"Loading shared library with C functions...", image_lib = cdll.LoadLibrary("OpenCV_test_DLL.dll") print"Done." # get function handles print"Loading functions of library...", image_load = image_lib.Load image_show = image_lib.Show cvReleaseImage = image_lib.aux_cvReleaseImage # set return type for functions (because ctypes default is int) image_load.restype = c_void_p image_show.restype = None cvReleaseImage.restype = None print"Done." # initialize source print"Initializing camera", source = struct() InitCamera(source) print"Done." # show video while (1): # get image as PIL image img = GetImage(source) # transform image to OpenCV IplImage cv_img = PIL2Ipl(img) # show image using OpenCV highgui lib image_show(cv_img) # release memory cvReleaseImage(byref(cv_img)) |
ipl.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | from ctypes import * from cv2 import cv # ctypes IplImage class cIplImage(Structure): _fields_ = [("nSize", c_int), ("ID", c_int), ("nChannels", c_int), ("alphaChannel", c_int), ("depth", c_int), ("colorModel", c_char * 4), ("channelSeq", c_char * 4), ("dataOrder", c_int), ("origin", c_int), ("align", c_int), ("width", c_int), ("height", c_int), ("roi", c_void_p), ("maskROI", c_void_p), ("imageID", c_void_p), ("tileInfo", c_void_p), ("imageSize", c_int), ("imageData", POINTER(c_char)), ("widthStep", c_int), ("BorderMode", c_int * 4), ("BorderConst", c_int * 4), ("imageDataOrigin", c_char_p)] # load DLL containing needed OpenCV functions libr = cdll.LoadLibrary("OpenCV_test_DLL.dll") cvSetData = libr.aux_cvSetData cvCreateImageHeader = libr.aux_cvCreateImageHeader cvCvtColor = libr.aux_cvCvtColor cvCopy = libr.aux_cvCopy cvReleaseImage = libr.aux_cvReleaseImage cvReleaseImageHeader = libr.aux_cvReleaseImageHeader # set return types for library functions cvSetData.restype = None cvCreateImageHeader.restype = POINTER(cIplImage) cvCvtColor.restype = POINTER(cIplImage) cvCopy.restype = None cvReleaseImage.restype = None cvReleaseImageHeader.restype = None #print"auxlib loaded" # convert Python PIL to ctypes Ipl def PIL2Ipl(pil_img): """Converts a PIL image to the OpenCV/IplImage data format. Supported input image formats are: RGB L F """ # mode dictionary: # (pil_mode : (ipl_depth, ipl_channels) mode_list = { "RGB" : (cv.IPL_DEPTH_8U, 3), "L" : (cv.IPL_DEPTH_8U, 1), "F" : (cv.IPL_DEPTH_32F, 1) } if not mode_list.has_key(pil_img.mode): raise ValueError, 'unknown or unsupported input mode' depth = c_int(mode_list[pil_img.mode][0]) channels = c_int(mode_list[pil_img.mode][1]) height = c_int(pil_img.size[1]) width = c_int(pil_img.size[0]) data = pil_img.tostring() ipl_img = cvCreateImageHeader(width, height, depth, channels); cvSetData(ipl_img, create_string_buffer(data,len(data)), c_int(width.value * channels.value)) brg_img = cvCvtColor(ipl_img,cv.CV_RGB2BGR) cvReleaseImageHeader(byref(ipl_img)) return brg_img |
希望对大家有所帮助:)