add patch evluation

This commit is contained in:
unknown
2026-01-08 14:23:38 +08:00
parent dd04b337f0
commit 56bb77e1fd
8 changed files with 440 additions and 5 deletions
+2 -1
View File
@@ -9,4 +9,5 @@ orisys.egg-info/
/dist_single_pyd/ /dist_single_pyd/
/old_orisys/ /old_orisys/
/dist_nuitka/ /dist_nuitka/
/build_nuitka/ /build_nuitka
/data/
BIN
View File
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

After

Width:  |  Height:  |  Size: 2.4 MiB

+3 -2
View File
@@ -19,7 +19,8 @@ def main():
# 步骤1:创建传感器对象 (必要步骤),config_name为配置文件名(内置配置文件名)或路径(自定义配置文件路径) # 步骤1:创建传感器对象 (必要步骤),config_name为配置文件名(内置配置文件名)或路径(自定义配置文件路径)
# 方式1: 从视频文件读取 # 方式1: 从视频文件读取
sensor = orisys.Sensor("./s1.mp4", isstitch=True, cuda=True, verbose=True,config_name="prototype") sensor = orisys.Sensor("./ov2710_uv_2.mp4", isstitch=True, cuda=True, verbose=True,config_name="./config/lens_test.json")
# 方式2: 从摄像头读取(取消注释使用) # 方式2: 从摄像头读取(取消注释使用)
# sensor = orisys.Sensor(0, cuda=True, verbose=True, config_name="prototype") # sensor = orisys.Sensor(0, cuda=True, verbose=True, config_name="prototype")
@@ -31,7 +32,7 @@ def main():
# 步骤2:信息提取 # 步骤2:信息提取
# 步骤 2.1: 获取并拼接图像 (必要步骤) # 步骤 2.1: 获取并拼接图像 (必要步骤)
sensor.get_img() sensor.get_img()
print("img_size_raw:", sensor.img_size_raw)
# 步骤 2.2: 计算形变场 (必要步骤) # 步骤 2.2: 计算形变场 (必要步骤)
sensor.compute_deformation() sensor.compute_deformation()
# 步骤 2.3: 计算触碰点 # 步骤 2.3: 计算触碰点
+53
View File
@@ -0,0 +1,53 @@
#%%
import numpy as np
import cv2
def generate_color_pattern(h, w, d, r):
# Initialize the pattern with zeros
pattern = np.zeros((h, w, 3))
# Calculate the number of patches in each dimension
num_patches_x = h // d
num_patches_y = w // d
# Maximum number of attempts to find a suitable color
max_attempts = 1000
# Iterate over the patches
for i in range(num_patches_x):
for j in range(num_patches_y):
# Get the neighbors
neighbors = []
if i > 0:
neighbors.append(pattern[(i-1)*d:i*d, j*d:(j+1)*d])
if j > 0:
neighbors.append(pattern[i*d:(i+1)*d, (j-1)*d:j*d])
# Generate a new color
for attempt in range(max_attempts):
new_color = np.random.rand(3)
if all(np.min((new_color - neighbor.mean(axis=(0, 1)))**2) >= r for neighbor in neighbors):
break
if attempt == max_attempts - 1:
print(f"Warning: Could not find a suitable color for patch ({i}, {j}) after {max_attempts} attempts")
# Apply the new color to the patch
pattern[i*d:(i+1)*d, j*d:(j+1)*d] = new_color
print('Patch ({}, {})'.format(i, j))
return pattern
# Test the function
pattern = generate_color_pattern(162, 162, 3, 0.75)
# Convert the pattern to a format suitable for display with OpenCV
pattern_display = (pattern * 255).astype(np.uint8)
# # Display the pattern
# cv2.imshow('Pattern', pattern_display)
# cv2.waitKey(0)
# cv2.destroyAllWindows()
# #%%
cv2.imwrite('color_pattern.png', pattern_display)
+139
View File
@@ -0,0 +1,139 @@
#%%
import numpy as np
import cv2
def merge_intervals(intervals):
# Merge overlapping intervals.
if not intervals:
return []
intervals.sort(key=lambda x: x[0])
merged = [intervals[0]]
for current in intervals[1:]:
last = merged[-1]
if current[0] <= last[1]:
merged[-1] = (last[0], max(last[1], current[1]))
else:
merged.append(current)
return merged
def get_allowed_intervals(neighbor_values, delta):
# Compute forbidden intervals for this channel from neighbor values.
forbidden = []
for c in neighbor_values:
low = max(0, c - delta)
high = min(1, c + delta)
forbidden.append((low, high))
# Merge overlapping forbidden intervals.
merged = merge_intervals(forbidden)
allowed = []
# Allowed region before the first forbidden interval.
if merged:
if merged[0][0] > 0:
allowed.append((0, merged[0][0]))
# Allowed regions between forbidden intervals.
for i in range(1, len(merged)):
allowed.append((merged[i-1][1], merged[i][0]))
# Allowed region after the last forbidden interval.
if merged[-1][1] < 1:
allowed.append((merged[-1][1], 1))
else:
allowed.append((0, 1))
return allowed
def sample_from_allowed(allowed):
# Choose a random value from the allowed intervals with probability proportional to interval length.
lengths = [end - start for start, end in allowed]
total_length = sum(lengths)
if total_length <= 0:
return None
point = np.random.rand() * total_length
cumulative = 0
for (start, end), length in zip(allowed, lengths):
cumulative += length
if point <= cumulative:
return np.random.uniform(start, end)
return allowed[-1][0]
def sample_channel(neighbor_values, delta):
# Compute allowed intervals and sample a value.
allowed = get_allowed_intervals(neighbor_values, delta)
if not allowed:
return None
return sample_from_allowed(allowed)
def generate_valid_color(neighbors, r):
"""
Generate a random color that satisfies, for each neighbor,
|R - R_neighbor| >= sqrt(r), and similarly for G and B.
neighbors: list of neighbor colors (each is an array of shape (3,))
r: threshold for squared difference.
"""
delta = np.sqrt(r)
color = np.zeros(3)
for channel in range(3):
neighbor_channel = [n[channel] for n in neighbors]
sampled = sample_channel(neighbor_channel, delta)
# If the allowed intervals are empty, use a fallback.
if sampled is None:
sampled = np.random.rand()
color[channel] = sampled
return color
def generate_color_pattern(h, w, d, r):
"""
Generate a dense color pattern.
h, w: height and width of the output image in pixels.
d: patch size in pixels (each patch is d x d).
r: randomness regulation factor. For each neighbor the condition is:
(|channel_new - channel_neighbor|)^2 >= r.
"""
pattern = np.zeros((h, w, 3), dtype=np.float32)
num_patches_x = h // d
num_patches_y = w // d
for i in range(num_patches_x):
for j in range(num_patches_y):
# Collect neighbor patch colors (using mean because patches have uniform color).
neighbors = []
if i > 0:
patch_above = pattern[(i-1)*d:i*d, j*d:(j+1)*d]
neighbors.append(np.mean(patch_above, axis=(0, 1)))
if j > 0:
patch_left = pattern[i*d:(i+1)*d, (j-1)*d:j*d]
neighbors.append(np.mean(patch_left, axis=(0, 1)))
if len(neighbors) == 0:
# For the first patch use a random color.
new_color = np.random.rand(3)
else:
new_color = generate_valid_color(neighbors, r)
pattern[i*d:(i+1)*d, j*d:(j+1)*d] = new_color
print('Patch ({}, {})'.format(i, j))
return pattern
def main():
# Parameters for the pattern.
# For example, using 100x100 pixels image with patches of 3x3 pixels.
height = 200
width = 200
patch_size = 3
# Set r to a value that gives sufficient difference.
# Note: r is the threshold for squared difference per channel, and the condition is
# |c - c_neighbor| >= sqrt(r). Very high r may make it impossible to satisfy.
r = 0.5
pattern = generate_color_pattern(height, width, patch_size, r)
# Scale to 0-255 for display and saving.
pattern_display = (pattern * 255).astype(np.uint8)
# Display the pattern with OpenCV.
cv2.imshow('Pattern', pattern_display)
cv2.waitKey(0)
cv2.destroyAllWindows()
# Save the pattern as an image file.
cv2.imwrite('color_pattern.png', pattern_display)
if __name__ == '__main__':
main()
+224
View File
@@ -0,0 +1,224 @@
import cv2
import numpy as np
import matplotlib.pyplot as plt
def calculate_local_contrast(image_input, window_size=5):
"""
Calculate Global Average Local Contrast (for fine texture detail evaluation in optical flow).
Args:
image_input: str (image file path) or numpy array (grayscale/RGB image).
window_size: int (odd number, default=5) — sliding window size for fine texture analysis.
Returns:
float: Global average local contrast (higher = better fine texture detail; target ≥0.2 for optical flow).
Raises:
ValueError: If image is invalid, window size is even/too small, or processing fails.
"""
# ---------------------- Step 1: Input Validation & Preprocessing ----------------------
# Validate window size (must be odd, ≥3 to capture meaningful texture)
if window_size % 2 == 0 or window_size < 3:
raise ValueError(f"Window size must be an odd integer ≥3 (got {window_size}). Use 3/5/7.")
# Load/convert image to grayscale (consistent with optical flow workflow)
if isinstance(image_input, str):
img = cv2.imread(image_input)
if img is None:
raise ValueError(f"Failed to load image from path: {image_input}")
elif isinstance(image_input, np.ndarray):
img = image_input.copy()
else:
raise ValueError("Input must be an image file path (str) or numpy array (image).")
# Convert to grayscale (handle RGB/BGR)
if len(img.shape) == 3:
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
else:
gray = img # Already grayscale
# Normalize to 0-255 (avoid non-standard pixel ranges, e.g., float32)
gray = cv2.normalize(gray, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
# ---------------------- Step 2: Sliding Window Local Contrast Calculation ----------------------
# Define padding (replicate edge pixels to avoid boundary distortion)
pad_size = window_size // 2
gray_padded = cv2.copyMakeBorder(
gray,
top=pad_size, bottom=pad_size, left=pad_size, right=pad_size,
borderType=cv2.BORDER_REPLICATE
)
# Initialize array to store local contrast for each window
local_contrast_vals = []
# Iterate over every pixel (center of the sliding window)
for y in range(pad_size, gray_padded.shape[0] - pad_size):
for x in range(pad_size, gray_padded.shape[1] - pad_size):
# Extract 5x5 (or custom) window around (x,y)
window = gray_padded[y-pad_size : y+pad_size+1, x-pad_size : x+pad_size+1]
# Compute Michelson contrast for the window (avoid division by zero)
window_max = np.max(window)
window_min = np.min(window)
if window_max + window_min == 0: # All pixels are 0 (black window)
lc = 0.0
else:
lc = (window_max - window_min) / (window_max + window_min)
local_contrast_vals.append(lc)
# ---------------------- Step 3: Global Average Local Contrast ----------------------
global_local_contrast = np.mean(local_contrast_vals) if local_contrast_vals else 0.0
# Round for readability (4 decimal places)
return global_local_contrast
def calculate_edge_strength_density(image_input):
"""
Calculate Edge Strength and Edge Density for optical flow evaluation.
Args:
image_input: Can be (1) str (image file path) or (2) numpy array (grayscale/RGB image).
Returns:
dict: Contains two metrics:
- edge_strength: Average gradient magnitude of valid edges (higher = sharper edges).
- edge_density: Percentage of valid edge pixels (higher = more texture/edges).
Raises:
ValueError: If image cannot be loaded/processed.
"""
# ---------------------- Step 1: Load/Preprocess Image ----------------------
img = image_input
# Convert to grayscale (handle RGB/BGR)
if len(img.shape) == 3:
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
else:
gray = img # Already grayscale
# Normalize to 0-255 (avoid float/non-standard pixel ranges)
gray = cv2.normalize(gray, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
# ---------------------- Step 2: Compute Gradient Magnitude (Sobel) ----------------------
# Sobel kernel size = 3 (balance between noise reduction and edge sharpness for optical flow)
sobel_x = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3) # Horizontal gradient
sobel_y = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3) # Vertical gradient
# Compute gradient magnitude (edge strength raw values)
gradient_magnitude = np.sqrt(sobel_x**2 + sobel_y**2)
# ---------------------- Step 3: Adaptive Threshold for Valid Edges ----------------------
# Use median gradient to filter noise (robust to outliers)
median_gradient = np.median(gradient_magnitude)
# Threshold = 1.5× median (filters weak noise, keeps meaningful edges for optical flow)
edge_threshold = 1.5 * median_gradient
# Create binary mask for valid edges (1 = valid edge, 0 = non-edge)
valid_edge_mask = (gradient_magnitude > edge_threshold).astype(np.uint8)
# ---------------------- Step 4: Calculate Metrics ----------------------
# Edge Strength: Average gradient magnitude of valid edges
valid_gradient_values = gradient_magnitude[valid_edge_mask == 1]
edge_strength = np.mean(valid_gradient_values) if len(valid_gradient_values) > 0 else 0.0
# Edge Density: Percentage of valid edge pixels (total pixels = height × width)
total_pixels = gray.shape[0] * gray.shape[1]
valid_edge_pixels = np.sum(valid_edge_mask)
edge_density = (valid_edge_pixels / total_pixels) * 100 if total_pixels > 0 else 0.0
# print(f"edge_strength: {edge_strength}, edge_density: {edge_density}")
# Round for readability (3 decimal places)
return edge_strength, edge_density
def custom_gray_conversion(img, r_weight=0.299, g_weight=0.587, b_weight=0.114,
red_boost=1.2, green_reduce=0.8):
"""
Custom grayscale calculation with tunable RGB weights + red/green contrast enhancement
Args:
image_path: Path to input image
r_weight: Weight for Red channel (default: 0.299)
g_weight: Weight for Green channel (default: 0.587)
b_weight: Weight for Blue channel (default: 0.114)
red_boost: Multiplier to boost red region grayscale (>1 = brighter)
green_reduce: Multiplier to reduce green region grayscale (<1 = darker)
Returns:
default_gray: Grayscale with standard weights
custom_gray: Grayscale with tunable weights + red/green enhancement
"""
# 1. Read image and validate
# img_bgr = cv2.imread(image_path)
img_bgr = img
if img_bgr is None:
raise ValueError("Failed to read image - check path!")
# 2. Convert BGR (OpenCV default) to RGB and split channels
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
# Convert to float32 to avoid integer overflow/precision loss
r = img_rgb[:, :, 0].astype(np.float32)
g = img_rgb[:, :, 1].astype(np.float32)
b = img_rgb[:, :, 2].astype(np.float32)
# 3. Calculate DEFAULT grayscale (standard weights)
default_gray = 0.299 * r + 0.587 * g + 0.114 * b
# 4. Calculate CUSTOM grayscale (tunable weights)
custom_gray_base = r_weight * r + g_weight * g + b_weight * b
# 5. Enhance red/green contrast (optional but effective)
# Define red/green regions using RGB value ratios (no HSV needed)
# red_mask = (r > g * 1.2) & (r > b * 1.2) & (r >= 40) # Red-dominant pixels
# green_mask = (g > r * 1.2) & (g > b * 1.2) & (g >= 40) # Green-dominant pixels
# Apply targeted adjustments to red/green regions
custom_gray = custom_gray_base.copy()
# custom_gray[red_mask] *= red_boost # Boost red region grayscale
# custom_gray[green_mask] *= green_reduce # Reduce green region grayscale
# 6. Normalize to 0-255 range and convert back to uint8 (OpenCV standard)
# Clip values first to avoid overflow, then scale if needed
default_gray = np.clip(default_gray, 0, 255).astype(np.uint8)
custom_gray = np.clip(custom_gray, 0, 255).astype(np.uint8)
return default_gray, custom_gray
# -------------------------- Test the Code --------------------------
if __name__ == "__main__":
# Replace with your image path
IMAGE_PATH = "./data/new_texture.jpg"
img = cv2.imread(IMAGE_PATH)
dx = -80
dy = 0
tile = img[410+dy:650+dy,940+dx:1180+dx,:]
# Example 1: Use standard weights (verify correctness)
default_gray, red_gray = custom_gray_conversion(
tile,
r_weight=1, g_weight=0, b_weight=0
)
default_gray, green_gray = custom_gray_conversion(
tile,
r_weight=0, g_weight=1, b_weight=0
)
default_gray, blue_gray = custom_gray_conversion(
tile,
r_weight=0, g_weight=0, b_weight=1
)
edge_strength, edge_density = calculate_edge_strength_density(default_gray)
local_contrast = calculate_local_contrast(default_gray)
print(f"标准灰度: 边缘强度: {edge_strength}, 边缘密度: {edge_density}, 局部对比度: {local_contrast}")
edge_strength, edge_density = calculate_edge_strength_density(red_gray)
local_contrast = calculate_local_contrast(red_gray)
print(f"红色灰度: 边缘强度: {edge_strength}, 边缘密度: {edge_density}, 局部对比度: {local_contrast}")
edge_strength, edge_density = calculate_edge_strength_density(green_gray)
local_contrast = calculate_local_contrast(green_gray)
print(f"绿色灰度: 边缘强度: {edge_strength}, 边缘密度: {edge_density}, 局部对比度: {local_contrast}")
edge_strength, edge_density = calculate_edge_strength_density(blue_gray)
local_contrast = calculate_local_contrast(blue_gray)
print(f"蓝色灰度: 边缘强度: {edge_strength}, 边缘密度: {edge_density}, 局部对比度: {local_contrast}")
plt.subplot(2, 2, 1)
plt.imshow(default_gray, cmap='gray')
plt.subplot(2, 2, 2)
plt.imshow(red_gray, cmap='gray')
plt.subplot(2, 2, 3)
plt.imshow(green_gray, cmap='gray')
plt.subplot(2, 2, 4)
plt.imshow(blue_gray, cmap='gray')
plt.show()
+18 -1
View File
@@ -446,7 +446,7 @@ class Stitcher:
n_boxes = 0 n_boxes = 0
bk_thresh = 0 # initial black threshold bk_thresh = 0 # initial black threshold
bk_thresh_step = 5 bk_thresh_step = 5
bk_thresh_max = 100 bk_thresh_max = 125
# Also include a low global V threshold to capture colored but dark tiles # Also include a low global V threshold to capture colored but dark tiles
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
@@ -503,6 +503,23 @@ class Stitcher:
cropped_boxes.append((nx0, ny0, nx1, ny1)) cropped_boxes.append((nx0, ny0, nx1, ny1))
# sort boxes left-to-right, top-to-bottom (useful for grid) # sort boxes left-to-right, top-to-bottom (useful for grid)
# Use quantized y-coordinates to group boxes into rows, making sorting robust to noise
if len(cropped_boxes) > 0:
# Calculate row tolerance based on typical box height (use median height)
heights = [b[3] - b[1] for b in cropped_boxes]
median_height = np.median(heights) if heights else target_h
# Use a fraction of the box height as tolerance (e.g., 20% of box height)
row_tolerance = max(1, int(median_height * 0.2))
# Quantize y-coordinates to group boxes into rows
def get_row_key(box):
y = box[1] # y0 coordinate
# Round y to nearest multiple of row_tolerance to group into rows
quantized_y = (y // row_tolerance) * row_tolerance
return (quantized_y, box[0]) # Sort by quantized row, then by x
cropped_boxes = sorted(cropped_boxes, key=get_row_key)
else:
cropped_boxes = sorted(cropped_boxes, key=lambda b: (b[1], b[0])) cropped_boxes = sorted(cropped_boxes, key=lambda b: (b[1], b[0]))
self.boxes = cropped_boxes self.boxes = cropped_boxes