使用Python进行视频流OCR

这篇博客将介绍如何使用Python,进行视频流OCR;

Optical Character Recognition OCR光学字符识别

之前的博客介绍了如何使用快速傅立叶变换(FFT)检测图像和文档中的模糊。使用这种方法,能够检测出模糊、低质量的图像,然后提醒用户应该尝试捕获更高质量的版本,以便能够对其进行OCR。

这篇博客将介绍如何对视频流进行OCR。首先需要检测模糊、低质量的帧过滤掉,只对足够质量的帧进行OCR。原理是使用FFT。

由于光照条件、相机镜头自动聚焦和运动模糊的快速变化,视频自然会有低质量的帧。
需要检测这些低质量的帧并丢弃它们,而不是试图对这些低质量的帧进行OCR,这最终会导致低OCR精度(或更糟的,完全没有意义的结果)。

检测低质量帧的一种简单方法是使用模糊检测。因此使用FFT模糊检测器处理视频流。保证OCR管道能够在视频流上运行,同时仍保持高精度。

1. 效果图

OCR视频效果图如下:
在这里插入图片描述

正确识别某一帧非模糊,及ocr卡片内容效果图如下:
顶部原始图:包含字符串模糊(Blurry)非模糊(Not Blurry)及fft计算得到的平均幅值;原始图上绘制卡片边框;
中间是卡片的自顶向下的鸟瞰图;同时绘制了识别的字符串边框;
底部是pytesseart识别出的字符串;(可以看到柿子中英文识别正确,英文persimmon识别完全,底部的水果有些模糊并未正确识别)
在这里插入图片描述
模糊直接丢弃不进行ocr效果图如下:

在这里插入图片描述

2. 原理

  • 编写操作高质量图像的计算机视觉代码总是比编写低质量图像容易。使用FFT模糊检测方法有助于确保只有更高质量的图像进入管道。
  • FFT模糊检测器还有另一个用途——它可以用来丢弃视频流中的低质量帧,否则OCR将无法处理这些帧。 由于照明条件的快速变化(例如,在阳光明媚的日子里走进黑暗的房间)、相机镜头的自动对焦或最常见的运动模糊,视频流自然会有低质量的帧。

识别中文需要单独设置 pytesseart

# 转换颜色空间,以进行ocr
rgb = cv2.cvtColor(card, cv2.COLOR_BGR2RGB)
# lang=chi_sim+eng 检测中文+字母+数字
# --psm 11 检测稀疏编码,可以设置 --psm 6
results = pytesseract.image_to_data(rgb,
                    lang='chi_sim+eng',
                    config='--psm 11',
                    output_type=Output.DICT)

cv2.imshow设置中文可参考

3. 源码

# USAGE
# python ocr_video.py --input images/card.mp4 --output output/ocr_video_output.avi

# helpers.py: #工具类,将视频OCR脚本的输出作为单独的输出视频写入磁盘
# blur_detection: 子模块,检测视频流中的模糊帧
# video_ocr: 将视频ocr的结果写入单独的视频文件
# images/x.mp4:要进行ocr的视频文件
# output/x.avi: ocr结果的单独视频文件
# visualization.py: 可视化输出检测结果,包括3部分:1. 检测到名片且注释模糊/不模糊的原始输入框(顶部)
#                                            2. 检测到文本的名片的自顶向下转换(中间)
#                                            3. 自顶向下转换的OCR文本本身(底部)

# 导入必要的包
import argparse
import time

import cv2
import imutils
import numpy as np
import pytesseract  # 提供Tesseract OCR的接口
from imutils.perspective import four_point_transform  # (四点变换):应用透视变换,以获得正在OCR'ing的名片的自顶向下/鸟瞰视图
from imutils.video import VideoStream
from pytesseract import Output

from blur_detection.blur_detector import detect_blur_fft  # fft模糊检测器
from pyimagesearch.helpers import addChiText  # cv2.imshow添加中文
from pyimagesearch.helpers import cleanup_text  # 清理OCR文本,去掉非ASCII字符,这样就可以使用cv2在输出图像上绘制OCR文本
from video_ocr.visualization import VideoOCROutputBuilder  # 可视化生成器

