圖片去黑邊(只考慮了去水平方向上的黑邊)的核心算法是要找到圖片頂部或頂部的黑邊位置,即兩個縱坐標(biāo)值, 主要用到了canny邊緣計算、
houghlines直線檢測、easyocr識別等算法。
給圖片去黑邊的實現(xiàn)邏輯為:
-
先進行canny邊緣計算,再進行houghlines直線檢測,取出圖片的水平邊緣 如果沒有找到水平邊緣,那么不做處理
-
對目標(biāo)水平邊緣進行過濾和分類
過濾邏輯是: 一側(cè)為黑色,另一側(cè)非黑色
分類邏輯是:
上邊是黑色,下邊是非黑色的,且位于圖片水平中線以上,作為候選上邊緣;
上邊是非黑色,下邊是黑色的,,且位于圖片水平中線以下,作為候選下邊緣 -
對候選的上下邊緣從外向內(nèi)逐一校驗,校驗標(biāo)準是邊緣之外不應(yīng)存在文字(因為圖片上的文字對于圖片也是有意義的) 也不應(yīng)存在高度超過一定閾值的元素, 從而得出符合條件且最靠內(nèi)側(cè)的上下邊緣
如果找不到符合條件的上邊緣,那么上邊緣就是0
如果找不到符合條件的下邊緣,那么下邊緣就是圖片高度-1文章來源:http://www.zghlxwxcb.cn/news/detail-800207.html -
根據(jù)找出的上線邊緣對原圖進行裁剪文章來源地址http://www.zghlxwxcb.cn/news/detail-800207.html
import cv2
import numpy as np
import easyocr
def isPixelBlack(pixel):
return pixel[0] <= 10 and pixel[1] <= 10 and pixel[2] <= 10
def checkLineIsBlack(img, width, y):
midX = int((width - 1) / 2)
pixel = img[y, midX]
if not isPixelBlack(pixel):
return False
for x in range(1, midX + 1):
if midX - x >= 0:
leftPixel = img[y, midX - x]
if not isPixelBlack(leftPixel):
return False
if midX + x < width:
rightPixel = img[y, midX + x]
if not isPixelBlack(rightPixel):
return False
return True
def computeBlackPixelNum(img, fromY, toY, x):
totalNum = 0
for y in range(fromY, toY):
curPixel = img[y, x]
if isPixelBlack(curPixel):
totalNum += 1
return totalNum
# 對于接近頂部或底部的邊緣忽略;對于中線附近的邊緣也忽略;
def isLevelLineNeedIgnore(height, y):
if y <= 50 or height - 1 - y <= 50:
return True
# 判斷y是否介于3/8 到 5/8 的高度之間
midZoneStart = int(0.4 * height)
midZoneEnd = int(0.6 * height)
if y >= midZoneStart and y <= midZoneEnd:
return True
return False
# 將寬度的1/6視作最小線段長度
def getMinLineLength(width):
return int(width / 10)
def computeValidFlag(valid_flag_list, left, right):
sum = 0
for index in range(left, right):
if valid_flag_list[index] > 0:
sum += 1
if sum <= 5:
return 0
return sum
# 計算水平線的邊緣類型: 0=無效 1=潛在的上邊緣 2=潛在的下邊緣 3 潛在的邊緣
def checkEdgeType(valid_flag_list, y, height, init):
midY = int(height / 2)
aboveFlag = computeValidFlag(valid_flag_list, max(0, y - 10 - init), y - 10)
belowFlag = computeValidFlag(valid_flag_list, y + 10, min(y + 10 + init, height - 1))
if aboveFlag > 0 and belowFlag > 0:
return 0
elif aboveFlag > 0 and belowFlag == 0 and y > midY:
return 2
elif aboveFlag == 0 and belowFlag > 0 and y < midY:
return 1
elif aboveFlag == 0 and belowFlag == 0:
return 3
return 0
# 挑選合適的上邊緣
def pickOutFinalTopY(img, height, width, valid_topY_array, valid_flag_list, reader):
bestTopY = 0
matchedTopY = []
otherTopY = []
for currentY in valid_topY_array:
validFlagNum = computeValidFlag(valid_flag_list, 0, currentY - 2)
if validFlagNum <= 20:
matchedTopY.append(currentY)
else:
otherTopY.append(currentY)
if len(otherTopY) == 0:
return matchedTopY[0]
else:
matchedTopY.sort()
if len(matchedTopY) > 0:
bestTopY = matchedTopY[len(matchedTopY) - 1]
# 將topY列表升序排列, 逐一驗證是否符合條件
valid_topY_array.sort()
midX = int(width / 2)
for candidateY in valid_topY_array:
if candidateY < bestTopY:
continue
sumFlag = computeValidFlag(valid_flag_list, 0, candidateY)
if sumFlag > 100:
break
sumBlack = computeBlackPixelNum(img, 0, candidateY, midX)
if sumBlack > 100:
break
# ocr讀取 (0,candidateY) 范圍內(nèi)的子圖, 判斷是否包含有文字
# 如果包含了文字,那么就不符合條件
roi = img[0:candidateY, 0:width]
result = reader.readtext(roi)
if len(result) > 0:
break
bestTopY = candidateY
return bestTopY
def pickOutFinalEndY(img, height, width, valid_endY_array, valid_flag_list, reader):
bestEndY = height - 1
matchedEndY = []
otherEndY = []
for currentY in valid_endY_array:
validFlagNum = computeValidFlag(valid_flag_list, currentY + 2, height)
if validFlagNum <= 20:
matchedEndY.append(currentY)
else:
otherEndY.append(currentY)
if len(otherEndY) == 0:
return matchedEndY[0]
else:
matchedEndY.sort(reverse=True)
if len(matchedEndY) > 0:
bestEndY = matchedEndY[0]
# 將endY列表降序排列, 逐一驗證是否符合條件
valid_endY_array.sort(reverse=True)
midX = int(width / 2)
for candidateY in valid_endY_array:
if candidateY > bestEndY:
continue
sum = computeValidFlag(valid_flag_list, candidateY, height)
if sum > 100:
break
sumBlack = computeBlackPixelNum(img, candidateY, height, midX)
if sumBlack > 100:
break
# ocr讀取 (candidateY,height) 范圍內(nèi)的子圖, 判斷是否包含有文字
# 如果包含了文字,那么就不符合條件
roi = img[candidateY:height, 0:width]
result = reader.readtext(roi)
if len(result) > 0:
break
bestEndY = candidateY
return bestEndY
def computeTopAndEnd(img, height, width, valid_flag_list, level_lines, reader):
# 1.過濾出有效的邊緣
valid_topY_array = []
valid_endY_array = []
midY = int(height / 2)
for level_line in level_lines:
x1, y, x2, y2 = level_line[0]
# 臨時劃線
# cv2.line(img, (0, y), (width - 1, y), (0, 0, 255), 1)
# 先判斷是否是有效的邊緣,如果是有效的邊緣, 再放入候選集合中
edgeType = checkEdgeType(valid_flag_list, y, height, 50)
if edgeType == 0:
continue
elif edgeType == 1:
valid_topY_array.append(y)
elif edgeType == 2:
valid_endY_array.append(y)
elif edgeType == 3:
if y > midY:
valid_endY_array.append(y)
elif y < midY:
valid_topY_array.append(y)
if len(valid_topY_array) <= 0 and len(valid_endY_array) <= 0:
return 0, height - 1
# 2.判斷有效的邊緣是否可以上邊緣或下邊緣(這個步驟里可能會用到ocr技術(shù))
finalTopY = 0
finalEndY = height - 1
if len(valid_topY_array) > 0:
finalTopY = pickOutFinalTopY(img, height, width, valid_topY_array, valid_flag_list, reader)
if len(valid_endY_array) > 0:
finalEndY = pickOutFinalEndY(img, height, width, valid_endY_array, valid_flag_list, reader)
# 3.返回上下黑邊縱坐標(biāo)
return finalTopY, finalEndY
# 對于無邊緣的縱坐標(biāo), 重新計算該縱坐標(biāo)上是否存在非黑像素
def recomputeValidFlagList(img, height, width, valid_flag_list):
for y in range(0, height):
if valid_flag_list[y] == 0:
lineBlackFlag = checkLineIsBlack(img, width, y)
if not lineBlackFlag:
valid_flag_list[y] = 1
def recognizeImageValidZone(imagePath, reader):
# 讀取圖片
img = cv2.imread(imagePath)
# 獲取圖像尺寸
height, width = img.shape[:2]
edges = cv2.Canny(img, 100, 200)
lines = cv2.HoughLinesP(edges, 1, np.pi / 180, 100, minLineLength=getMinLineLength(width), maxLineGap=10)
if lines is None:
print(imagePath + "不存在直線")
return 0, height - 1
levelLines = []
for line in lines:
x1, y1, x2, y2 = line[0]
if y1 != y2:
continue
if isLevelLineNeedIgnore(height, y1):
continue
# print(f"水平直線===================={y1}")
# cv2.line(img, (x1, y1), (x2, y2), (0, 255, 0), 2)
levelLines.append(line)
if len(levelLines) == 0:
print(imagePath + "-----不存在水平直線")
return 0, height - 1
# 計算標(biāo)識數(shù)組,用于標(biāo)識各行是否存在非黑像素
valid_flag_list = [0 for _ in range(height)]
# 遍歷邊緣檢測后的圖像,找到邊緣像素的坐標(biāo)
for y in range(edges.shape[0]):
for x in range(edges.shape[1]):
if edges[y][x] != 0: # 如果當(dāng)前像素不是背景(即邊緣)
valid_flag_list[y] = 1
break
recomputeValidFlagList(img, height, width, valid_flag_list)
return computeTopAndEnd(img, height, width, valid_flag_list, levelLines, reader)
def doDropForImage(srcDir, srcFile, targetDir, reader):
# 讀取圖片
img = cv2.imread(srcDir + srcFile)
# 獲取圖像尺寸
height, width = img.shape[:2]
# 獲取起止的縱坐標(biāo)
startY, overY = recognizeImageValidZone(srcDir + srcFile, reader)
crop_img = img[startY:overY + 1, 0:width]
cv2.imwrite(targetDir + srcFile + "_dealed.jpg", crop_img)
def preDropForImage(srcDir, srcFile, targetDir, reader):
# 讀取圖片
img = cv2.imread(srcDir + srcFile)
# 獲取圖像尺寸
height, width = img.shape[:2]
# 獲取起止的縱坐標(biāo)
startY, overY = recognizeImageValidZone(srcDir + srcFile, reader)
# 標(biāo)記一下圖片邊緣
if startY != 0:
cv2.line(img, (0, startY), (width - 1, startY), (0, 255, 0), 2)
if overY != height - 1:
cv2.line(img, (0, overY), (width - 1, overY), (0, 255, 0), 2)
if startY == 0 and overY == height - 1:
cv2.imwrite(targetDir + 'unchanged/' + srcFile + "_dealed.jpg", img)
else:
cv2.imwrite(targetDir + 'changed/' + srcFile, img)
reader = easyocr.Reader(['ch_sim', 'en'], gpu=False)
preDropForImage('E:/black/sample_images_black/', "1.jpg", 'E:/black/success_dealed/', reader)
到了這里,關(guān)于用Python實現(xiàn)給圖片去黑邊的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!