screwdriver/test/TEST_Measure.py

332 lines
17 KiB
Python
Raw Permalink Normal View History

2025-02-06 16:10:58 +08:00
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}")