332 lines
17 KiB
Python
332 lines
17 KiB
Python
import re
|
|
|
|
import cv2
|
|
import numpy as np
|
|
from tkinter import filedialog, Tk
|
|
import os
|
|
import copy
|
|
|
|
class TestMeasurement:
|
|
def __init__(self, input_image_path):
|
|
# 載入測試影像
|
|
self.InputImage = cv2.imread(input_image_path, cv2.IMREAD_GRAYSCALE)
|
|
if self.InputImage is None:
|
|
raise ValueError(f"無法載入影像:{input_image_path}")
|
|
|
|
def FindMaxContours(self, contours):
|
|
# 簡單的找最大輪廓
|
|
if not contours:
|
|
return -1
|
|
return max(range(len(contours)), key=lambda i: cv2.contourArea(contours[i]))
|
|
|
|
def FindMaxContours_For_torxtamperproof(self, contours):
|
|
# 假設的最大輪廓邏輯
|
|
return self.FindMaxContours(contours)
|
|
|
|
def FindMaxContours_For_Other(self, contours):
|
|
# 假設的其他邏輯
|
|
return self.FindMaxContours(contours)
|
|
|
|
def draw_min_rect(self, image, contours, max_idx):
|
|
# 畫出最小外接矩形並計算長寬比
|
|
rect = cv2.minAreaRect(contours[max_idx])
|
|
box = cv2.boxPoints(rect)
|
|
box = np.intp(box)
|
|
cv2.drawContours(image, [box], 0, (0, 255, 0), 2)
|
|
w, h = rect[1]
|
|
if h == 0:
|
|
return 0
|
|
return round(max(w, h) / min(w, h), 2)
|
|
|
|
def GetMask(self, image):
|
|
ret, maskimage = cv2.threshold(image, 90, 255, cv2.THRESH_BINARY_INV)
|
|
contours, _ = cv2.findContours(maskimage, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
|
|
max_idx = self.FindMaxContours(contours)
|
|
if max_idx > -1:
|
|
(x, y, w, h) = cv2.boundingRect(contours[max_idx])
|
|
mask = np.zeros(maskimage.shape, dtype='uint8')
|
|
cv2.rectangle(mask, (x, y), (x + w, y + h), (255, 255, 255), -1)
|
|
return mask
|
|
else:
|
|
print('錯誤:無法生成 Mask')
|
|
return []
|
|
|
|
def measure(self, classnumber):
|
|
# 測試 measure 函式,僅簡化處理
|
|
if len(self.InputImage.shape) == 3:
|
|
inputimage = cv2.cvtColor(self.InputImage, cv2.COLOR_BGR2GRAY)
|
|
else:
|
|
inputimage = self.InputImage.copy()
|
|
|
|
maskimage = self.GetMask(inputimage)
|
|
if maskimage is []:
|
|
return self.InputImage, 0, 0, 0
|
|
|
|
# 簡化測試模式,例如使用 "square" 測試案例
|
|
if len(self.InputImage.shape) == 3:
|
|
inputimage = cv2.cvtColor(self.InputImage, cv2.COLOR_BGR2GRAY) # 圖像存儲使用8-8-8 24位RGB格式
|
|
else:
|
|
inputimage = copy.deepcopy(self.InputImage)
|
|
self.InputImage = cv2.cvtColor(self.InputImage, cv2.COLOR_GRAY2BGR)
|
|
# GaussianBlur = cv2.GaussianBlur(inputimage, (31, 31), 0)
|
|
|
|
maskimage = self.GetMask(inputimage)
|
|
if maskimage is []:
|
|
return self.InputImage, 0, 0, 0
|
|
|
|
if classnumber == 'spanner' or classnumber == '03_Spanner':
|
|
_, Threshold_Image = cv2.threshold(inputimage, 60, 255, cv2.THRESH_BINARY)
|
|
count_zero_pixels = (Threshold_Image == 255).sum()
|
|
# print(count_zero_pixels)
|
|
return Threshold_Image, count_zero_pixels, 0, 0
|
|
elif classnumber == 'square':
|
|
ret, Threshold_Image = cv2.threshold(inputimage, 130, 255, cv2.THRESH_BINARY)
|
|
cv2.bitwise_and(Threshold_Image, maskimage, Threshold_Image)
|
|
elif classnumber == 'hextamperproof':
|
|
ret, Threshold_Image = cv2.threshold(inputimage, 120, 255, cv2.THRESH_BINARY)
|
|
cv2.imwrite(f'./Measure/ht_1_Threshold_Image.png', Threshold_Image)
|
|
cv2.bitwise_and(Threshold_Image, maskimage, Threshold_Image)
|
|
cv2.imwrite(f'./Measure/ht_2_maskimage.png', maskimage)
|
|
cv2.imwrite(f'./Measure/ht_3_Threshold_Image_And.png', Threshold_Image)
|
|
elif classnumber == 'triwing':
|
|
ret, Threshold_Image = cv2.threshold(inputimage, 100, 255, cv2.THRESH_BINARY) # 120
|
|
cv2.bitwise_and(Threshold_Image, maskimage, Threshold_Image)
|
|
elif classnumber == 'torxtamperproof':
|
|
ret, Threshold_Image = cv2.threshold(inputimage, 120, 255, cv2.THRESH_BINARY) # 120
|
|
cv2.imwrite(f'./Measure/tt_1_Threshold_Image.png', Threshold_Image)
|
|
cv2.bitwise_and(Threshold_Image, maskimage, Threshold_Image)
|
|
cv2.imwrite(f'./Measure/tt_2_maskimage.png', maskimage)
|
|
cv2.imwrite(f'./Measure/tt_3_Threshold_Image_And.png', Threshold_Image)
|
|
elif classnumber == 'slotted':
|
|
ret, Threshold_Image = cv2.threshold(inputimage, 120, 255, cv2.THRESH_BINARY) # 150
|
|
cv2.bitwise_and(Threshold_Image, maskimage, Threshold_Image)
|
|
else:
|
|
ret, Threshold_Image = cv2.threshold(inputimage, 136, 255, cv2.THRESH_BINARY) # 145
|
|
cv2.bitwise_and(Threshold_Image, maskimage, Threshold_Image)
|
|
|
|
contours, _ = cv2.findContours(Threshold_Image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
|
|
if classnumber == 'torxtamperproof':
|
|
max_idx = self.FindMaxContours_For_torxtamperproof(contours)
|
|
else:
|
|
max_idx = self.FindMaxContours_For_Other(contours)
|
|
if max_idx > -1:
|
|
torxtamperproof_area = 0
|
|
torxtamperproof_approx_arcLength = 0
|
|
torxtamperproof_approx_area = 0
|
|
square_area = 0
|
|
vertices = 0
|
|
(x, y), radius = cv2.minEnclosingCircle(contours[max_idx])
|
|
center = (int(x), int(y))
|
|
|
|
radius_int = int(radius)
|
|
|
|
if classnumber in ['hextamperproof']:
|
|
count = 1
|
|
mask = np.zeros(Threshold_Image.shape, dtype='uint8')
|
|
cv2.circle(mask, center, radius_int, (255, 255, 255), -1)
|
|
|
|
new_image = cv2.bitwise_and(inputimage, mask)
|
|
cv2.imwrite(f'./Measure/ht_4_new_image.png', new_image)
|
|
mean_value = cv2.mean(new_image[center[1] - radius_int:center[1] + radius_int,
|
|
center[0] - radius_int:center[0] + radius_int]) # 計算套頭形狀部分的亮度平均值
|
|
threshold_value = int(mean_value[0] * 1.2) # 計算inputimage的閾值
|
|
|
|
ret, Threshold_Image_mean_value = cv2.threshold(inputimage, threshold_value, 255,
|
|
cv2.THRESH_BINARY) # 二值化
|
|
self.Histogram = cv2.bitwise_not(Threshold_Image_mean_value)
|
|
contours_hextamperproof, hierarchy = cv2.findContours(cv2.bitwise_not(Threshold_Image_mean_value),
|
|
cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)
|
|
|
|
i = 0
|
|
hierach_id = 0
|
|
inner_class = []
|
|
while not (hierarchy[0][i][2] == -1) and hierarchy[0][i][1] == -1 and i < len(
|
|
contours_hextamperproof) and not (hierarchy[0][i][3] == -1):
|
|
hierach_id = i
|
|
i += 1
|
|
# print(hierach_id, hierarchy[0][hierach_id])
|
|
while not (hierarchy[0][hierach_id][0] == -1):
|
|
inner_class.append(contours_hextamperproof[hierach_id])
|
|
hierach_id = hierarchy[0][hierach_id][0]
|
|
# print(hierach_id, hierarchy[0][hierach_id])
|
|
|
|
Max_contour_index_hextamperproof = self.FindMaxContours_For_hextamperproof(inner_class) # 找最大輪廓
|
|
|
|
(x, y), radius = cv2.minEnclosingCircle(
|
|
inner_class[Max_contour_index_hextamperproof]) # 找最大輪廓最小外接園的直徑和圓心
|
|
center = (int(x), int(y))
|
|
radius_int = int(radius)
|
|
cv2.circle(self.InputImage, center, radius_int, (0, 242, 255), 1)
|
|
|
|
elif classnumber in ['torxtamperproof']:
|
|
mask = np.zeros(Threshold_Image.shape, dtype='uint8')
|
|
# self.Histogram = cv2.bitwise_and(inputimage, maskimage, inputimage)
|
|
cv2.circle(mask, center, radius_int, (255, 255, 255), -1)
|
|
new_image = cv2.bitwise_and(inputimage, mask)
|
|
cv2.imwrite(f'./Measure/tt_4_new_image.png', new_image)
|
|
mean_value = cv2.mean(
|
|
new_image[center[1] - radius_int:center[1] + radius_int,
|
|
center[0] - radius_int:center[0] + radius_int])
|
|
threshold_value = int(mean_value[0] * 1.36) # 1.36
|
|
ret, Threshold_Image_mean_value = cv2.threshold(inputimage, threshold_value, 255, cv2.THRESH_BINARY)
|
|
contours_torxtamperproof, _ = cv2.findContours(cv2.bitwise_and(Threshold_Image_mean_value, maskimage),
|
|
cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
|
|
|
|
testContoursImage = self.InputImage.copy()
|
|
cv2.drawContours(testContoursImage, contours_torxtamperproof, -1, (255, 0, 0), 1)
|
|
cv2.imwrite(f'./Measure/tt_5_testContoursImage.png', testContoursImage)
|
|
|
|
Max_contour_index_torxtamperproof = self.FindMaxContours_For_torxtamperproof(contours_torxtamperproof)
|
|
cv2.drawContours(self.InputImage, contours_torxtamperproof, Max_contour_index_torxtamperproof,
|
|
(99, 242, 50), 1)
|
|
|
|
testContoursImage2 = self.InputImage.copy()
|
|
cv2.drawContours(testContoursImage2, contours_torxtamperproof, Max_contour_index_torxtamperproof,
|
|
(255, 0, 0),
|
|
1)
|
|
cv2.imwrite(f'./Measure/tt_6_testContoursImage.png', testContoursImage2)
|
|
|
|
(x, y), radius = cv2.minEnclosingCircle(contours_torxtamperproof[Max_contour_index_torxtamperproof])
|
|
|
|
epsilon = 0.045 * cv2.arcLength(contours_torxtamperproof[Max_contour_index_torxtamperproof], True)
|
|
approx = cv2.approxPolyDP(contours_torxtamperproof[Max_contour_index_torxtamperproof], epsilon, True)
|
|
torxtamperproof_approx_area = cv2.contourArea(approx)
|
|
torxtamperproof_approx_arcLength = cv2.arcLength(approx, True)
|
|
if torxtamperproof_approx_arcLength > 80 and torxtamperproof_approx_area < 450.5:
|
|
torxtamperproof_approx_arcLength = 80.292929
|
|
|
|
elif classnumber in ['square', 'slotted', 'triangle']:
|
|
mask = np.zeros(Threshold_Image.shape, dtype='uint8')
|
|
# self.Histogram = cv2.bitwise_and(inputimage, maskimage, inputimage)
|
|
cv2.circle(mask, center, radius_int, (255, 255, 255), -1)
|
|
new_image = cv2.bitwise_and(inputimage, mask)
|
|
# self.Histogram = new_image[center[1]-radius_int:center[1]+radius_int, center[0]-radius_int:center[0]+radius_int]
|
|
self.Histogram = mask
|
|
mean_value = cv2.mean(new_image[center[1] - radius_int:center[1] + radius_int,
|
|
center[0] - radius_int:center[0] + radius_int])
|
|
threshold_value = int(mean_value[0] * 1.1)
|
|
# print(f'mean_value = {mean_value[0]} mean_value\' = {threshold_value}')
|
|
ret, Threshold_Image_mean_value = cv2.threshold(inputimage, threshold_value, 255, cv2.THRESH_BINARY)
|
|
# self.Histogram = Threshold_Image_mean_value
|
|
contours_square, _ = cv2.findContours(cv2.bitwise_and(Threshold_Image_mean_value, maskimage),
|
|
cv2.RETR_EXTERNAL,
|
|
cv2.CHAIN_APPROX_NONE)
|
|
Max_contour_index_square = self.FindMaxContours_For_torxtamperproof(contours_square)
|
|
square_area = cv2.contourArea(contours_square[Max_contour_index_square])
|
|
cv2.drawContours(self.InputImage, contours_square, Max_contour_index_square, (99, 242, 50), 1)
|
|
# 近似轮廓
|
|
epsilon = 0.05 * cv2.arcLength(contours_square[Max_contour_index_square], True)
|
|
approx = cv2.approxPolyDP(contours_square[Max_contour_index_square], epsilon, True)
|
|
cv2.drawContours(self.InputImage, [approx], -1, (0, 0, 255), 1)
|
|
vertices = len(approx)
|
|
# print(f'vertices up = {classnumber} {vertices}')
|
|
else:
|
|
self.Histogram = maskimage
|
|
cv2.circle(self.InputImage, center, radius_int, (255, 0, 0), 1)
|
|
|
|
# 获取轮廓的顶点数
|
|
# print((radius*2)*22*0.001)
|
|
# print(min(rect[1][:])*22*0.001)
|
|
# print(max(rect[1][:])*22*0.001)
|
|
# GaussianBlur = cv2.cvtColor(GaussianBlur, cv2.COLOR_GRAY2BGR) # 圖像存儲使用8-8-8 24位RGB格式
|
|
radius = round(radius * 2 * 22 * 0.001, 3) # 54.55
|
|
# cv2.imwrite(f'./Measure/{classnumber}_{radius}.png', Threshold_Image)
|
|
if classnumber in ['torx', 'pentalope', 'torxtamperproof']:
|
|
# 近似轮廓
|
|
if classnumber in ['pentalope'] and radius < 0.377:
|
|
epsilon = 0.045 * cv2.arcLength(contours[max_idx], True)
|
|
else:
|
|
epsilon = 0.04 * cv2.arcLength(contours[max_idx], True)
|
|
approx = cv2.approxPolyDP(contours[max_idx], epsilon, True)
|
|
cv2.drawContours(self.InputImage, [approx], -1, (0, 0, 255), 1)
|
|
vertices = len(approx)
|
|
elif classnumber in ['triangle', 'phillips', 'slotted']:
|
|
# 近似轮廓
|
|
epsilon = 0.05 * cv2.arcLength(contours[max_idx], True)
|
|
approx = cv2.approxPolyDP(contours[max_idx], epsilon, True)
|
|
cv2.drawContours(self.InputImage, [approx], -1, (0, 0, 255), 1)
|
|
vertices = len(approx)
|
|
# print(f'vertices under = {classnumber} {vertices}')
|
|
elif classnumber in ['triwing']:
|
|
epsilon = 0.07 * cv2.arcLength(contours[max_idx], True)
|
|
approx = cv2.approxPolyDP(contours[max_idx], epsilon, True)
|
|
cv2.drawContours(self.InputImage, [approx], -1, (0, 0, 255), 1)
|
|
vertices = len(approx)
|
|
|
|
aspect_ratios = self.draw_min_rect(self.InputImage, contours, max_idx)
|
|
if classnumber in ['torxtamperproof']:
|
|
return self.InputImage, radius, torxtamperproof_approx_arcLength, torxtamperproof_approx_area
|
|
# elif classnumber in ['square', 'slotted', 'triangle']:
|
|
# return self.InputImage, radius, square_area, vertices
|
|
else:
|
|
# if classnumber in ['square', 'slotted', 'triangle']:
|
|
# print(f'aspect_ratios = {classnumber} {aspect_ratios}')
|
|
return self.InputImage, radius, aspect_ratios, vertices
|
|
else:
|
|
return self.InputImage, 0, 0, 0
|
|
|
|
def process_selected_images(self, file_paths):
|
|
for image_path in file_paths:
|
|
print(f"正在處理影像:{image_path}")
|
|
self.InputImage = cv2.imread(image_path)
|
|
|
|
if self.InputImage is None:
|
|
print(f"無法讀取圖片:{image_path}")
|
|
continue
|
|
|
|
# 更新正則表達式,支援更多種類別檔名格式
|
|
class_patterns = [
|
|
r'_([a-zA-Z]+)_\d+' # 適配格式 "20240826_173329_torxtamperproof_1_20_0_0_0"
|
|
r'|-(\d+)_([a-zA-Z]+)_', # 適配格式 "13_Square_1-130000000-02_Opposite_1-92"
|
|
r'([a-zA-Z]+)_\d+\.', # 格式: torx_001.png
|
|
r'(\w+)\.\w+$', # 直接從檔名提取,如 square.png
|
|
r'_(\w+)\.\w+$' # 格式: image_square.png
|
|
]
|
|
|
|
classnumber = None
|
|
for pattern in class_patterns:
|
|
match = re.search(pattern, image_path, re.IGNORECASE)
|
|
if match:
|
|
# 取第一個非空的群組
|
|
classnumber = next(filter(None, match.groups()))
|
|
break
|
|
|
|
if classnumber:
|
|
# 統一轉換為小寫,以增加匹配的一致性
|
|
classnumber = classnumber.lower()
|
|
print(f"檔案: {os.path.basename(image_path)} => 解析類別: {classnumber}")
|
|
return classnumber
|
|
else:
|
|
print(f"檔案名稱無法解析類別: {os.path.basename(image_path)}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# 設定輸出資料夾
|
|
output_directory = r"C:\Users\user\Desktop\0723ProgramBackup\test\detection_results"
|
|
os.makedirs(output_directory, exist_ok=True)
|
|
|
|
# 使用 Tkinter 開啟檔案選擇對話框
|
|
Tk().withdraw() # 關閉主視窗
|
|
file_paths = filedialog.askopenfilenames(title="選擇影像檔案",
|
|
filetypes=[("Image files", "*.png;*.jpg;*.jpeg;*.bmp")])
|
|
|
|
if not file_paths:
|
|
print("未選擇影像檔案")
|
|
else:
|
|
for file_path in file_paths:
|
|
# 初始化測試類別
|
|
tester = TestMeasurement(file_path)
|
|
|
|
# 從檔名取得類別
|
|
classnumber = tester.process_selected_images([file_path])
|
|
|
|
try:
|
|
# 測試不同的 classnumber
|
|
result_image, result_radius, result_metric, result_vertices = tester.measure(classnumber)
|
|
output_file = os.path.basename(file_path).replace(".", f"_{classnumber}.")
|
|
output_path = os.path.join(output_directory, output_file)
|
|
cv2.imwrite(output_path, result_image)
|
|
print(
|
|
f"影像: {file_path} | 測試類型: {classnumber}, 結果: 半徑={result_radius}, 度量={result_metric}, 頂點數={result_vertices}, 輸出影像: {output_path}")
|
|
except Exception as e:
|
|
print(f"處理影像時發生錯誤: {e}") |