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}")