當預處理完成后,其實我們已經拿到一個比較正經的圖像了如果二維碼本身沒有什么太大的缺陷,此時應當是可以直接掃描出來的
但是由於各種各樣的原因,zbar無法直接識別圖像,還需要我們對圖像進行解析
最簡單的方法就是識別圖片中的每一行、每一列,記錄對應的行列分割線
算法簡述如下,以行搜索為例:
以上一行的下沿 + 一定的偏移量 作為起始行,從起始行的每一列開始尋找連續性的白點或者黑點,當出現黑白點切換時,認為已經抵達了當前行的下沿,將當前的行index添加到一個數組當中
循環完每一列之后,取當前所有下沿數據數組的中位數和平均數,取其較大值,即我們認為下沿應當是偏激進的
# 尋找rows
def found_point_rows(mat):
temp1 = []
i = 0
normal_gap = 5
last_gap = 0
while i < mat.shape[0]:
if i + 11 < mat.shape[0]:
if i < mat.shape[0] - 20:
gap = normal_gap
else:
gap = last_gap
point_row = Point_rows(mat[int(i) + gap:min(int(i) + 18, 406), :], min(12, 406 - int(i)))
mat_temp = point_row + gap
else:
break
if mat_temp is None:
i = i + 11
else:
i = i + mat_temp
temp1.append(i)
return temp1
def Point_rows(mat, border):
lower_limit_j = []
for i in range(0, mat.shape[1]):
for j2 in range(0, min(border, mat.shape[0]) - 1):
if mat[0:j2 + 1, i].mean() == 255 and mat[j2 + 1, i] == 0 and j2 > 2: # 如果這一列,超過5個全是白色,下一個是黑色 則認為也到了邊界
lower_limit_j.append(j2)
break
elif mat[j2, i] == 0 and mat[j2 + 1, i] == 255 and j2 > 2: # 如果這個是黑色而且下一個是白色,並且黑色數量大於五個,則認為到達了下限邊界
lower_limit_j.append(j2)
break
if len(lower_limit_j) == 0:
return 6
return max(int(np.median(np.array(lower_limit_j))), int((np.array(lower_limit_j).mean())))
如果當前圖像是一個殘損的圖像,即左側的定位點不完整,是無法確認生成了36列定位線的,需要根據左側的坐標點重新計算定位線
坐標點的規則是,7~29的單數格子都為白,其他格子都為黑,當我們掃描某一列的分割點時,判定是否每個點都符合這個規律,確定正確的點給予1的積分,確定錯誤的點給予0的積分,不確定的給予0.5的積分,最終合並積分數量大於某個閾值,可以判定當前列就是定位線列:
def scan_anchor_point(image, point_rows, point_columns):
# 按列掃描,統計每個格子的數據,統計37個格子的匹配度
# i為行,j為列
match_count_min = 30
for j in range(0, len(point_columns)):
match_count = 0
for i in range(0, len(point_rows)):
start_x = 0 if j == 0 else point_columns[j - 1] + 1
end_x = point_columns[j]
start_y = 0 if i == 0 else point_rows[i - 1] + 1
end_y = point_rows[i]
# 計算當前格子的匹配度
# 白色格子坐標為 7 ~ 29之間的單數
mean_color = image[start_y:end_y, start_x:end_x].mean();
if mean_color < 50:
color = "black"
elif mean_color > 150:
color = "white"
else:
color = "middle"
if color == "middle":
match_count += 0.8
elif color == "white":
if i >= 7 and i <= 29 and i / 2 != int(i / 2):
match_count += 1
else:
match_count += 0
else:
if i >= 7 and i <= 29 and i / 2 != int(i / 2):
match_count += 0
else:
match_count += 1
if match_count > match_count_min:
return start_x
return None
最終根據列數、行數、以及圖片的殘損情況來調整圖片的row和column的分割數組
# 找到圖片的XY分割線
def find_split_lines(self, image, defect_flag):
point_rows = split.found_point_rows(image)
point_columns = split.found_point_columns(image)
# 如果找出來的行列分割線多一個,刪除掉最后一條線
if len(point_columns) == 37:
point_columns.pop(36)
if len(point_rows) == 37:
point_rows.pop(36)
# 生成划線圖
draw_line_image = np.copy(image)
for rowIndex in range(0, len(point_rows)):
cv2.line(draw_line_image, (0, point_rows[rowIndex]), (406, point_rows[rowIndex]), (0, 0, 0), 1)
for colIndex in range(0, len(point_columns)):
cv2.line(draw_line_image, (point_columns[colIndex], 0), (point_columns[colIndex], 406), (0, 0, 0), 1)
if self.trace_image:
cv2.imwrite(self.trace_path + "501_draw_line_" + self.image_name, draw_line_image)
# 判斷當前圖像是否缺損,缺損的話尋找坐標點重建圖像
if defect_flag:
start_x = loc.scan_anchor_point(image, point_rows, point_columns)
# 如果當前圖像缺損,且未找到坐標點,返回空
if start_x == None:
print("未找到定位坐標點")
return None
else:
# 找到坐標點,對圖片進行縮放處理
# 切掉圖片左側的校驗區域,后續自動補齊
fixed_image = np.zeros((407, 341), np.uint8)
source_pst = np.float32([[start_x, 0], [start_x, 406], [406, 0], [406, 406]])
target_pst = np.float32([[0, 0], [0, 406], [341, 0], [341, 406]])
fixed_m = cv2.getPerspectiveTransform(source_pst, target_pst)
fixed_image = cv2.warpPerspective(image, fixed_m, (341, 407))
# 將圖片恢復二值化
ret2, th2 = cv2.threshold(fixed_image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
ret3, fixed_image = cv2.threshold(th2, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
if self.trace_image:
cv2.imwrite(self.trace_path + "502_threshed_fixed_" + self.image_name, fixed_image)
# 重新計算一次關鍵信息坐標點
point_rows = split.found_point_rows(fixed_image)
point_columns = split.found_point_columns(fixed_image)
# 刪除多余的分割線
if defect_flag and len(point_columns) == 31:
point_columns.pop(30)
if not defect_flag and len(point_columns) == 37:
point_columns.pop(36)
if len(point_rows) == 37:
point_rows.pop(36)
defect_draw_line_image = np.copy(fixed_image)
for rowIndex in range(0, len(point_rows)):
cv2.line(defect_draw_line_image, (0, point_rows[rowIndex]), (406, point_rows[rowIndex]),
(0, 0, 0), 1)
for colIndex in range(0, len(point_columns)):
cv2.line(defect_draw_line_image, (point_columns[colIndex], 0), (point_columns[colIndex], 406),
(0, 0, 0),
1)
if self.trace_image:
cv2.imwrite(self.trace_path + "503_defect_draw_line_" + self.image_name, defect_draw_line_image)
else:
fixed_image = image
# 根據XY定位點修復row/column
(point_rows, point_columns) = split.fix_rows_columns(fixed_image, point_rows, point_columns,
defect_flag)
# 如果非殘缺圖片,必須有36個分割線
# 如果為殘缺圖片,必須有30個分割線
if (not defect_flag and len(point_columns) != 36) or (defect_flag and len(point_columns) != 30):
print("列數量不匹配")
return None
if len(point_rows) != 36:
print("行數量不匹配")
return None
# 增補中止線
point_rows.append(407)
if not defect_flag:
point_columns.append(407)
else:
point_columns.append(341)
return fixed_image, point_rows, point_columns
分割線的划線圖如下所示:
目前這種算法在二維碼圖片整齊的時候比較好用,如果紙張出現了折疊、揉搓導致的物理偏移目前是沒有很好的修復手段。因為物理偏移是無法使用投影變換恢復回來的,只能在尋找每一行每一列的時候,通過線段的延展方向來構造彎曲的分割線來做識別,思考了一下比較復雜,而且目前也沒有遇到這種問題,暫時就沒有改進