【问题标题】:Why are C# callbacks only executed when the python module uses cv.imshow()?为什么只有在 python 模块使用 cv.imshow() 时才执行 C# 回调?
【发布时间】:2020-07-25 18:43:44
【问题描述】:

我想不出一个更好的更具描述性的标题,因为它涉及 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# 上,这种行为不会发生,如果我尝试使用模块而不将网络摄像头提要设置为trueC# 应用程序挂起,这是因为调用主操作的Start()成为阻塞调用,并且不返回任何回调。
顺便说一下我的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 c++ unmanaged pybind11


    【解决方案1】:

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

    然后我注意到cv2.imshow() 创建了一个短暂的暂停,在此期间,C# 客户端有机会更新输出及其发送到的内容。我首先尝试在 Python 中暂停当前正在运行的线程,它实际上有点工作,输出开始在 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 窗口并不是解决方案,因为它会闪烁并且非常糟糕。

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

    当我找到比 C# 方面(在线程上运行回调)或 Python 端更好的解决方案时,我会更新这个答案,制作一个可见的窗口或完全解决这个问题。

    更新

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

    【讨论】:

      猜你喜欢
      • 2016-06-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多