文字检测篇-传统篇

       文字检测是文字识别过程中的一个非常重要的环节,文字检测的主要目标是将图片中的文字区域位置检测出来,以便于进行后面的文字识别,只有找到了文本所在区域,才能对其内容进行识别。

       文字检测的场景主要分为两种,一种是简单场景,另一种是复杂场景。其中,简单场景的文字检测较为简单,例如像书本扫描、屏幕截图、或者清晰度高、规整的照片等;而复杂场景,主要是指自然场景,情况比较复杂,例如像街边的广告牌、产品包装盒、设备上的说明、商标等等,存在着背景复杂、光线忽明忽暗、角度倾斜、扭曲变形、清晰度不足等各种情况,文字检测的难度更大。

简单场景、复杂场景中常用的文字检测方法,包括形态学操作、MSER+NMS、SWT、CTPN、SegLink、EAST等方法:

1、简单场景:形态学操作法

通过利用计算机视觉中的图像形态学操作,包括膨胀、腐蚀基本操作,即可实现简单场景的文字检测,例如检测屏幕截图中的文字区域位置

中,“膨胀”就是对图像中的高亮部分进行扩张,让白色区域变多;“腐蚀”就是图像中的高亮部分被蚕食,让黑色区域变多。通过膨胀、腐蚀的一系列操作,可将文字区域的轮廓突出,并消除掉一些边框线条,再通过查找轮廓的方法计算出文字区域的位置出来。主要的步骤如下:

  • 读取图片,并转为灰度图
  • 图片二值化,或先降噪后再二值化,以便简化处理
  • 膨胀、腐蚀操作,突出轮廓、消除边框线条
  • 查找轮廓,去除不符合文字特点的边框
  • 返回文字检测的边框结果
import numpy as np
import cv2


