Python調用微信OCR識別文字和座標

原理

在看雪看到一篇文章:逆向調用QQ截圖NT與WeChatOCR-軟件逆向。裏面說了怎麼調用微信和QQ本地的OCR模型,還有很詳細的分析過程。

我稍微看了下文章,多的也看不懂。大概流程是使用mmmojo.dll這個dll來與WeChatOCR.exe做通信的,也是用它來啓動和關閉WeChatOCR.exe進程的。所以關鍵只需要知道這個dll裏的導出函數怎麼使用,就能自己實現調用OCR。並且可以脫離微信,不需要啓動微信就能調用。既然這樣,那完全可以使用Python加載mmmojo.dll啓動WeChatOCR.exe並和它通信進行OCR識別。

代碼怎麼實現的就不多說的,感興趣的可以看github的源碼。我就說下有意思的一個技巧和一個踩坑的地方

回調指針技巧

OCR識別成功完成後會調用你給定的回調函數,並將結果作爲參數傳給回調函數。而其中一個回調函數的原型是static void OCRRemoteOnConnect(bool is_connected, void* user_data);。第一個參數是當前連接狀態,比較有意思的是第二個參數


第二個參數是你給定的一個指針,它可以通過SetMMMojoEnvironmentCallbacks這個導出函數來設置,然後你就可以在回調函數裏訪問到這個指針。這個有什麼用呢?就以上面github裏的C++代碼爲例,代碼裏是將它設置爲類的this指針,然後在建立連接後調用OCRRemoteOnConnect回調函數時,通過這個this指針改變類變量m_connect_con_var,然後你才能調用DoOCRTask,如果m_connect_con_var變量沒有被設置,說明沒有連接成功,就一直等待。


搜了一下,在Python裏也可以實現這樣一個操作,把這個值設置成Python類對象,然後就可以在回調函數訪問這個類對象。原理大概像這篇文章:python - Back-casting a ctypes.py_object in a callback - Stack Overflow。先使用ctypes.py_object將對象轉化爲一個PyObject指針傳給c層,然後在回調函數裏再通過ctypes.cast(context, py_object).value得到這個對象,在這個項目裏的代碼如下:

# 將self轉爲c指針設置成user_data
SetMMMojoEnvironmentCallbacks(m_mmmojo_env_ptr, 0, py_object(self))
# 在回調函數裏使用它
def OCRRemoteOnConnect(is_connected:c_bool, user_data:py_object):
    print(f"OCRRemoteOnConnect 回調函數被調用, 參數, is_connected: {is_connected}")
    if user_data:
        manager_obj:OcrManager = cast(user_data, py_object).value
        manager_obj.SetConnectState(True)

踩坑

調用dll時的參數不能直接用c_wchar_p,需要先賦值給一個變量,不然會被垃圾回收機制給回收了。而且錯誤很難定位,不會報錯,程序直接終止

# 錯誤代碼
SetMMMojoEnvironmentInitParams(m_mmmojo_env_ptr, 2, c_wchar_p(m_exe_path))
# 正確代碼
c_m_exe_path = c_wchar_p(m_exe_path)
SetMMMojoEnvironmentInitParams(m_mmmojo_env_ptr, 2, c_m_exe_path)

另外還要注意的它的生命週期,和使用的時間。有些使用比較久的,你還得定義成全局變量或者賦值給self.

如何使用

安裝

我已經發布到了pypi上,可以使用pip安裝:pip install wechat-ocr

如果使用的是國內源,可能還沒有更新,可以使用pip install wechat-ocr -i https://pypi.org/simple來使用官方源安裝

使用

import os
import json
import time
from wechat_ocr.ocr_manager import OcrManager, OCR_MAX_TASK_ID


wechat_ocr_dir = "C:\\Users\\Administrator\\AppData\\Roaming\\Tencent\\WeChat\\XPlugin\\Plugins\\WeChatOCR\\7057\\extracted\\WeChatOCR.exe"
wechat_dir = "D:\\GreenSoftware\\WeChat\\3.9.6.32"

def ocr_result_callback(img_path:str, results:dict):
    result_file = os.path.basename(img_path) + ".json"
    print(f"識別成功,img_path: {img_path}, result_file: {result_file}")
    with open(result_file, 'w', encoding='utf-8') as f:
       f.write(json.dumps(results, ensure_ascii=False, indent=2))

def main():
    ocr_manager = OcrManager(wechat_dir)
    # 設置WeChatOcr目錄
    ocr_manager.SetExePath(wechat_ocr_dir)
    # 設置微信所在路徑
    ocr_manager.SetUsrLibDir(wechat_dir)
    # 設置ocr識別結果的回調函數
    ocr_manager.SetOcrResultCallback(ocr_result_callback)
    # 啓動ocr服務
    ocr_manager.StartWeChatOCR()
    # 開始識別圖片
    ocr_manager.DoOCRTask(r"T:\Code\WeChat\OCR\Python\img\1.png")
    ocr_manager.DoOCRTask(r"T:\Code\WeChat\OCR\Python\img\2.png")
    ocr_manager.DoOCRTask(r"T:\Code\WeChat\OCR\Python\img\3.png")
    time.sleep(1)
    while ocr_manager.m_task_id.qsize() != OCR_MAX_TASK_ID:
        pass
    # 識別輸出結果
    ocr_manager.KillWeChatOCR()
    

if __name__ == "__main__":
    main()

運行結果:

源碼倉庫

https://github.com/kanadeblisst00/wechat_ocr

aardio版本

aardio作者也實現了一個aardio版本:https://mp.weixin.qq.com/s/kYGGyjKW-GJxlqGkmQfuBg

本文由博客一文多發平臺 OpenWrite 發佈!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章