# 构建命令行参数及解析
# --input 可选输入视频路径
# --output 可选输出视频路径,默认使用网络摄像头。
# --min-conf 过滤弱检测的置信度参数
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--input", type=str, default='images/card.mp4',
                help="path to optional input video (webcam will be used otherwise)")
ap.add_argument("-o", "--output", type=str,
                help="path to optional output video")
ap.add_argument("-c", "--min-conf", type=int, default=50,
                help="minimum confidence value to filter weak text detection")
args = vars(ap.parse_args())

# 初始化videoOcrOutputBuilder,以轻松可视化
outputBuilder = None

# 初始化输出视频写入器和输出视频的空间维度
writer = None
outputW = None
outputH = None

# 创建一个命名的窗口,以进行输出Ocr可视化,以及设置窗口位置
# 使用cv2.moveWindow函数在屏幕上移动窗口。需要执行移动操作,因为输出窗口的大小是动态的——它将随着输出的增长和收缩而增长。
cv2.namedWindow("Output")

# 初始化一个布尔变量:显示使用Webcam还是输入视频文件
webcam = not args.get("input", False)

# 如果未提供视频路径,获取网络摄像头指针
if webcam:
    print("[INFO] starting video stream...")
    vs = VideoStream(src=0).start()
    time.sleep(2.0)

# 否则,获取视频流指针
else:
    print("[INFO] opening video file...")
    vs = cv2.VideoCapture(args["input"])

