图像形态学操作,包括膨胀、腐蚀基本操作,即可实现简单场景的文字检测。
其中,“膨胀”就是对图像中的高亮部分进行扩张,让白色区域变多;“腐蚀”就是图像中的高亮部分被蚕食,让黑色区域变多。通过膨胀、腐蚀的一系列操作,可将文字区域的轮廓突出,并消除掉一些边框线条,再通过查找轮廓的方法计算出文字区域的位置出来。
关于 形态学详情请查看
主要的步骤如下:
示例:在以下示例中,我选取了绿通道,因为该图片是绿色的,该通道文字信息含量较高。
import cv2
import numpy as np
def match_loc(model_imgpath,test_imgpath): # 通过匹配获取目标图片相对位置
model_img = cv2.imread(model_imgpath)
test_img = cv2.imread(test_imgpath)
b_model, g_model, r_model = cv2.split(model_img)
b_test, g_test, r_test = cv2.split(test_img)
w, h = g_model.shape[::-1]
result = cv2.matchTemplate(g_test, g_model, cv2.TM_CCOEFF_NORMED)
(min_val, score, min_loc, max_loc) = cv2.minMaxLoc(result)
bottom_right = (max_loc[0] + w, max_loc[1] + h)
print('max_loc',max_loc)
kuang = cv2.rectangle(test_img, max_loc, bottom_right, 255, 2)
cv2.imshow('test_img',kuang)
cv2.waitKey(0)
return test_img,max_loc, g_test
def Morph_exam(test_img,g_test):
sobel = cv2.Sobel(g_test, cv2.CV_8U, 1, 0, ksize=3)
ret, binary = cv2.threshold(sobel, 0, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY)
cv2.imshow('binary',binary)
cv2.waitKey(0)
# 形态核:膨胀让轮廓突出--- 腐蚀去掉细节--再膨胀,让轮廓更明显
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)
cv2.imshow('dilation2',dilation2)
cv2.waitKey(0)
# 查找轮廓和筛选文字区域
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 < 800):
continue
rect = cv2.minAreaRect(cnt)
print("rect is: ",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(test_img, [box], 0, (0, 255, 0), 2)
cv2.imshow('img', test_img)
cv2.waitKey(0)
return region
if __name__ == '__main__':
model_imgpath = './HK/moban.png'
test_imgpath = './HK/test.png'
test_img,max_loc, g_test = match_loc(model_imgpath, test_imgpath)
Morph_exam(test_img,g_test)
import numpy as np
import imutils
import cv2,os
def model_Template(imgpath):
print('img_path', imgpath)
image = cv2.imread(imgpath)
assert image.shape[2]==3
g_gray = image[:,:,1]
templ_gray = cv2.Canny(g_gray, 50, 200)
return image,templ_gray
def loop_match(image,gray,templ_gray):
tH, tW = templ_gray.shape[:2]
found = None
for scale in np.linspace(0.2, 1.0, 5)[::-1]:
resized = imutils.resize(gray, width=int(gray.shape[1] * scale))
r = gray.shape[1] / float(resized.shape[1])
if resized.shape[0] < tH or resized.shape[1] < tW:
break
result = cv2.matchTemplate(resized, templ_gray, cv2.TM_CCOEFF)
(_, maxVal, _, maxLoc) = cv2.minMaxLoc(result)
if maxVal>14800000:
print('maxVal:%s, match OK!'% maxVal)
clone = np.dstack([resized, resized, resized])
cv2.rectangle(clone, (maxLoc[0], maxLoc[1]),
(maxLoc[0] + tW, maxLoc[1] + tH), (0, 0, 255), 2)
cv2.imshow("Visualize", clone)
cv2.waitKey(0)
if found is None or maxVal > found[0]:
found = (maxVal, maxLoc, r)
# 变量并根据调整后的比率计算边界框的(x,y)坐标
(_, maxLoc, r) = found
(startX, startY) = (int(maxLoc[0] * r), int(maxLoc[1] * r))
(endX, endY) = (int((maxLoc[0] + tW) * r), int((maxLoc[1] + tH) * r))
cv2.rectangle(image, (startX, startY), (endX, endY), (0, 0, 255), 2)
cv2.imshow("Image", image)
cv2.waitKey(0)
if __name__ == '__main__':
imgpath = './HK/moban1.png'
imgdirs = './HK'
_,templ_gray = model_Template(imgpath)
for img in os.listdir(imgdirs):
img_path = os.path.join(imgdirs,img)
image,gray = model_Template(img_path)
loop_match(image,gray,templ_gray)
MSER最大稳定极值区域(MSER-Maximally Stable Extremal Regions),该算法是2002提出的,主要是基于分水岭的思想来做图像中斑点的检测。
关于分水岭详情 请点击查看
原理:MSER对一幅已经处理成灰度的图像做二值化处理,这个处理的阈值从0到255递增,这个阈值的递增类似于在一片土地上做水平面的上升,随着水平面上升,高高低低凹凸不平的土地区域就会不断被淹没,这就是分水岭算法,而这个高低不同,就是图像中灰度值的不同。而在一幅含有文字的图像上,有些区域(比如文字)由于颜色(灰度值)是一致的,因此在水平面(阈值)持续增长的一段时间内都不会被覆盖,直到阈值涨到文字本身的灰度值时才会被淹没,这些区域就叫做最大稳定极值区域。
关于【MSER的基本原理】 请点击。
q ( i ) = ∣ Q i − Q i − Δ ∣ ∣ Q i − Δ ∣ q(i)=\frac{|Q_i - Q_{i-\Delta}|}{|Q_{i-\Delta}|} q(i)=∣Qi−Δ∣∣Qi−Qi−Δ∣
其中, Q i Q_i Qi表示阈值为 i i i 时的某一连通区域, Δ \Delta Δ为灰度阈值的微小变化量, q ( i ) q(i) q(i)为阈值是 i i i 时的区域 Q i Q_i Qi 的变化率。
当 q ( i ) q(i) q(i)为局部极小值时,则 Q i Q_i Qi为最大稳定极值区域。
示例:mser = cv2.MSER_create(_delta=2, _min_area=200, _max_variation=0.7)
MSER官网: https://docs.opencv.org/3.4/d3/d28/classcv_1_1MSER.html
MSER具有以下特点:
1、对图像灰度具有仿射变换的不变性;
2、稳定性:具有相同阈值范围内所支持的区域才会被选择;
3、无需任何平滑处理就可以实现多尺度检测,即小的和大的结构都可以被检测到。
NMS是经常伴随图像区域检测的算法,作用是去除重复的区域,在人脸识别、物体检测等领域都经常使用,全称是非极大值抑制(non maximum suppression),顾名思义就是抑制不是极大值的元素,所以用在这里就是抑制不是最大框的框,也就是去除大框中包含的小框。
NMS的基本思想是遍历将所有的框得分排序,选中其中得分最高的框,然后遍历其余框找到和当前最高分的框的重叠面积(IOU)大于一定阈值的框,删除。然后继续这个过程,找另一个得分高的框,再删除IOU大于阈值的框,循环。
在这个例子中,就是设定一个IOU阈值(比如0.5,也就是如果两个框的重叠面积大于其中一个框的50%,那么就删除那个框),然后遍历所有框,对剩下的每个框,遍历判断其余框中与他重叠面积大于阈值的,则删除。最后剩下的就是不包含重叠部分的文本框了。
import cv2
import numpy as np
def match_loc(model_imgpath,test_imgpath):
model_img = cv2.imread(model_imgpath)
test_img = cv2.imread(test_imgpath)
b_model, g_model, r_model = cv2.split(model_img)
b_test, g_test, r_test = cv2.split(test_img)
w, h = g_model.shape[::-1]
result = cv2.matchTemplate(g_test, g_model, cv2.TM_CCOEFF_NORMED)
(min_val, score, min_loc, max_loc) = cv2.minMaxLoc(result)
bottom_right = (max_loc[0] + w, max_loc[1] + h)
print('max_loc',max_loc)
kuang = cv2.rectangle(test_img, max_loc, bottom_right, 255, 2)
cv2.imshow('test_img',test_img)
cv2.waitKey(0)
return test_img,max_loc, g_test
def MSER(test_img,g_test):
imgcopy = test_img.copy()
mser = cv2.MSER_create()
regions, _ = mser.detectRegions(g_test) # 获取文本区域
hulls = [cv2.convexHull(p.reshape(-1, 1, 2)) for p in regions] # 绘制文本区域
# cv2.polylines(test_img, hulls, 1, (0, 255, 0))
# cv2.imshow('temp_img', test_img)
# 将不规则检测框处理成矩形框
keep = []
for c in hulls:
x, y, w, h = cv2.boundingRect(c)
# 筛选出可能是文本区域的方框,若去掉会出现很多小方框
if (h > w * 1.3) or h < 25:
continue
keep.append([x, y, x + w, y + h])
cv2.rectangle(imgcopy, (x, y), (x + w, y + h), (255, 255, 0), 1)
cv2.imshow("imgcopy", imgcopy)
cv2.waitKey(0)
return keep
def NMS(boxes, overlapThresh): # NMS 方法(Non Maximum Suppression,非极大值抑制)
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")
def MSER_NMS(test_img,g_test):
test_copy = test_img.copy()
keep = MSER(test_img, g_test)
keep2 = np.array(keep)
pick = NMS(keep2, 0.5)
print("[x] after applying non-maximum, %d bounding boxes" % (len(pick)))
for (startX, startY, endX, endY) in pick:
cv2.rectangle(test_copy, (startX, startY), (endX, endY), (255, 185, 120), 2)
cv2.imshow("After NMS", test_copy)
cv2.waitKey(0)
cv2.destroyAllWindows()
if __name__ == '__main__':
model_imgpath = './HK/moban1.png'
test_imgpath = './HK/moban.png'
test_img,max_loc, g_test = match_loc(model_imgpath, test_imgpath)
MSER_NMS(test_img, g_test)
特别鸣谢:
非极大值抑制: https://www.pyimagesearch.com/2014/11/17/non-maximum-suppression-object-detection-python/