环境:
windows系统+visual studio MSVC编译环境+python3.7.
(注意:windows系统下要用MSVC编译成动态库dll,用qt中的MinGW编译出来的动态库调用报错,具体原因不明。如果使用MSVC编译器,就需要调用的第三方opencv库也是MSVC编译的才行。博主测试过,在网上下载的window系统下的opencv.exe文件是用MSVC编译,可以直接拿来用。在调用过程中,环境搭配是最复杂的,报的问题也是很奇葩的,往往在网上也找不到相关资料,需要自己摸索,博主已经完成了这些BUG,写这篇博客记录下,以免以后忘记。)
动态库C++代码
配置相关opencv路径。
func1.cpp
#include <iostream>
#include <stack>
#include <vector>
#include <map>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2/imgproc/types_c.h>
#include <stdio.h>
#ifdef __cplusplus
#define XEXT extern "C"
#else
#define XEXT
#endif
#ifdef _WIN32
#define XLIB XEXT __declspec(dllexport)
#else
#define XLIB XEXT
#endif
using namespace cv;
using namespace std;
struct Image_To_C
{
int h;
int w;
int c;
float* data;
};
XLIB void Testfunc1()
{
printf("Testfunc1 success\n");
}
// change uchar* to mat
Mat readfrombuffer(uchar* frame_data, int height, int width, int channels) {
if (channels == 1) {
Mat img(height, width, CV_8UC1);
uchar* ptr = img.ptr<uchar>(0);
int count = 0;
for (int row = 0; row < height; row++) {
ptr = img.ptr<uchar>(row);
for (int col = 0; col < width; col++) {
for (int c = 0; c < channels; c++) {
ptr[col * channels + c] = frame_data[count];
count++;
}
}
}
return img;
}
}
//########################种子填充法)#########################
XLIB void ConnectedCountBySeedFill(const cv::Mat& _binImg, cv::Mat& _lableImg, int& iConnectedAreaCount, map<ushort, vector <cv::Point>>& mask_pos)
{
//拓宽1个像素的原因是:如果连通域在边缘,运行此函数会异常崩溃,所以需要在周围加一圈0值,确保连通域不在边上
//==========图像周围拓宽1个像素============================================
int top, bottom; //【添加边界后的图像尺寸】
int leftImage, rightImage;
int borderType = BORDER_CONSTANT; //BORDER_REPLICATE
//【初始化参数】
top = (int)(1); bottom = (int)(1);
leftImage = (int)(1); rightImage = (int)(1);
Mat _binImg2, _binImg3;
//_binImg.copyTo(_binImg2);
//初始化参数value
Scalar value(0); //填充值
//创建图像边界
copyMakeBorder(_binImg, _binImg3, top, bottom, leftImage, rightImage, borderType, value);
//==========图像周围拓宽1个像素============================================
// connected component analysis (4-component)
// use seed filling algorithm
// 1. begin with a foreground pixel and push its foreground neighbors into a stack;
// 2. pop the top pixel on the stack and label it with the same label until the stack is empty
//
// foreground pixel: _binImg(x,y) = 1
// background pixel: _binImg(x,y) = 0
if (_binImg3.empty() ||
_binImg3.type() != CV_8UC1)
{
return;
}
_lableImg.release();
_binImg3.convertTo(_lableImg, CV_16UC1);
ushort label = 1; // start by 2
int rows = _binImg3.rows - 1;
int cols = _binImg3.cols - 1;
for (int i = 1; i < rows - 1; i++)
{
ushort* data = _lableImg.ptr<ushort>(i); //取一行数据
for (int j = 1; j < cols - 1; j++)
{
if (data[j] == 1) //像素不为0
{
std::stack<std::pair<int, int>> neighborPixels; //新建一个栈
neighborPixels.push(std::pair<int, int>(i, j)); // 像素坐标: <i,j> ,以该像素为起点,寻找连通域
++label; // 开始一个新标签,各连通域区别的标志
vector <cv::Point> mask_point;
while (!neighborPixels.empty())
{
// 获取堆栈中的顶部像素并使用相同的标签对其进行标记
std::pair<int, int> curPixel = neighborPixels.top();
int curX = curPixel.first;
int curY = curPixel.second;
_lableImg.ptr<ushort>(curX)[curY] = label; //对图像对应位置的点进行标记
mask_point.push_back(Point(curX, curY));
// 弹出顶部像素 (顶部像素出栈)
neighborPixels.pop();
// 加入8邻域点
if (_lableImg.ptr<ushort>(curX)[curY - 1] == 1)
{// 左点
neighborPixels.push(std::pair<int, int>(curX, curY - 1)); //左边点入栈
}
if (_lableImg.ptr<ushort>(curX)[curY + 1] == 1)
{// 右点
neighborPixels.push(std::pair<int, int>(curX, curY + 1)); //右边点入栈
}
if (_lableImg.ptr<ushort>(curX - 1)[curY] == 1)
{// 上点
neighborPixels.push(std::pair<int, int>(curX - 1, curY)); //上边点入栈
}
if (_lableImg.ptr<ushort>(curX + 1)[curY] == 1)
{// 下点
neighborPixels.push(std::pair<int, int>(curX + 1, curY)); //下边点入栈
}
//===============补充到8连通域======================================================
if (_lableImg.ptr<ushort>(curX - 1)[curY - 1] == 1)
{// 左上点
neighborPixels.push(std::pair<int, int>(curX - 1, curY - 1)); //左上点入栈
}
if (_lableImg.ptr<ushort>(curX - 1)[curY + 1] == 1)
{// 右上点
neighborPixels.push(std::pair<int, int>(curX - 1, curY + 1)); //右上点入栈
}
if (_lableImg.ptr<ushort>(curX + 1)[curY - 1] == 1)
{// 左下点
neighborPixels.push(std::pair<int, int>(curX + 1, curY - 1)); //左下点入栈
}
if (_lableImg.ptr<ushort>(curX + 1)[curY + 1] == 1)
{// 右下点
neighborPixels.push(std::pair<int, int>(curX + 1, curY + 1)); //右下点入栈
}
//===============补充到8连通域======================================================
}
if (mask_point.size() != 0)
{
mask_pos.insert(std::map<ushort, vector <cv::Point>>::value_type(label, mask_point));
}
}
}
}
iConnectedAreaCount = label - 1; //因为label从2开始计数的
}
//###########################################################
#if 1
XLIB char* func1(uchar* frame_data, int rows, int cols, int channels, int upper, int lower, int step)
{
Mat src = readfrombuffer(frame_data, rows, cols, channels);
//imwrite("src.jpg", src);
Mat resultImg = Mat::zeros(src.rows, src.cols, CV_16UC1);
map<ushort, vector <cv::Point>> mask_pos;
//map<ushort,vector <cv::Point>> mask_pos_result;
ushort label_max_index = 0;
for (int thresh_step = upper; thresh_step > lower; thresh_step -= step)
{
Mat binaryImg;
threshold(src, binaryImg, thresh_step, 1, CV_THRESH_BINARY);
// 连通域标记
Mat labelImg;
int iConnectedAreaCount = 0; //连通域个数
ConnectedCountBySeedFill(binaryImg, labelImg, iConnectedAreaCount, mask_pos);//针对黑底白字
//第一次拷贝数据
if (thresh_step == upper)
{
for (int i = 1; i < labelImg.rows - 1; i++)
{
ushort* labelImg_ptr = labelImg.ptr<ushort>(i);
ushort* resultImg_ptr = resultImg.ptr<ushort>(i - 1);
for (int j = 1; j < labelImg.cols - 1; j++)
{
resultImg_ptr[j - 1] = (ushort)labelImg_ptr[j];
}
}
//labelImg.copyTo(resultImg);
label_max_index = iConnectedAreaCount + 1;
//imwrite("labelImg2.jpg", resultImg*10);
/*
for ( auto rit = mask_pos.begin(); rit != mask_pos.end(); ++rit)
{
mask_pos_result[rit->first] = rit->second;
}*/
mask_pos.clear();
continue;
}
//imwrite("labelImg3.jpg", labelImg*10);
for (auto iter = mask_pos.begin(); iter != mask_pos.end(); ++iter) //遍历每个连通区域
{
vector<int> labelflag;
for (int i = 0; i < iter->second.size(); i++) //遍历连通区域中像素点的坐标
{
ushort value = resultImg.ptr<ushort>(iter->second[i].x - 1)[iter->second[i].y - 1];
if (1)
{
int k;/*
if(0 == labelflag.size())
{
labelflag.push_back(value);
continue;
}*/
for (k = 0; k < labelflag.size(); k++) //存储标签值,排除相同标签。类似:values = np.unique(mask[yy, xx])
{
if (labelflag[k] == value)
{
break;
}
}
if (k == labelflag.size())
{
labelflag.push_back(value);
}
else
{
;
}
}
}
if (labelflag.size() == 2) //等价python函数中的elif len(values) == 2:分支语句。
{
ushort temp = labelflag[0] > labelflag[1] ? labelflag[0] : labelflag[1];
for (int i = 0; i < iter->second.size(); i++) //遍历连通区域中像素点的坐标
{
resultImg.ptr<ushort>(iter->second[i].x - 1)[iter->second[i].y - 1] = temp;
}
}
if ((labelflag.size() == 1) && (labelflag[0] == 0)) //等价python函数的if len(values) == 1 and values[0] == 0:分支语句
{
label_max_index += 1;
for (int i = 0; i < iter->second.size(); i++) //遍历连通区域中像素点的坐标
{
resultImg.ptr<ushort>(iter->second[i].x - 1)[iter->second[i].y - 1] = label_max_index;
}
}
labelflag.clear();
}
mask_pos.clear();
}
//imwrite("func1.jpg", resultImg);
//return resultImg;
// 直接返回ushort数据类型还有些问题,尝试失败。
//ushort* char_r = new ushort[resultImg.cols* resultImg.rows*1 + 10];
//memcpy(char_r, resultImg.data, sizeof(char) * (resultImg.cols * resultImg.rows * 1));
#if 1
Mat result_u8;
resultImg.convertTo(result_u8,CV_8UC1);
//imwrite("result_u8.jpg", result_u8);
vector<uchar> data_encode;
imencode(".jpg", result_u8, data_encode);
std::string str_encode(data_encode.begin(), data_encode.end());
//char* char_r = new char[str_encode.size() + 50];
char* char_r = new char[result_u8.rows * result_u8.cols / 2]; //根据压缩比调整
memcpy(char_r, str_encode.data(), sizeof(char) * (str_encode.size()));
#endif
return char_r;
}
#endif
python代码
注释中一部分是测试程序,一部分是采取另外一种方式实现。
from ctypes import *
import cv2
import ctypes
import numpy as np
from skimage.measure import label
import sys
import time
sys.path.append(b'D:/softwareworkspace/pycharm')
#class Image_To_C(Structure):
#_fields_ = [("h", c_int), ("w", c_int), ("c", c_int), ("data", TenArrType_two)]
if __name__ == "__main__":
image_path = "F:\\first_prtoject\\python_to_cplus\\data\\0.jpg"
src_image = cv2.imread(image_path, 0)
src_image = np.asarray(src_image, dtype=np.uint8)
#print(src_image)
src_image2 = src_image.ctypes.data_as(ctypes.c_char_p)
upper = 160
lower = 100
step = 10
h, w = src_image.shape #灰度图这里shape函数只返回h,w两个值。
c = 1
'''
TenArrType = c_int * w
TenArrType_two = TenArrType * h
TenArrType_two_data = TenArrType_two()
class Image_To_C(Structure):
_fields_ = [("h", c_int), ("w", c_int), ("c", c_int), ("data", TenArrType_two)]
'''
try:
print("*************")
lib = CDLL("D:/softwareworkspace/pycharm/libfunc1")
#print(type(src_image.data[0, 0]))
#print(len(src_image.data), h, w)
'''
for i in range(0, h):
for j in range(0, w):
TenArrType_two_data[i][j] = (c_int)(src_image.data[i, j])
imagetoc = Image_To_C(h, w, c, TenArrType_two_data) #byref相当于取地址& POINTER(c_float)相当于 float *
lib.func1.argtypes = (Image_To_C, c_int, c_int, c_int)
lib.func1.restype = (Image_To_C)
lib.func1(imagetoc, c_int(upper), c_int(lower), c_int(step))
'''
lib.func1.restype = c_void_p
#lib.func1.restype = c_uint64
lib.func1.argtypes = (c_char_p, c_int, c_int, c_int, c_int, c_int, c_int)
start_time = time.time() # 开始时间
a = lib.func1(src_image2, c_int(h), c_int(w), c_int(c), c_int(upper), c_int(lower), c_int(step))
b = ctypes.string_at(a, h * w * 1 // 2) # string_at(c_str_p) # 获取内容
nparr = np.frombuffer(b, np.uint8)
#print(nparr)
img_decode = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
end_time = time.time() # 结束时间
print("time:%f" % (end_time - start_time)) # 结束时间-开始时间
cv2.imwrite("dst.jpg", img_decode)
'''
#暂时有问题
lib.func1.restype = POINTER(c_ushort)
lib.func1.argtypes = (c_char_p, c_int, c_int, c_int, c_int, c_int, c_int)
a = lib.func1(src_image2, c_int(h), c_int(w), c_int(c), c_int(upper), c_int(lower), c_int(step))
b = ctypes.string_at(a, h * w * 1) # string_at(c_str_p) # 获取内容
nparr = np.frombuffer(b, np.ushort)
print(nparr)
cv2.imwrite("dst.jpg", nparr)
'''
except Exception as ex:
print("call_c_funcs.py", ex)
备注
python代码加速可以采用cython和numba的形式,不过numba只对python和numpy的部分函数其作用,对第三方库(opencv、skimage等)没作用,因为第三方库自身已经做了优化,而且numba并不知道第三方库函数内部是如何实现的,做不了相应优化。