# i = 0
# 遍历每一帧
while True:
    # 获取摄像头/文件流的帧,并处理
    orig = vs.read()
    orig = orig if webcam else orig[1]

    # 如果获取的是视频流文件,且没有获取到帧,则表示到达了文件末尾
    if not webcam and orig is None:
        break
    orig = orig + 50  # 照片太暗,批量加亮50
    orig = orig[200:700, :]

    # 调整帧大小,计算新宽度与旧宽度的比率
    # 将透视变换应用于原始高分辨率帧时,将需要此比率。
    frame = imutils.resize(orig, width=500)
    ratio = orig.shape[1] / float(frame.shape[1])

    # 如果VideoOCROutputBuilder视频可视化器是None,初始化它
    if outputBuilder is None:
        outputBuilder = VideoOCROutputBuilder(frame)

    # 初始化卡片和Ocr输出 ROIs
    card = None
    ocr = None

    # 转换帧为灰度图,fft检测文本/文档模糊
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    (mean, blurry) = detect_blur_fft(gray, thresh=-9)

    # 绘制是否模糊,模糊红色,不模糊绿色
    color = (0, 0, 255) if blurry else (0, 255, 0)
    text = "Blurry ({:.4f})" if blurry else "Not Blurry ({:.4f})"
    text = text.format(mean)
    cv2.putText(frame, text, (10, 25), cv2.FONT_HERSHEY_SIMPLEX,
                0.7, color, 2)

    # 卡片ROI将包含名片的自上而下转换(如果在当前帧中找到名片),而ocr将包含ocr文本本身。
    # 只有当帧不模糊时,进行Ocr检测
    if not blurry:
        # cv2.imwrite("images/card" + str(random.randint(0, 100)) + ".jpg", orig)
        # 使用高斯核对帧进行平滑,执行边缘检测
        blurred = cv2.GaussianBlur(gray, (7, 7,), 0)
        edged = cv2.Canny(blurred, 75, 200)

        # 在边缘图中寻找轮廓,按面积大小倒序排列取前5个
        # 名片将是具有四个顶点的输入帧中最大的ROI
        cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL,
                                cv2.CHAIN_APPROX_SIMPLE)
        cnts = imutils.grab_contours(cnts)
        cnts = sorted(cnts, key=cv2.contourArea, reverse=True)[:1]

        # 初始化轮廓线列表和卡片对应
        cardCnt = None

        # 遍历轮廓
        for c in cnts:
            # # 近似轮廓
            # peri = cv2.arcLength(c, True)
            # approx = cv2.approxPolyDP(c, 0.02 * peri, True)
            #
            # # 如果有4个顶点,则认为找到了卡片的边缘线
            # if len(approx) == 4:
            #     cardCnt = approx
            #     break
            rect = cv2.minAreaRect(c)
            box = cv2.boxPoints(rect)
            box = np.int0(box)
            cardCnt = box

        # 如果找到了卡片,则进行ocr
        if cardCnt is not None:
            # 在帧上绘制轮廓线
            cv2.drawContours(frame, [cardCnt], -1, (0, 255, 0), 3)

            # 对原始的高分辨率图像应用透视变换(以便更好地进行OCR
            # 对卡片应用4点透视变换以获取自上而下的鸟瞰图
            card = four_point_transform(orig,
                                        cardCnt.reshape(4, 2) * ratio)

            # 开辟空间以用于可视化
            ocr = np.zeros(card.shape, dtype="uint8")

            # 转换颜色空间,以进行ocr
            rgb = cv2.cvtColor(card, cv2.COLOR_BGR2RGB)
            # lang=chi_sim+eng 检测中文+字母+数字
            # --psm 11 检测稀疏编码
            results = pytesseract.image_to_data(rgb,
                                                lang='chi_sim+eng',
                                                config='--psm 11',
                                                output_type=Output.DICT)

            # 使用ocr文本本身对输出ocr可视化进行注释
            # 遍历每一个识别出的单独的文本
            for i in range(0, len(results["text"])):
                # 提取当前文本的边界框
                x = results["left"][i]
                y = results["top"][i]
                w = results["width"][i]
                h = results["height"][i]

                # 提取文本和置信度值
                text = results["text"][i]
                conf = int(float(results["conf"][i]))

                # 过滤弱检测
                if conf > args["min_conf"]:
                    # 去除非ASCII码来处理文本
                    text = cleanup_text(text)

                    # 处理后的文本非空,在文本周围绘制边界框
                    if len(text) > 0:
                        cv2.rectangle(card, (x, y), (x + w, y + h),
                                      (0, 255, 0), 2)
                        # cv2.putText(ocr, text, (x, y - 10),
                        #             cv2.FONT_HERSHEY_SIMPLEX, 0.5,
                        #             (0, 0, 255), 1)

                        # 添加中文字体
                        ocr = addChiText(ocr, text, (x, y - 10), (255, 0, 0), 25)

    # 构建最终ocr可视化输出
    output = outputBuilder.build(frame, card, ocr)

    # 检查是否设置输出视频文件路径,及视频写入器
    if args["output"] is not None and writer is None:
        # 获取输出视频帧维度及初始化视频写入器
        output = imutils.resize(output, height=600)
        (outputH, outputW) = output.shape[:2]
        fourcc = cv2.VideoWriter_fourcc(*"MJPG")
        writer = cv2.VideoWriter(args["output"], fourcc, 27,
                                 (outputW, outputH), True)

    # 如果视频写入器非空,写入输出到单独视频文件
    if writer is not None:
        # 强制调整视频流输出以匹配输出视频的维度
        outputFrame = cv2.resize(output, (outputW, outputH))
        writer.write(outputFrame)

    # 展示可视化结果
    cv2.imshow("Output", imutils.resize(output, height=600))
    # i = i + 1
    # cv2.imwrite("imgs/" + str(i) + ".jpg", cv2.resize(output, (250, 600)))
    cv2.moveWindow("Output", 0, 0)
    key = cv2.waitKey(1) & 0xFF
    if not blurry:
        key = cv2.waitKey(0)
    else:
        key = cv2.waitKey(1) & 0xFF

    # 按下‘q’键,退出循环
    if key == ord("q"):
        break

# 释放视频指针
# 如果使用网络摄像头
if webcam:
    vs.stop()
# 如果使用视频流指针
else:
    vs.release()

# 关闭所有窗口
cv2.destroyAllWindows()

参考

猜你喜欢

转载自blog.csdn.net/qq_40985985/article/details/124910773
今日推荐