安装numpy、easyocr后cv2报gapi_wip_gst_GStreamerPipeline属性错误解决
在Python 3.10环境下开发票据OCR识别功能,此前相关代码运行完全正常,执行numpy、easyocr库安装操作后突发运行异常,全程未修改业务代码逻辑。曾尝试降级OpenCV版本,但操作后触发其他新异常,测试过多数线上公开的同类问题解决方案,均未解决问题。
运行代码时抛出AttributeError,报错定位到C:\Users\user\AppData\Roaming\Python\Python310\site-packages\cv2\gapi\__init__.py文件第290行,执行cv.gapi.wip.GStreamerPipeline = cv.gapi_wip_gst_GStreamerPipeline语句时触发异常,提示:
partially initialized module 'cv2' has no attribute 'gapi_wip_gst_GStreamerPipeline' (most likely due to a circular import)
即部分初始化的cv2模块无gapi_wip_gst_GStreamerPipeline属性,大概率由循环导入导致。
代码主要实现票据图片的轮廓提取、透视矫正、灰度处理,最终调用easyocr完成文字识别提取,完整代码如下:
import cv2 import numpy as np import easyocr from nltk.corpus import words from nltk.metrics.distance import jaccard_distance from nltk.util import ngrams # this needs to run only once to load the model into memory file_name = 'Image/Image/7.jpg' image = cv2.imread(file_name) def opencv_resize(image, ratio): width = int(image.shape[1] * ratio) height = int(image.shape[0] * ratio) dim = (width, height) # return cv2.resize(image, dim, interpolation=cv2.INTER_AREA) return cv2.resize(image, dim, interpolation=cv2.INTER_NEAREST) # def plot_rgb(image=image): # return cv2.imshow('rgb', cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) # def plot_gray(image=image): # return cv2.imshow('gray', image) # Downscale image as finding receipt contour is more efficient on a small image resize_ratio = 500 / image.shape[0] original = image.copy() image = opencv_resize(image, resize_ratio) # cv2.imshow('downscaled', image) # Convert to grayscale for further processing gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # plot_gray(gray) ret, img = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY, cv2.THRESH_OTSU) # Detect all contours in Canny-edged image contours, hierarchy = cv2.findContours( img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) image_with_contours = cv2.drawContours( image.copy(), contours, -1, (0, 255, 0), 3) # plot_rgb(image_with_contours) # Get 10 largest contours largest_contours = sorted(contours, key=cv2.contourArea, reverse=True)[:10] image_with_largest_contours = cv2.drawContours( image.copy(), largest_contours, -1, (0, 255, 0), 3) # plot_rgb(image_with_largest_contours) # approximate the contour by a more primitive polygon shape def approximate_contour(contour): peri = cv2.arcLength(contour, True) return cv2.approxPolyDP(contour, 0.032 * peri, True) def get_receipt_contour(contours): # loop over the contours for c in contours: approx = approximate_contour(c) # if our approximated contour has four points, we can assume it is receipt's rectangle # if len(approx) == 4: if len(approx) == 4: return approx get_receipt_contour(largest_contours) receipt_contour = get_receipt_contour(largest_contours) image_with_receipt_contour = cv2.drawContours( image.copy(), [receipt_contour], -1, (0, 255, 0), 2) def contour_to_rect(contour): pts = contour.reshape(4, 2) rect = np.zeros((4, 2), dtype="float32") # top-left point has the smallest sum # bottom-right has the largest sum s = pts.sum(axis=1) rect[0] = pts[np.argmin(s)] rect[2] = pts[np.argmax(s)] diff = np.diff(pts, axis=1) rect[1] = pts[np.argmin(diff)] rect[3] = pts[np.argmax(diff)] return rect / resize_ratio def wrap_perspective(img, rect): # unpack rectangle points: top left, top right, bottom right, bottom left (tl, tr, br, bl) = rect # compute the width of the new image widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2)) widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2)) # compute the height of the new image heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2)) heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2)) # take the maximum of the width and height values to reach # our final dimensions maxWidth = max(int(widthA), int(widthB)) maxHeight = max(int(heightA), int(heightB)) # destination points which will be used to map the screen to a "scanned" view dst = np.array([ [0, 0], [maxWidth - 1, 0], [maxWidth - 1, maxHeight - 1], [0, maxHeight - 1]], dtype="float32") M = cv2.getPerspectiveTransform(rect, dst) return cv2.warpPerspective(img, M, (maxWidth, maxHeight)) scanned = wrap_perspective(original.copy(), contour_to_rect(receipt_contour)) def bw_scanner(image): gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) return gray result = bw_scanner(scanned) correct_words = words.words() def wordDetectionAndExtraction(img): reader = easyocr.Reader(['en']) readImage = reader.readtext(img, detail=0) return readImage print(wordDetectionAndExtraction(result))
报错本质是OpenCV版本与easyocr依赖版本不兼容,加上用户目录下残留多版本OpenCV文件,导致cv2模块加载时出现循环引用,无法正常初始化gapi相关属性。之前盲目降级OpenCV时没有清理干净残留文件,出现版本错配,所以触发其他异常。
- 彻底卸载所有OpenCV相关包,执行以下命令:
pip uninstall -y opencv-python opencv-contrib-python opencv-python-headless
如果之前用conda安装过OpenCV,额外执行conda remove -y opencv。卸载完成后手动删除C:\Users\user\AppData\Roaming\Python\Python310\site-packages路径下所有名称以cv2开头的文件夹,避免旧版本残留文件干扰。 - 安装经过兼容性验证的固定版本依赖,不要使用最新版OpenCV和1.24以上版本numpy:
pip install numpy==1.23.5 opencv-python==4.5.5.62 opencv-contrib-python==4.5.5.62 easyocr==1.7.0 - 调整代码结构,把全局作用域下直接执行的图片读取、处理逻辑放到
if __name__ == "__main__":代码块中,避免模块导入阶段就触发cv2加载,进一步规避循环导入风险。
内容的提问来源于stack exchange,提问作者random

