添加状态分类和液面分割
74
angle_base_obb/anger_caculate.py
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
from ultralytics import YOLO
|
||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
import os
|
||||||
|
|
||||||
|
# 1. 加载模型
|
||||||
|
model = YOLO(r'/home/hx/yolo/ultralytics_yolo11-main/runs/train/exp_obb/weights/best.pt')
|
||||||
|
|
||||||
|
# 2. 读取图像
|
||||||
|
img_path = r"/output_masks/3.png"
|
||||||
|
img = cv2.imread(img_path)
|
||||||
|
|
||||||
|
# ✅ 检查图像是否加载成功
|
||||||
|
if img is None:
|
||||||
|
print(f"❌ 错误:无法读取图像!请检查路径:{img_path}")
|
||||||
|
print("💡 提示:可能是文件不存在、路径错误或图像损坏")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
# 3. 预测(使用 OBB 模式!)
|
||||||
|
results = model(
|
||||||
|
img,
|
||||||
|
save=False,
|
||||||
|
imgsz=1280, # 必须和训练时一致
|
||||||
|
conf=0.25,
|
||||||
|
mode='obb' # 启用旋转框模式
|
||||||
|
)
|
||||||
|
|
||||||
|
# 4. 获取结果并绘制
|
||||||
|
result = results[0]
|
||||||
|
annotated_img = result.plot() # 自动绘制旋转框
|
||||||
|
|
||||||
|
# ✅ 5. 保存推理结果图像
|
||||||
|
output_dir = "./inference_results"
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
filename = os.path.basename(img_path)
|
||||||
|
save_path = os.path.join(output_dir, "detected_" + filename)
|
||||||
|
cv2.imwrite(save_path, annotated_img)
|
||||||
|
print(f"✅ 推理结果已保存至: {save_path}")
|
||||||
|
|
||||||
|
# ✅ 6. 提取旋转框信息并计算夹角(修正版)
|
||||||
|
boxes = result.obb
|
||||||
|
if boxes is None or len(boxes) == 0:
|
||||||
|
print("❌ No objects detected.")
|
||||||
|
else:
|
||||||
|
print(f"✅ Detected {len(boxes)} object(s):")
|
||||||
|
angles = [] # 存储每个框的旋转角度(角度制)
|
||||||
|
|
||||||
|
for i, box in enumerate(boxes):
|
||||||
|
cls = int(box.cls.cpu().numpy()[0])
|
||||||
|
conf = box.conf.cpu().numpy()[0]
|
||||||
|
|
||||||
|
# ✅ 正确方式:使用 .xywhr 获取旋转角度(新版本 API)
|
||||||
|
xywhr = box.xywhr.cpu().numpy()[0] # [x_center, y_center, width, height, rotation]
|
||||||
|
angle_rad = xywhr[4] # 第5个值是旋转角度(弧度)
|
||||||
|
angle_deg = np.degrees(angle_rad) # 转为角度
|
||||||
|
|
||||||
|
angles.append(angle_deg)
|
||||||
|
print(f" Box {i+1}: Class: {cls}, Confidence: {conf:.3f}, Angle: {angle_deg:.2f}°")
|
||||||
|
|
||||||
|
# ✅ 计算任意两个框之间的最小夹角差
|
||||||
|
if len(angles) >= 2:
|
||||||
|
print("\n🔍 计算旋转框之间的夹角差:")
|
||||||
|
for i in range(len(angles)):
|
||||||
|
for j in range(i + 1, len(angles)):
|
||||||
|
diff = abs(angles[i] - angles[j])
|
||||||
|
min_angle_diff = min(diff, 180 - diff) # 取最小夹角(0~180°内)
|
||||||
|
print(f" Box {i+1} 与 Box {j+1} 的最小夹角差: {min_angle_diff:.2f}°")
|
||||||
|
else:
|
||||||
|
print("⚠️ 检测到少于两个目标,无法计算夹角。")
|
||||||
|
|
||||||
|
# ✅ 7. 显示图像
|
||||||
|
cv2.imshow("YOLO OBB Prediction", annotated_img)
|
||||||
|
cv2.waitKey(0)
|
||||||
|
cv2.destroyAllWindows()
|
||||||
BIN
angle_base_obb/inference_results/detected_1.jpg
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
angle_base_obb/inference_results/detected_2.jpg
Normal file
|
After Width: | Height: | Size: 1012 KiB |
BIN
angle_base_obb/inference_results/detected_3.png
Normal file
|
After Width: | Height: | Size: 4.8 MiB |
|
Before Width: | Height: | Size: 851 KiB |
|
Before Width: | Height: | Size: 851 KiB |
@ -2,10 +2,10 @@ from ultralytics import YOLO
|
|||||||
import cv2
|
import cv2
|
||||||
|
|
||||||
# 1. 加载模型
|
# 1. 加载模型
|
||||||
model = YOLO(r'/home/hx/yolo/ultralytics_yolo11-main/runs/train/exp_obb2/weights/best.pt')
|
model = YOLO(r'/home/hx/yolo/ultralytics_yolo11-main/runs/train/exp_obb/weights/best.pt')
|
||||||
|
|
||||||
# 2. 读取图像
|
# 2. 读取图像
|
||||||
img_path = r"/home/hx/桌面/image/images/test/1.jpg"
|
img_path = r"/home/hx/yolo/test_image/2.png"
|
||||||
img = cv2.imread(img_path)
|
img = cv2.imread(img_path)
|
||||||
|
|
||||||
# ✅ 检查图像是否加载成功
|
# ✅ 检查图像是否加载成功
|
||||||
|
|||||||
140
angle_base_point/angle3.py
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
from ultralytics import YOLO
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
|
||||||
|
# ====================== 用户配置 ======================
|
||||||
|
MODEL_PATH = "/home/hx/yolo/ultralytics_yolo11-main/runs/train/point/exp_pose2/weights/best.pt"
|
||||||
|
IMAGE_PATH = "/home/hx/yolo/output_masks/3.png"
|
||||||
|
SAVE_RESULT = True
|
||||||
|
SAVE_PATH = "/home/hx/yolo/test_image/result_with_angle2.jpg"
|
||||||
|
SHOW_IMAGE = True
|
||||||
|
|
||||||
|
# 关键点颜色:红、绿、蓝(对应 kpt 1, 2, 3)
|
||||||
|
KPT_COLORS = [
|
||||||
|
(0, 0, 255), # 红色 - kpt 1
|
||||||
|
(0, 255, 0), # 绿色 - kpt 2
|
||||||
|
(255, 0, 0), # 蓝色 - kpt 3
|
||||||
|
]
|
||||||
|
|
||||||
|
# ====================== 计算两向量夹角(0~180°)=======================
|
||||||
|
def calculate_angle_between_vectors(v1, v2):
|
||||||
|
"""
|
||||||
|
计算两个向量之间的夹角(单位:度),范围 0 ~ 180°
|
||||||
|
"""
|
||||||
|
v1 = np.array(v1)
|
||||||
|
v2 = np.array(v2)
|
||||||
|
|
||||||
|
if np.linalg.norm(v1) == 0 or np.linalg.norm(v2) == 0:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
v1_u = v1 / np.linalg.norm(v1)
|
||||||
|
v2_u = v2 / np.linalg.norm(v2)
|
||||||
|
|
||||||
|
dot = np.clip(np.dot(v1_u, v2_u), -1.0, 1.0)
|
||||||
|
angle_rad = np.arccos(dot)
|
||||||
|
angle_deg = np.degrees(angle_rad)
|
||||||
|
return angle_deg
|
||||||
|
|
||||||
|
# ====================== 主程序 =======================
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("正在加载模型...")
|
||||||
|
try:
|
||||||
|
model = YOLO(MODEL_PATH)
|
||||||
|
print(f"✅ 模型加载完成: {MODEL_PATH}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 模型加载失败: {e}")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
# 读取图像
|
||||||
|
img = cv2.imread(IMAGE_PATH)
|
||||||
|
if img is None:
|
||||||
|
raise FileNotFoundError(f"❌ 无法加载图像: {IMAGE_PATH}")
|
||||||
|
|
||||||
|
h, w = img.shape[:2]
|
||||||
|
print(f"✅ 图像加载成功: {IMAGE_PATH} ({w}x{h})")
|
||||||
|
|
||||||
|
# --- 推理 ---
|
||||||
|
results = model(img, conf=0.25, imgsz=1280)
|
||||||
|
result = results[0]
|
||||||
|
|
||||||
|
# 存储每个实例的关键点和置信度
|
||||||
|
instances = []
|
||||||
|
|
||||||
|
if result.keypoints is not None and result.boxes is not None:
|
||||||
|
kpts_list = result.keypoints.xy.cpu().numpy() # (N, K, 2)
|
||||||
|
confs = result.boxes.conf.cpu().numpy() # (N,)
|
||||||
|
bboxes = result.boxes.xyxy.cpu().numpy() # (N, 4)
|
||||||
|
|
||||||
|
# 组合:置信度 + 关键点 + 边框
|
||||||
|
for i in range(len(kpts_list)):
|
||||||
|
if len(kpts_list[i]) >= 2: # 至少有两个关键点
|
||||||
|
kpt1 = (int(kpts_list[i][0][0]), int(kpts_list[i][0][1]))
|
||||||
|
kpt2 = (int(kpts_list[i][1][0]), int(kpts_list[i][1][1]))
|
||||||
|
instances.append({
|
||||||
|
'conf': confs[i],
|
||||||
|
'kpt1': kpt1,
|
||||||
|
'kpt2': kpt2,
|
||||||
|
'bbox': bboxes[i]
|
||||||
|
})
|
||||||
|
|
||||||
|
# 按置信度降序排序
|
||||||
|
instances.sort(key=lambda x: x['conf'], reverse=True)
|
||||||
|
|
||||||
|
# 只保留置信度最高的两个实例
|
||||||
|
top_two = instances[:2]
|
||||||
|
|
||||||
|
if len(top_two) == 2:
|
||||||
|
inst1, inst2 = top_two
|
||||||
|
|
||||||
|
# 绘制第一个实例的关键点和连线
|
||||||
|
cv2.circle(img, inst1['kpt1'], 10, KPT_COLORS[0], -1)
|
||||||
|
cv2.circle(img, inst1['kpt2'], 10, KPT_COLORS[1], -1)
|
||||||
|
cv2.line(img, inst1['kpt1'], inst1['kpt2'], (255, 255, 0), 3) # 黄色线
|
||||||
|
cv2.putText(img, "1", (inst1['kpt1'][0]+10, inst1['kpt1'][1]-10),
|
||||||
|
cv2.FONT_HERSHEY_SIMPLEX, 1.0, KPT_COLORS[0], 2)
|
||||||
|
cv2.putText(img, "2", (inst1['kpt2'][0]+10, inst1['kpt2'][1]-10),
|
||||||
|
cv2.FONT_HERSHEY_SIMPLEX, 1.0, KPT_COLORS[1], 2)
|
||||||
|
|
||||||
|
# 绘制第二个实例的关键点和连线
|
||||||
|
cv2.circle(img, inst2['kpt1'], 10, KPT_COLORS[0], -1)
|
||||||
|
cv2.circle(img, inst2['kpt2'], 10, KPT_COLORS[1], -1)
|
||||||
|
cv2.line(img, inst2['kpt1'], inst2['kpt2'], (0, 255, 255), 3) # 青色线
|
||||||
|
cv2.putText(img, "1", (inst2['kpt1'][0]+10, inst2['kpt1'][1]-10),
|
||||||
|
cv2.FONT_HERSHEY_SIMPLEX, 1.0, KPT_COLORS[0], 2)
|
||||||
|
cv2.putText(img, "2", (inst2['kpt2'][0]+10, inst2['kpt2'][1]-10),
|
||||||
|
cv2.FONT_HERSHEY_SIMPLEX, 1.0, KPT_COLORS[1], 2)
|
||||||
|
|
||||||
|
# 构造两个向量:kpt2 - kpt1
|
||||||
|
vec1 = np.array([inst1['kpt2'][0] - inst1['kpt1'][0],
|
||||||
|
inst1['kpt2'][1] - inst1['kpt1'][1]])
|
||||||
|
|
||||||
|
vec2 = np.array([inst2['kpt2'][0] - inst2['kpt1'][0],
|
||||||
|
inst2['kpt2'][1] - inst2['kpt1'][1]])
|
||||||
|
|
||||||
|
# 计算夹角
|
||||||
|
angle = calculate_angle_between_vectors(vec1, vec2)
|
||||||
|
print(f"✅ 置信度最高的两个实例,其关键点1-2连线夹角: {angle:.1f}°")
|
||||||
|
|
||||||
|
# 在图像上显示角度
|
||||||
|
cv2.putText(img, f"Angle: {angle:.1f}°", (50, 50),
|
||||||
|
cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 255, 255), 3, lineType=cv2.LINE_AA)
|
||||||
|
|
||||||
|
else:
|
||||||
|
print(f"⚠️ 检测到 {len(top_two)} 个有效实例(需要至少2个)")
|
||||||
|
else:
|
||||||
|
print("⚠️ 未检测到关键点或边界框")
|
||||||
|
|
||||||
|
# --- 保存结果 ---
|
||||||
|
os.makedirs(os.path.dirname(SAVE_PATH), exist_ok=True)
|
||||||
|
if SAVE_RESULT:
|
||||||
|
cv2.imwrite(SAVE_PATH, img)
|
||||||
|
print(f"✅ 结果图像已保存至: {SAVE_PATH}")
|
||||||
|
|
||||||
|
# --- 显示图像 ---
|
||||||
|
if SHOW_IMAGE:
|
||||||
|
cv2.imshow("Keypoint Angle Between Two Best Instances", img)
|
||||||
|
print("👉 按任意键关闭窗口...")
|
||||||
|
cv2.waitKey(0)
|
||||||
|
cv2.destroyAllWindows()
|
||||||
@ -46,7 +46,7 @@ def draw_keypoints_and_angle(image, keypoints, angle):
|
|||||||
|
|
||||||
# ====================== 用户配置 ======================
|
# ====================== 用户配置 ======================
|
||||||
MODEL_PATH = '/home/hx/yolo/ultralytics_yolo11-main/runs/train/exp_pose/weights/best.pt' # 替换为你的模型路径
|
MODEL_PATH = '/home/hx/yolo/ultralytics_yolo11-main/runs/train/exp_pose/weights/best.pt' # 替换为你的模型路径
|
||||||
IMAGE_PATH = '/home/hx/yolo/test_image/1.png' # 替换为你的测试图像路径
|
IMAGE_PATH = '/home/hx/yolo/output_masks/1.jpg' # 替换为你的测试图像路径
|
||||||
CONF_THRESHOLD = 0.25
|
CONF_THRESHOLD = 0.25
|
||||||
SAVE_RESULT = True
|
SAVE_RESULT = True
|
||||||
SAVE_PATH = '/home/hx/yolo/test_image/result_with_angle.jpg'
|
SAVE_PATH = '/home/hx/yolo/test_image/result_with_angle.jpg'
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import os
|
|||||||
|
|
||||||
# ====================== 用户配置 ======================
|
# ====================== 用户配置 ======================
|
||||||
MODEL_PATH = '/home/hx/yolo/ultralytics_yolo11-main/runs/train/exp_pose/weights/best.pt'
|
MODEL_PATH = '/home/hx/yolo/ultralytics_yolo11-main/runs/train/exp_pose/weights/best.pt'
|
||||||
IMAGE_PATH = '/home/hx/yolo/test_image/2.png'
|
IMAGE_PATH = '/output_masks/3.png'
|
||||||
OUTPUT_DIR = '/home/hx/yolo/output_images' # 保存结果的文件夹
|
OUTPUT_DIR = '/home/hx/yolo/output_images' # 保存结果的文件夹
|
||||||
|
|
||||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import numpy as np
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
# ------------------ 模型与路径配置 ------------------
|
# ------------------ 模型与路径配置 ------------------
|
||||||
MODEL_PATH = '../ultralytics_yolo11-main/runs/train/exp4/weights/best.pt'
|
MODEL_PATH = '../ultralytics_yolo11-main/runs/train/seg_j/exp/weights/best.pt'
|
||||||
OUTPUT_DIR = '../test_image'
|
OUTPUT_DIR = '../test_image'
|
||||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ def detect_jaw_angle(image_path, mode='show'):
|
|||||||
|
|
||||||
# 创建掩码并检测
|
# 创建掩码并检测
|
||||||
composite_mask = np.zeros((h, w), dtype=np.uint8)
|
composite_mask = np.zeros((h, w), dtype=np.uint8)
|
||||||
results = model(image_path, imgsz=1280, conf=0.5)
|
results = model(image_path, imgsz=640, conf=0.5)
|
||||||
|
|
||||||
jaws = []
|
jaws = []
|
||||||
for r in results:
|
for r in results:
|
||||||
@ -143,13 +143,13 @@ def detect_jaw_angle(image_path, mode='show'):
|
|||||||
|
|
||||||
# ------------------ 主函数 ------------------
|
# ------------------ 主函数 ------------------
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# ✅ 设置输入图像路径
|
# ✅ 设置输入图像路
|
||||||
image_path = '/test_image/1.png' # ← 修改为你自己的图片路径
|
image_path = r"/home/hx/yolo/output_masks/2.jpg" # ← 修改为你自己的图片路径
|
||||||
|
|
||||||
# ✅ 模式选择:
|
# ✅ 模式选择:
|
||||||
# mode='show': 保存可视化图像
|
# mode='show': 保存可视化图像
|
||||||
# mode='silent': 只返回角度
|
# mode='silent': 只返回角度
|
||||||
mode = 'silent' # 或 'silent'
|
mode = 'show' # 或 'silent'
|
||||||
|
|
||||||
print(f"🔍 正在处理图像: {image_path}")
|
print(f"🔍 正在处理图像: {image_path}")
|
||||||
angle = detect_jaw_angle(image_path, mode=mode)
|
angle = detect_jaw_angle(image_path, mode=mode)
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import torch
|
|||||||
import torch.nn.functional as F
|
import torch.nn.functional as F
|
||||||
|
|
||||||
# ------------------ 模型与路径配置 ------------------
|
# ------------------ 模型与路径配置 ------------------
|
||||||
MODEL_PATH = '../ultralytics_yolo11-main/runs/train/exp4/weights/best.pt'
|
MODEL_PATH = '../ultralytics_yolo11-main/runs/train/seg/exp2/weights/best.pt'
|
||||||
OUTPUT_DIR = '../test_image'
|
OUTPUT_DIR = '../test_image'
|
||||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ def detect_jaw_angle_fast(image_path, mode='silent'):
|
|||||||
name_only = os.path.splitext(filename)[0]
|
name_only = os.path.splitext(filename)[0]
|
||||||
|
|
||||||
# 推理(批量可进一步提速)
|
# 推理(批量可进一步提速)
|
||||||
results = model(image_path, imgsz=1280, conf=0.5, device='cuda')
|
results = model(image_path, imgsz=1280, conf=0.15, device='cuda')
|
||||||
r = results[0]
|
r = results[0]
|
||||||
|
|
||||||
if r.masks is None:
|
if r.masks is None:
|
||||||
@ -100,9 +100,9 @@ def detect_jaw_angle_fast(image_path, mode='silent'):
|
|||||||
|
|
||||||
# ------------------ 测试 ------------------
|
# ------------------ 测试 ------------------
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
image_path = '/test_image/1.png'
|
image_path = '/home/hx/yolo/output_masks/1.jpg'
|
||||||
print(f"🚀 处理: {image_path}")
|
print(f"🚀 处理: {image_path}")
|
||||||
angle = detect_jaw_angle_fast(image_path, mode='silent')
|
angle = detect_jaw_angle_fast(image_path, mode='show')
|
||||||
if angle is not None:
|
if angle is not None:
|
||||||
print(f"✅ 角度: {angle}°")
|
print(f"✅ 角度: {angle}°")
|
||||||
else:
|
else:
|
||||||
|
|||||||
109
angle_base_seg/angle_test_1.py
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
from ultralytics import YOLO
|
||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
import os
|
||||||
|
import torch
|
||||||
|
import torch.nn.functional as F
|
||||||
|
|
||||||
|
# ------------------ 模型与路径配置 ------------------
|
||||||
|
MODEL_PATH = '../ultralytics_yolo11-main/runs/train/seg/exp3/weights/best.pt'
|
||||||
|
OUTPUT_DIR = '../test_image'
|
||||||
|
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
model = YOLO(MODEL_PATH)
|
||||||
|
model.to('cuda')
|
||||||
|
|
||||||
|
|
||||||
|
def detect_jaw_angle_fast(image_path, mode='silent'):
|
||||||
|
img = cv2.imread(image_path)
|
||||||
|
if img is None:
|
||||||
|
raise FileNotFoundError(f"无法读取图像: {image_path}")
|
||||||
|
|
||||||
|
h, w = img.shape[:2]
|
||||||
|
filename = os.path.basename(image_path)
|
||||||
|
name_only = os.path.splitext(filename)[0]
|
||||||
|
|
||||||
|
# 推理(批量可进一步提速)
|
||||||
|
results = model(image_path, imgsz=640, conf=0.5, device='cuda')
|
||||||
|
r = results[0]
|
||||||
|
|
||||||
|
if r.masks is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 【优化1】一次性上采样所有 masks
|
||||||
|
masks_tensor = r.masks.data # [N, h_m, w_m]
|
||||||
|
boxes = r.boxes.xyxy.cpu().numpy()
|
||||||
|
|
||||||
|
masks = F.interpolate(
|
||||||
|
masks_tensor.unsqueeze(0).float(),
|
||||||
|
size=(h, w),
|
||||||
|
mode='bilinear',
|
||||||
|
align_corners=False
|
||||||
|
)
|
||||||
|
masks = (masks[0] > 0.5).cpu().numpy().astype(np.uint8) # [N, h, w]
|
||||||
|
|
||||||
|
jaws = []
|
||||||
|
|
||||||
|
for i, (mask, box) in enumerate(zip(masks, boxes)):
|
||||||
|
x1, y1, x2, y2 = map(int, box)
|
||||||
|
|
||||||
|
# 【优化4】提前过滤小框
|
||||||
|
if (x2 - x1) * (y2 - y1) < 100:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 【优化2】裁剪区域
|
||||||
|
x1c, y1c = max(0, x1), max(0, y1)
|
||||||
|
x2c, y2c = min(w, x2), min(h, y2)
|
||||||
|
mask_crop = mask[y1c:y2c, x1c:x2c]
|
||||||
|
|
||||||
|
# 【优化3】使用 findNonZero + convexHull
|
||||||
|
coords = cv2.findNonZero(mask_crop)
|
||||||
|
if coords is None or len(coords) < 5:
|
||||||
|
continue
|
||||||
|
|
||||||
|
hull = cv2.convexHull(coords)
|
||||||
|
area = cv2.contourArea(hull)
|
||||||
|
if area < 100:
|
||||||
|
continue
|
||||||
|
|
||||||
|
rect = cv2.minAreaRect(hull)
|
||||||
|
jaws.append({'rect': rect, 'area': area})
|
||||||
|
|
||||||
|
if len(jaws) < 2:
|
||||||
|
return None
|
||||||
|
|
||||||
|
jaws = sorted(jaws, key=lambda x: x['area'], reverse=True)[:2]
|
||||||
|
rect1, rect2 = jaws[0]['rect'], jaws[1]['rect']
|
||||||
|
|
||||||
|
def get_angle(rect):
|
||||||
|
w, h = rect[1]
|
||||||
|
angle = rect[2]
|
||||||
|
return angle + 90 if w < h else angle
|
||||||
|
|
||||||
|
angle1 = get_angle(rect1) % 180
|
||||||
|
angle2 = get_angle(rect2) % 180
|
||||||
|
opening_angle = min(abs(angle1 - angle2), 180 - abs(angle1 - angle2))
|
||||||
|
|
||||||
|
# 可视化(可选)
|
||||||
|
if mode == 'show':
|
||||||
|
vis = np.zeros((h, w, 3), dtype=np.uint8)
|
||||||
|
box1 = cv2.boxPoints(rect1)
|
||||||
|
box2 = cv2.boxPoints(rect2)
|
||||||
|
cv2.drawContours(vis, [np.int32(box1)], 0, (0, 0, 255), 2)
|
||||||
|
cv2.drawContours(vis, [np.int32(box2)], 0, (255, 0, 0), 2)
|
||||||
|
cv2.putText(vis, f"{opening_angle:.1f}°", (20, 50),
|
||||||
|
cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 255, 0), 2)
|
||||||
|
cv2.imwrite(os.path.join(OUTPUT_DIR, f"fast_{name_only}.png"), vis)
|
||||||
|
|
||||||
|
return round(opening_angle, 2)
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------ 测试 ------------------
|
||||||
|
if __name__ == '__main__':
|
||||||
|
image_path = '/home/hx/yolo/output_masks/2.jpg'
|
||||||
|
print(f"🚀 处理: {image_path}")
|
||||||
|
angle = detect_jaw_angle_fast(image_path, mode='show')
|
||||||
|
if angle is not None:
|
||||||
|
print(f"✅ 角度: {angle}°")
|
||||||
|
else:
|
||||||
|
print("❌ 未检测到两个夹具")
|
||||||
206
angle_base_seg/bushu.py
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import os
|
||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
import argparse
|
||||||
|
import torch
|
||||||
|
import torch.nn.functional as F
|
||||||
|
import torchvision
|
||||||
|
|
||||||
|
# ---------------- 配置 ----------------
|
||||||
|
OBJ_THRESH = 0.25
|
||||||
|
NMS_THRESH = 0.45
|
||||||
|
MAX_DETECT = 300
|
||||||
|
IMG_SIZE = (640, 640) # (W,H)
|
||||||
|
|
||||||
|
OUTPUT_DIR = "result"
|
||||||
|
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------- 工具函数 ----------------
|
||||||
|
def sigmoid(x):
|
||||||
|
return 1 / (1 + np.exp(-x))
|
||||||
|
|
||||||
|
def dfl(position):
|
||||||
|
x = torch.tensor(position)
|
||||||
|
n, c, h, w = x.shape
|
||||||
|
y = x.reshape(n, 4, c // 4, h, w)
|
||||||
|
y = y.softmax(2)
|
||||||
|
acc_metrix = torch.arange(c // 4).float().reshape(1, 1, c // 4, 1, 1)
|
||||||
|
y = (y * acc_metrix).sum(2)
|
||||||
|
return y.numpy()
|
||||||
|
|
||||||
|
def box_process(position):
|
||||||
|
grid_h, grid_w = position.shape[2:4]
|
||||||
|
col, row = np.meshgrid(np.arange(0, grid_w), np.arange(0, grid_h))
|
||||||
|
col, row = col.reshape(1, 1, grid_h, grid_w), row.reshape(1, 1, grid_h, grid_w)
|
||||||
|
grid = np.concatenate((col, row), axis=1)
|
||||||
|
stride = np.array([IMG_SIZE[1] // grid_h, IMG_SIZE[0] // grid_w]).reshape(1, 2, 1, 1)
|
||||||
|
|
||||||
|
position = dfl(position)
|
||||||
|
box_xy = grid + 0.5 - position[:, 0:2, :, :]
|
||||||
|
box_xy2 = grid + 0.5 + position[:, 2:4, :, :]
|
||||||
|
xyxy = np.concatenate((box_xy * stride, box_xy2 * stride), axis=1)
|
||||||
|
return xyxy
|
||||||
|
|
||||||
|
def _crop_mask(masks, boxes):
|
||||||
|
n, h, w = masks.shape
|
||||||
|
x1, y1, x2, y2 = torch.chunk(boxes[:, :, None], 4, 1)
|
||||||
|
r = torch.arange(w, device=masks.device, dtype=x1.dtype)[None, None, :]
|
||||||
|
c = torch.arange(h, device=masks.device, dtype=x1.dtype)[None, :, None]
|
||||||
|
return masks * ((r >= x1) * (r < x2) * (c >= y1) * (c < y2))
|
||||||
|
|
||||||
|
def post_process(input_data):
|
||||||
|
proto = input_data[-1]
|
||||||
|
boxes, scores, seg_part = [], [], []
|
||||||
|
default_branch = 3
|
||||||
|
pair_per_branch = len(input_data) // default_branch
|
||||||
|
|
||||||
|
for i in range(default_branch):
|
||||||
|
boxes.append(box_process(input_data[pair_per_branch * i]))
|
||||||
|
scores.append(np.ones_like(input_data[pair_per_branch * i + 1][:, :1, :, :], dtype=np.float32))
|
||||||
|
seg_part.append(input_data[pair_per_branch * i + 3])
|
||||||
|
|
||||||
|
def sp_flatten(_in):
|
||||||
|
ch = _in.shape[1]
|
||||||
|
_in = _in.transpose(0, 2, 3, 1)
|
||||||
|
return _in.reshape(-1, ch)
|
||||||
|
|
||||||
|
boxes = np.concatenate([sp_flatten(v) for v in boxes])
|
||||||
|
scores = np.concatenate([sp_flatten(v) for v in scores])
|
||||||
|
seg_part = np.concatenate([sp_flatten(v) for v in seg_part])
|
||||||
|
|
||||||
|
# 阈值过滤
|
||||||
|
keep = np.where(scores.reshape(-1) >= OBJ_THRESH)
|
||||||
|
boxes, scores, seg_part = boxes[keep], scores[keep], seg_part[keep]
|
||||||
|
|
||||||
|
# NMS
|
||||||
|
ids = torchvision.ops.nms(torch.tensor(boxes, dtype=torch.float32),
|
||||||
|
torch.tensor(scores, dtype=torch.float32), NMS_THRESH)
|
||||||
|
ids = ids.tolist()[:MAX_DETECT]
|
||||||
|
boxes, scores, seg_part = boxes[ids], scores[ids], seg_part[ids]
|
||||||
|
|
||||||
|
# mask decode
|
||||||
|
ph, pw = proto.shape[-2:]
|
||||||
|
proto = proto.reshape(seg_part.shape[-1], -1)
|
||||||
|
seg_img = np.matmul(seg_part, proto)
|
||||||
|
seg_img = sigmoid(seg_img)
|
||||||
|
seg_img = seg_img.reshape(-1, ph, pw)
|
||||||
|
seg_img = F.interpolate(torch.tensor(seg_img)[None], torch.Size([640, 640]), mode='bilinear', align_corners=False)[0]
|
||||||
|
seg_img_t = _crop_mask(seg_img, torch.tensor(boxes))
|
||||||
|
seg_img = seg_img_t.numpy() > 0.5
|
||||||
|
|
||||||
|
return boxes, scores, seg_img
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------- 角度计算 ----------------
|
||||||
|
def compute_angle(boxes, seg_img, h, w, filename, mode="show"):
|
||||||
|
composite_mask = np.zeros((h, w), dtype=np.uint8)
|
||||||
|
jaws = []
|
||||||
|
|
||||||
|
for i, box in enumerate(boxes):
|
||||||
|
x1, y1, x2, y2 = map(int, box)
|
||||||
|
x1, y1 = max(0, x1), max(0, y1)
|
||||||
|
x2, y2 = min(w, x2), min(h, y2)
|
||||||
|
|
||||||
|
obj_mask = np.zeros((h, w), dtype=np.uint8)
|
||||||
|
mask_resized = cv2.resize(seg_img[i].astype(np.uint8), (w, h))
|
||||||
|
obj_mask[y1:y2, x1:x2] = mask_resized[y1:y2, x1:x2] * 255
|
||||||
|
|
||||||
|
contours, _ = cv2.findContours(obj_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
|
||||||
|
if len(contours) == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
largest_contour = max(contours, key=cv2.contourArea)
|
||||||
|
area = cv2.contourArea(largest_contour)
|
||||||
|
if area < 100:
|
||||||
|
continue
|
||||||
|
|
||||||
|
rect = cv2.minAreaRect(largest_contour)
|
||||||
|
jaws.append({'contour': largest_contour, 'rect': rect, 'area': area})
|
||||||
|
composite_mask = np.maximum(composite_mask, obj_mask)
|
||||||
|
|
||||||
|
if len(jaws) < 2:
|
||||||
|
print(f"❌ 检测到的夹具少于2个(共{len(jaws)}个)")
|
||||||
|
return None
|
||||||
|
|
||||||
|
jaws.sort(key=lambda x: x['area'], reverse=True)
|
||||||
|
jaw1, jaw2 = jaws[0], jaws[1]
|
||||||
|
|
||||||
|
def get_long_edge_vector(rect):
|
||||||
|
center, (w_, h_), angle = rect
|
||||||
|
rad = np.radians(angle + (0 if w_ >= h_ else 90))
|
||||||
|
return np.array([np.cos(rad), np.sin(rad)])
|
||||||
|
|
||||||
|
def get_center(contour):
|
||||||
|
M = cv2.moments(contour)
|
||||||
|
return np.array([M['m10']/M['m00'], M['m01']/M['m00']]) if M['m00'] != 0 else np.array([0, 0])
|
||||||
|
|
||||||
|
dir1, dir2 = get_long_edge_vector(jaw1['rect']), get_long_edge_vector(jaw2['rect'])
|
||||||
|
center1, center2 = get_center(jaw1['contour']), get_center(jaw2['contour'])
|
||||||
|
fixture_center = (center1 + center2) / 2
|
||||||
|
|
||||||
|
to_center1, to_center2 = fixture_center - center1, fixture_center - center2
|
||||||
|
if np.linalg.norm(to_center1) > 1e-6 and np.dot(dir1, to_center1) < 0:
|
||||||
|
dir1 = -dir1
|
||||||
|
if np.linalg.norm(to_center2) > 1e-6 and np.dot(dir2, to_center2) < 0:
|
||||||
|
dir2 = -dir2
|
||||||
|
|
||||||
|
cos_angle = np.clip(np.dot(dir1, dir2), -1.0, 1.0)
|
||||||
|
angle = np.degrees(np.arccos(cos_angle))
|
||||||
|
opening_angle = min(angle, 180 - angle)
|
||||||
|
|
||||||
|
if mode == "show":
|
||||||
|
vis_img = np.stack([composite_mask]*3, axis=-1)
|
||||||
|
vis_img[composite_mask > 0] = [255, 255, 255]
|
||||||
|
|
||||||
|
box1, box2 = np.int32(cv2.boxPoints(jaw1['rect'])), np.int32(cv2.boxPoints(jaw2['rect']))
|
||||||
|
cv2.drawContours(vis_img, [box1], 0, (0, 0, 255), 2)
|
||||||
|
cv2.drawContours(vis_img, [box2], 0, (255, 0, 0), 2)
|
||||||
|
|
||||||
|
scale = 60
|
||||||
|
c1, c2 = tuple(np.int32(center1)), tuple(np.int32(center2))
|
||||||
|
end1, end2 = tuple(np.int32(center1 + scale * dir1)), tuple(np.int32(center2 + scale * dir2))
|
||||||
|
cv2.arrowedLine(vis_img, c1, end1, (0, 255, 0), 2, tipLength=0.3)
|
||||||
|
cv2.arrowedLine(vis_img, c2, end2, (0, 255, 0), 2, tipLength=0.3)
|
||||||
|
|
||||||
|
cv2.putText(vis_img, f"Angle: {opening_angle:.2f}°", (20, 50),
|
||||||
|
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)
|
||||||
|
|
||||||
|
save_path = os.path.join(OUTPUT_DIR, f'angle_{filename}')
|
||||||
|
cv2.imwrite(save_path, vis_img)
|
||||||
|
print(f"✅ 结果已保存: {save_path}")
|
||||||
|
|
||||||
|
return round(opening_angle, 2)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------- 主程序 ----------------
|
||||||
|
def main():
|
||||||
|
# 固定路径(写死)
|
||||||
|
MODEL_PATH = "/userdata/bushu/seg.rknn"
|
||||||
|
IMG_PATH = "/userdata/bushu/test.jpg"
|
||||||
|
|
||||||
|
from py_utils.rknn_executor import RKNN_model_container
|
||||||
|
model = RKNN_model_container(MODEL_PATH, target='rk3588', device_id=None)
|
||||||
|
|
||||||
|
img_src = cv2.imread(IMG_PATH)
|
||||||
|
if img_src is None:
|
||||||
|
print("❌ 图片路径错误:", IMG_PATH)
|
||||||
|
return
|
||||||
|
h, w = img_src.shape[:2]
|
||||||
|
img = cv2.resize(img_src, IMG_SIZE)
|
||||||
|
|
||||||
|
outputs = model.run([img])
|
||||||
|
boxes, scores, seg_img = post_process(outputs)
|
||||||
|
|
||||||
|
filename = os.path.basename(IMG_PATH)
|
||||||
|
angle = compute_angle(boxes, seg_img, h, w, filename, mode="show")
|
||||||
|
if angle is not None:
|
||||||
|
print(f"🎉 检测到的夹具开合角度: {angle}°")
|
||||||
|
|
||||||
|
model.release()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@ -5,7 +5,7 @@ import os
|
|||||||
|
|
||||||
# ------------------ 配置 ------------------
|
# ------------------ 配置 ------------------
|
||||||
model_path = '../ultralytics_yolo11-main/runs/train/exp4/weights/best.pt'
|
model_path = '../ultralytics_yolo11-main/runs/train/exp4/weights/best.pt'
|
||||||
img_folder = '/home/hx/yolo/ultralytics_yolo11-main/dataset1/test' # 你的图片文件夹路径
|
img_folder = '/home/hx/yolo/test_image' # 你的图片文件夹路径
|
||||||
output_mask_dir = 'output_masks1'
|
output_mask_dir = 'output_masks1'
|
||||||
os.makedirs(output_mask_dir, exist_ok=True)
|
os.makedirs(output_mask_dir, exist_ok=True)
|
||||||
|
|
||||||
|
|||||||
@ -4,8 +4,8 @@ import numpy as np
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
# ------------------ 配置 ------------------
|
# ------------------ 配置 ------------------
|
||||||
model_path = '../ultralytics_yolo11-main/runs/train/exp4/weights/best.pt'
|
model_path = '/home/hx/yolo/ultralytics_yolo11-main/runs/train/seg/exp3/weights/best.pt'
|
||||||
img_folder = '/home/hx/yolo/ultralytics_yolo11-main/dataset1/test'
|
img_folder = '/media/hx/04e879fa-d697-4b02-ac7e-a4148876ebb0/dataset/seg/dataset2/test'
|
||||||
output_mask_dir = 'output_masks1'
|
output_mask_dir = 'output_masks1'
|
||||||
os.makedirs(output_mask_dir, exist_ok=True)
|
os.makedirs(output_mask_dir, exist_ok=True)
|
||||||
|
|
||||||
@ -139,9 +139,9 @@ def process_image(img_path, output_dir):
|
|||||||
composite_mask = np.maximum(composite_mask, obj_mask)
|
composite_mask = np.maximum(composite_mask, obj_mask)
|
||||||
|
|
||||||
# 保存合并掩码
|
# 保存合并掩码
|
||||||
mask_save_path = os.path.join(output_dir, f'mask_{name_only}.png')
|
#mask_save_path = os.path.join(output_dir, f'mask_{name_only}.png')
|
||||||
cv2.imwrite(mask_save_path, composite_mask)
|
#cv2.imwrite(mask_save_path, composite_mask)
|
||||||
print(f"✅ 掩码已保存: {mask_save_path}")
|
#print(f"✅ 掩码已保存: {mask_save_path}")
|
||||||
|
|
||||||
if len(rotated_rects) < 2:
|
if len(rotated_rects) < 2:
|
||||||
print(f"⚠️ 检测到的对象少于2个(共{len(rotated_rects)}个): {filename}")
|
print(f"⚠️ 检测到的对象少于2个(共{len(rotated_rects)}个): {filename}")
|
||||||
|
|||||||
@ -4,8 +4,8 @@ import numpy as np
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
# ------------------ 配置 ------------------
|
# ------------------ 配置 ------------------
|
||||||
model_path = '../ultralytics_yolo11-main/runs/train/exp4/weights/best.pt'
|
model_path = '/home/hx/yolo/ultralytics_yolo11-main/runs/train/seg/exp3/weights/best.pt'
|
||||||
img_folder = '/home/hx/yolo/ultralytics_yolo11-main/dataset1/test'
|
img_folder = '/media/hx/04e879fa-d697-4b02-ac7e-a4148876ebb0/dataset/seg/dataset2/test'
|
||||||
output_mask_dir = 'output_masks1'
|
output_mask_dir = 'output_masks1'
|
||||||
os.makedirs(output_mask_dir, exist_ok=True)
|
os.makedirs(output_mask_dir, exist_ok=True)
|
||||||
|
|
||||||
|
|||||||
@ -80,8 +80,8 @@ def process_images_in_folder(input_folder, output_folder):
|
|||||||
|
|
||||||
# ================ 使用示例 ================
|
# ================ 使用示例 ================
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
folder = "/media/hx/04e879fa-d697-4b02-ac7e-a4148876ebb0/dataset/f10"
|
folder = "/media/hx/04e879fa-d697-4b02-ac7e-a4148876ebb0/dataset/f15"
|
||||||
output_folder = "/media/hx/04e879fa-d697-4b02-ac7e-a4148876ebb0/dataset/f10"
|
output_folder = "/media/hx/04e879fa-d697-4b02-ac7e-a4148876ebb0/dataset/f15"
|
||||||
|
|
||||||
if not os.path.exists(folder):
|
if not os.path.exists(folder):
|
||||||
print("❌ 输入文件夹不存在!")
|
print("❌ 输入文件夹不存在!")
|
||||||
|
|||||||
@ -106,7 +106,7 @@ def delete_gray_images(folder_path, extensions=None, dry_run=False):
|
|||||||
|
|
||||||
|
|
||||||
# ================== 用户配置 ==================
|
# ================== 用户配置 ==================
|
||||||
FOLDER_PATH = "/media/hx/04e879fa-d697-4b02-ac7e-a4148876ebb0/dataset/f10" # 修改为你的图片文件夹
|
FOLDER_PATH = "/media/hx/04e879fa-d697-4b02-ac7e-a4148876ebb0/dataset/f15" # 修改为你的图片文件夹
|
||||||
DRY_RUN = False # 先设为 True 测试,确认无误后再改为 False
|
DRY_RUN = False # 先设为 True 测试,确认无误后再改为 False
|
||||||
|
|
||||||
# ================== 执行 ==================
|
# ================== 执行 ==================
|
||||||
|
|||||||
@ -91,7 +91,7 @@ def delete_similar_consecutive_images(folder_path, threshold=0.95, extensions=No
|
|||||||
|
|
||||||
|
|
||||||
# ================== 用户配置 ==================
|
# ================== 用户配置 ==================
|
||||||
FOLDER_PATH = "/media/hx/04e879fa-d697-4b02-ac7e-a4148876ebb0/dataset/f10" # 修改为你的图片文件夹路径
|
FOLDER_PATH = "/media/hx/04e879fa-d697-4b02-ac7e-a4148876ebb0/dataset/f15" # 修改为你的图片文件夹路径
|
||||||
THRESHOLD = 0.90 # SSIM 阈值
|
THRESHOLD = 0.90 # SSIM 阈值
|
||||||
|
|
||||||
# ================== 执行 ==================
|
# ================== 执行 ==================
|
||||||
|
|||||||
BIN
output_images/keypoints_1.jpg
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
output_images/keypoints_2.jpg
Normal file
|
After Width: | Height: | Size: 974 KiB |
|
Before Width: | Height: | Size: 5.4 MiB |
|
Before Width: | Height: | Size: 4.8 MiB After Width: | Height: | Size: 4.8 MiB |
BIN
output_masks/1.jpg
Normal file
|
After Width: | Height: | Size: 587 KiB |
BIN
output_masks/2.jpg
Normal file
|
After Width: | Height: | Size: 513 KiB |
|
Before Width: | Height: | Size: 3.9 MiB After Width: | Height: | Size: 3.9 MiB |
BIN
output_masks/4.jpg
Normal file
|
After Width: | Height: | Size: 545 KiB |
BIN
seg01.onnx
Normal file
BIN
test_image/2.png
|
Before Width: | Height: | Size: 6.5 MiB |
|
Before Width: | Height: | Size: 26 KiB |
@ -1,7 +1,7 @@
|
|||||||
path: /home/hx/yolo/ultralytics_yolo11-main/dataset1 # 数据集所在路径
|
path: /media/hx/04e879fa-d697-4b02-ac7e-a4148876ebb0/dataset/seg/dataset2 # 数据集所在路径
|
||||||
train: train # 数据集路径下的train.txt
|
train: train # 数据集路径下的train.txt
|
||||||
val: val # 数据集路径下的val.txt
|
val: val # 数据集路径下的val.txt
|
||||||
test: test # 数据集路径下的test.txt
|
test: test # 数据集路径下的test.txt
|
||||||
|
|
||||||
nc: 1
|
nc: 1
|
||||||
names: ['夹具1']
|
names: ['jiaju']
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 515 KiB |
|
Before Width: | Height: | Size: 518 KiB |
|
Before Width: | Height: | Size: 534 KiB |
|
Before Width: | Height: | Size: 529 KiB |
|
Before Width: | Height: | Size: 497 KiB |
|
Before Width: | Height: | Size: 549 KiB |
|
Before Width: | Height: | Size: 579 KiB |
|
Before Width: | Height: | Size: 571 KiB |
|
Before Width: | Height: | Size: 590 KiB |
|
Before Width: | Height: | Size: 554 KiB |
|
Before Width: | Height: | Size: 578 KiB |
|
Before Width: | Height: | Size: 601 KiB |
|
Before Width: | Height: | Size: 614 KiB |
|
Before Width: | Height: | Size: 616 KiB |
|
Before Width: | Height: | Size: 618 KiB |
|
Before Width: | Height: | Size: 560 KiB |
|
Before Width: | Height: | Size: 572 KiB |
|
Before Width: | Height: | Size: 589 KiB |
|
Before Width: | Height: | Size: 598 KiB |
|
Before Width: | Height: | Size: 607 KiB |
|
Before Width: | Height: | Size: 613 KiB |
|
Before Width: | Height: | Size: 619 KiB |
|
Before Width: | Height: | Size: 551 KiB |
|
Before Width: | Height: | Size: 564 KiB |
|
Before Width: | Height: | Size: 587 KiB |
|
Before Width: | Height: | Size: 599 KiB |
|
Before Width: | Height: | Size: 610 KiB |
|
Before Width: | Height: | Size: 638 KiB |
|
Before Width: | Height: | Size: 583 KiB |
|
Before Width: | Height: | Size: 606 KiB |
|
Before Width: | Height: | Size: 623 KiB |
|
Before Width: | Height: | Size: 633 KiB |
|
Before Width: | Height: | Size: 640 KiB |
|
Before Width: | Height: | Size: 646 KiB |
|
Before Width: | Height: | Size: 647 KiB |
|
Before Width: | Height: | Size: 591 KiB |
|
Before Width: | Height: | Size: 612 KiB |
|
Before Width: | Height: | Size: 625 KiB |
|
Before Width: | Height: | Size: 628 KiB |
|
Before Width: | Height: | Size: 638 KiB |
|
Before Width: | Height: | Size: 641 KiB |
|
Before Width: | Height: | Size: 573 KiB |
|
Before Width: | Height: | Size: 600 KiB |
|
Before Width: | Height: | Size: 620 KiB |
|
Before Width: | Height: | Size: 635 KiB |
|
Before Width: | Height: | Size: 640 KiB |
|
Before Width: | Height: | Size: 576 KiB |
|
Before Width: | Height: | Size: 601 KiB |
|
Before Width: | Height: | Size: 616 KiB |
|
Before Width: | Height: | Size: 630 KiB |
|
Before Width: | Height: | Size: 633 KiB |
|
Before Width: | Height: | Size: 640 KiB |
|
Before Width: | Height: | Size: 643 KiB |
|
Before Width: | Height: | Size: 583 KiB |
|
Before Width: | Height: | Size: 607 KiB |
|
Before Width: | Height: | Size: 627 KiB |
|
Before Width: | Height: | Size: 636 KiB |
|
Before Width: | Height: | Size: 642 KiB |
|
Before Width: | Height: | Size: 648 KiB |
|
Before Width: | Height: | Size: 650 KiB |
|
Before Width: | Height: | Size: 594 KiB |
|
Before Width: | Height: | Size: 614 KiB |