环境:

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并不知道第三方库函数内部是如何实现的,做不了相应优化。