def traditional_image_processing(image):
    # 转化成灰度图
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    #使用锐化操作,突出图像的高频特征,好像没啥用处
    #gray = cv2.filter2D(gray, -1,kernel=np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]], np.float32))  # 对图像进行滤波,是锐化操作
    #gray = cv2.filter2D(gray, -1, kernel=np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]], np.float32))

    # 利用Sobel边缘检测生成二值图
    sobel = cv2.Sobel(gray, cv2.CV_8U, 0, 1, ksize=3)
    cv2.imshow("sobel",sobel)
    #gradY = cv2.Sobel(sobel, ddepth=cv2.CV_8U, dx=0, dy=1,ksize=3)
    #sobel = cv2.subtract(sobel, gradY)  # 使用减法作图像融合?
    # 二值化
    ret, binary = cv2.threshold(sobel, 0, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY)

    # 膨胀、腐蚀
    element1 = cv2.getStructuringElement(cv2.MORPH_RECT, (30, 9))
    element2 = cv2.getStructuringElement(cv2.MORPH_RECT, (24, 6))

    # 膨胀一次,让轮廓突出
    dilation = cv2.dilate(binary, element2, iterations=1)

    # 腐蚀一次,去掉细节
    erosion = cv2.erode(dilation, element1, iterations=1)

    # 再次膨胀,让轮廓明显一些
    dilation2 = cv2.dilate(erosion, element2, iterations=2)

    #  查找轮廓和筛选文字区域
    region = []
    _,contours, hierarchy = cv2.findContours(dilation2, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    for i in range(len(contours)):
        cnt = contours[i]

        # 计算轮廓面积,并筛选掉面积小的
        area = cv2.contourArea(cnt)
        if (area < 1000):
            continue

        # 找到最小的矩形
        rect = cv2.minAreaRect(cnt)
        print("rect is: ")
        print(rect)

        # box是四个点的坐标
        box = cv2.boxPoints(rect)
        box = np.int0(box)

        # 计算高和宽
        height = abs(box[0][1] - box[2][1])
        width = abs(box[0][0] - box[2][0])

        # 根据文字特征,筛选那些太细的矩形,留下扁的
        if (height > width * 1.3):
            continue

        region.append(box)

    # 绘制轮廓
    for box in region:
        cv2.drawContours(img, [box], 0, (0, 255, 0), 2)

    cv2.imshow('img', img)

if __name__ == '__main__':
    img = cv2.imread('22.png', cv2.IMREAD_COLOR)
    traditional_image_processing(img)
    cv2.waitKey(0)

2、简单场景:MSER+NMS检测法

MSER(Maximally Stable Extremal Regions,最大稳定极值区域)是一个较为流行的文字检测传统方法(相对于基于深度学习的AI文字检测而言),在传统OCR中应用较广,在某些场景下,又快又准。

MSER算法是在2002提出来的,主要是基于分水岭的思想进行检测。分水岭算法思想来源于地形学,将图像当作自然地貌,图像中每一个像素的灰度值表示该点的海拔高度,每一个局部极小值及区域称为集水盆地,两个集水盆地之间的边界则为分水岭。

MSER的处理过程是这样的,对一幅灰度图像取不同的阈值进行二值化处理,阈值从0至255递增,这个递增的过程就好比是一片土地上的水面不断上升,随着水位的不断上升,一些较低的区域就会逐渐被淹没,从天空鸟瞰,大地变为陆地、水域两部分,并且水域部分在不断扩大。在这个“漫水”的过程中,图像中的某些连通区域变化很小,甚至没有变化,则该区域就被称为最大稳定极值区域。在一幅有文字的图像上,文字区域由于颜色(灰度值)是一致的,因此在水平面(阈值)持续增长的过程中,一开始不会被“淹没”,直到阈值增加到文字本身的灰度值时才会被“淹没”。该算法可以用来粗略地定位出图像中的文字区域位置。

听起来这个处理过程似乎非常复杂,好在OpenCV中已内置了MSER的算法,可以直接调用,大大简化了处理过程。

def mser_image_processing(image):

    gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
    visual = image.copy()
    original = gray.copy()

    mser = cv2.MSER_create()
    regions,_=mser.detectRegions(gray)
    hulls = [cv2.convexHull(p.reshape(-1,1,2)) for p in regions]
    cv2.polylines(image,hulls,1,(0,255,0))
    cv2.imshow("image",image)

    keep=[]
    for c in hulls:
        x,y,w,h = cv2.boundingRect(c)
        keep.append([x,y,x+w,y+h])
        #cv2.rectangle(visual,(x,y),(x+w,y+h),(255,255,0),1)
    keep = np.array(keep)
    boxes = nms(keep,0.5)
    for box in boxes:
        cv2.rectangle(visual, (box[0], box[1]), (box[2], box[3]), (255, 0, 0), 1)
    cv2.imshow("hulls",visual)

# NMS 方法(Non Maximum Suppression,非极大值抑制)
def nms(boxes, overlapThresh):
    if len(boxes) == 0:
        return []

    if boxes.dtype.kind == "i":
        boxes = boxes.astype("float")

    pick = []

    # 取四个坐标数组
    x1 = boxes[:, 0]
    y1 = boxes[:, 1]
    x2 = boxes[:, 2]
    y2 = boxes[:, 3]

    # 计算面积数组
    area = (x2 - x1 + 1) * (y2 - y1 + 1)

    # 按得分排序(如没有置信度得分,可按坐标从小到大排序,如右下角坐标)
    idxs = np.argsort(y2)

    # 开始遍历,并删除重复的框
    while len(idxs) > 0:
        # 将最右下方的框放入pick数组
        last = len(idxs) - 1
        i = idxs[last]
        pick.append(i)

        # 找剩下的其余框中最大坐标和最小坐标
        xx1 = np.maximum(x1[i], x1[idxs[:last]])
        yy1 = np.maximum(y1[i], y1[idxs[:last]])
        xx2 = np.minimum(x2[i], x2[idxs[:last]])
        yy2 = np.minimum(y2[i], y2[idxs[:last]])

        # 计算重叠面积占对应框的比例,即 IoU
        w = np.maximum(0, xx2 - xx1 + 1)
        h = np.maximum(0, yy2 - yy1 + 1)
        overlap = (w * h) / area[idxs[:last]]

        # 如果 IoU 大于指定阈值,则删除
        idxs = np.delete(idxs, np.concatenate(([last], np.where(overlap > overlapThresh)[0])))

    return boxes[pick].astype("int")


if __name__ == '__main__':
    img = cv2.imread('13.png', cv2.IMREAD_COLOR)
    mser_image_processing(img)
    cv2.waitKey(0)

检测结果:

 

  • 7
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值