为什么 C# 回调仅在 python 模块使用 cv.imshow() 时执行?

Why are C# callbacks only executed when the python module uses cv.imshow()?

我想不出一个更好的更具描述性的标题,因为它涉及 3 种语言,我现在将对其进行解释。
我围绕 Python 模块编写了一个 C++ 包装器,顺便说一句,它在 C++ 中工作得很好。我用这个包装器制作了一个 DLL,并将一些功能公开为 C,并在 C# 应用程序中使用它们。

问题是,如果我不显示网络摄像头源,C# 应用程序就会挂起。
即在Python模块中有这样的条件:

if self.debug_show_feed:
    cv2.imshow('service core face Capture', frame)

并且当设置 True 时,将显示网络摄像头画面。
这主要是我放置的调试内容,对于实际生产需要禁用它。在 C++ 上没问题 我可以将其设置为 false(通过构造函数),一切都很好。
但是,在 C# 上,如果我尝试使用该模块而不将网络摄像头馈送设置为 true,则 C# 应用程序挂起,这是因为 [=调用主要操作的 24=] 变为阻塞调用,并返回 none 的回调。
顺便说一句,我的DllImport如下:

[DllImport(@"Core_DLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int Initialize(bool showFeed);

[DllImport(@"Core_DLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void Start(bool async);

[DllImport(@"Core_DLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void Stop();

[DllImport(@"Core_DLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void SetCpuAffinity(int mask);



public delegate void CallbackDelegate(bool status, string message);
[MethodImplAttribute(MethodImplOptions.InternalCall)]

[DllImport(@"Core_DLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void AddCallback(IntPtr fn);

[DllImport(@"Core_DLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void RemoveCallback(IntPtr fn);

这是我的 C# 回调:

private CallbackDelegate del;
public void SetUpCallback()
{
    txtLog.Text += "Registering C# callback...\r\n";
    del = new CallbackDelegate(callback01);
    AddCallback(Marshal.GetFunctionPointerForDelegate(del));
    txtLog.Text += "Calling passed C++ callback...\r\n";
}

bool status;
string id;
public void callback01(bool status, string id)
{
     this.status = status;
     this.id = id;
}

这是执行的主要 python 模块:

def start(self):
    try:
        self.is_running = True
        self._main_loop()

    except Exception as ex:
        path='exceptions-servicecore.log'
        track = traceback.format_exc()
        exception_time = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
        with open(path, 'a') as f:
            f.writelines(f'\n{exception_time} : exception occured {ex.args} \n{track}')

def start_async(self):
    st = threading.Thread(target=self.start) 
    st.start()

def _main_loop(self):

    name = None
    is_valid = False
    while self.is_running and self.cap.isOpened():
        is_success, frame = self.cap.read()
        if is_success:
            name="sth"
            is_valid=True

            self._execute_callbacks(is_valid, name, frame)
            self._execute_c_callbacks(is_valid, name)

            if self.debug_show_feed:
                cv2.imshow('service core face Capture', frame)

        if self.save:
            self.video_writer.write(frame)

        if (cv2.waitKey(1)&0xFF == ord('q')) or (not self.is_running):
            break

    self.cap.release()
    if self.save:
        self.video_writer.release()
    cv2.destroyAllWindows()    

知道这只发生在 C# 而不是 C++,我可能在我的封送处理或我尝试在这种情况下使用 C# 回调的方式中遇到一些问题.

这里有一个 Visual Studio 和一个最小的例子来证明这一点:https://workupload.com/file/epsgzmMMVMY

这里有什么问题?为什么 cv.imshow() 导致此行为?

我找到了 C# 端的回调没有输出任何内容的原因。回调都按应有的方式执行,但由于 Python 侧的主循环是阻塞方法,因此它们仅在阻塞方法结束时才开始执行(就像输出没有得到的递归函数一样直到最后都归还)。

然后我注意到 cv2.imshow() 产生了一个短暂的停顿,在那个时候,C# 客户端有机会更新输出及其发送到的目标。我首先尝试在 Python 中暂停当前的 运行ning 线程,它确实起作用了,输出开始在 C# 端弹出,但应用程序仍然没有响应。

我注意到当 showFeed 为 False 时,我实际上可以通过在 else 子句中使用 cv2.waitkey(1)cv2.imread('') 使回调输出显示在 C# 中:

while (self.is_running):
...
    if self.showFeed:
        cv2.imshow("image", frame)
    else:
        #cv2.imread('')
        # or 
        cv2.waitkey(1)
    ...

并写成:

while (self.is_running):
...
    if self.showFeed:
        cv2.imshow("image", frame)
    else:
        cv2.namedWindow('image', cv2.WINDOW_OPENGL)
        cv2.waitKey(1)
        cv2.destroyAllWindows()
    ...

输出显示正常,应用程序再次响应,但是,不断创建和销毁空的 Opencv window 不是解决方案,因为它会闪烁并且非常糟糕。

我需要补充一点,在 c# 上使用 timer() 控制事件来打印输出并保持应用程序响应不起作用,创建新线程似乎也不起作用。似乎不能像这样使用这种方式编组的回调(不过我很高兴听到我错了)。

当我找到比这些更好的解决方案时,我会更新这个答案,无论是在 C# 方面(运行在线程上设置回调)还是在 Python 方面,使可见 window 或完全解决此问题。

更新

我删除了 Python 方面的更改并在 C# 部分实现了线程。初始线程不起作用的原因是,所有互操作都必须在同一个线程中调用,这意味着所有导入的方法,如 InitializeStartAddCallback 必须在同一个线程中进行设置和 运行。