本课学习图像变换(仿射/透视)的核心原理与实现。我们将从数学基础出发,通过代码实践真正理解每一个算法的运作机制。
import cv2, numpy as np
img = np.zeros((300,300), dtype=np.uint8)
cv2.rectangle(img, (50,50), (150,150), 200, -1)
cv2.circle(img, (200,200), 40, 150, -1)
M_translate = np.float32([[1,0,50],[0,1,30]])
M_rotate = cv2.getRotationMatrix2D((150,150), 45, 1.0)
M_shear = np.float32([[1,0.3,0],[0.3,1,0]])
translated = cv2.warpAffine(img, M_translate, (300,300))
rotated = cv2.warpAffine(img, M_rotate, (300,300))
sheared = cv2.warpAffine(img, M_shear, (300,300))
print(f"平移矩阵: {M_translate.tolist()}")
print(f"旋转矩阵(45deg): {np.round(M_rotate,3).tolist()}")
cv2.imwrite('/var/www/ttl/cv/l06_affine.png', np.hstack([img, translated, rotated, sheared]))
import cv2, numpy as np
img = np.zeros((200,200), dtype=np.uint8)
cv2.rectangle(img, (60,60), (140,140), 255, -1)
methods = {'NEAREST':cv2.INTER_NEAREST, 'LINEAR':cv2.INTER_LINEAR, 'CUBIC':cv2.INTER_CUBIC}
for name, method in methods.items():
scaled = cv2.resize(img, None, fx=3, fy=3, interpolation=method)
print(f"{name}: shape={scaled.shape}")
M = cv2.getRotationMatrix2D((100,100), 45, 1.0)
rot = cv2.warpAffine(img, M, (200,200))
cv2.imwrite('/var/www/ttl/cv/l06_rotate.png', rot)
import cv2, numpy as np
img = np.zeros((400,500,3), dtype=np.uint8)
cv2.rectangle(img, (50,50), (450,350), (40,40,40), -1)
cv2.putText(img, 'Document', (100,180), cv2.FONT_HERSHEY_SIMPLEX, 2, (200,200,200), 2)
src = np.float32([[50,50],[450,50],[450,350],[50,350]])
dst = np.float32([[100,80],[420,50],[380,320],[80,350]])
M_distort = cv2.getPerspectiveTransform(src, dst)
distorted = cv2.warpPerspective(img, M_distort, (500,400))
M_undistort = cv2.getPerspectiveTransform(dst, src)
undistorted = cv2.warpPerspective(distorted, M_undistort, (500,400))
M_identity = M_undistort @ M_distort
print(f"正x逆(应接近I): {np.round(M_identity/M_identity[2,2], 2).tolist()}")
cv2.imwrite('/var/www/ttl/cv/l06_perspective.png', np.hstack([img, distorted, undistorted]))
import cv2, numpy as np
img = np.zeros((32,32,3), dtype=np.uint8)
cv2.circle(img, (16,16), 10, (0,255,0), -1)
for name, method in [('NEAREST',cv2.INTER_NEAREST),('LINEAR',cv2.INTER_LINEAR),('CUBIC',cv2.INTER_CUBIC)]:
scaled = cv2.resize(img, None, fx=8, fy=8, interpolation=method)
diag = [scaled[i*8,i*8,2] for i in range(32)]
transitions = sum(1 for i in range(1,len(diag)) if abs(diag[i]-diag[i-1])>30)
print(f"{name}: 对角线边缘过渡={transitions}次")
cv2.imwrite('/var/www/ttl/cv/l06_interp.png', cv2.resize(img, None, fx=8,fy=8, interpolation=cv2.INTER_CUBIC))
import cv2, numpy as np
M = cv2.getRotationMatrix2D((100,100), 30, 1.5)
A = M[:2,:2]
U, S, Vt = np.linalg.svd(A)
print(f"变换矩阵SVD: 奇异值={S}, 面积缩放={S[0]*S[1]:.3f}")
pts1 = np.float32([[50,50],[200,50],[50,200]])
pts2 = np.float32([[70,60],[220,40],[40,210]])
M_affine = cv2.getAffineTransform(pts1, pts2)
print(f"3点仿射矩阵: {np.round(M_affine,3).tolist()}")
for i in range(3):
p = np.append(pts1[i], 1)
print(f" 点{i}: 输入{pts1[i]}->计算{np.round(M_affine@p,1)}, 目标{pts2[i]}")
import cv2, numpy as np
ref = np.zeros((256,256), dtype=np.uint8)
cv2.circle(ref, (128,128), 60, 200, -1)
M_known = cv2.getRotationMatrix2D((128,128), 5, 1.0)
M_known[0,2] += 10; M_known[1,2] += 5
moved = cv2.warpAffine(ref, M_known, (256,256))
f_ref = np.fft.fft2(ref)
f_mov = np.fft.fft2(moved)
cross_power = f_ref * np.conj(f_mov) / (np.abs(f_ref)*np.abs(f_mov) + 1e-10)
correlation = np.fft.ifft2(cross_power)
shift = np.unravel_index(np.argmax(np.abs(correlation)), correlation.shape)
if shift[0] > 128: shift = (shift[0]-256, shift[1])
if shift[1] > 128: shift = (shift[0], shift[1]-256)
print(f"相位相关检测平移: ({shift[1]}, {shift[0]})")
print(f"实际平移: (10, 5)")
import cv2, numpy as np
img = np.zeros((400,600,3), dtype=np.uint8)
cv2.fillPoly(img, [np.array([[200,0],[400,0],[500,400],[100,400]])], (60,60,60))
for y in range(0,400,40):
cv2.line(img, (280,y), (300,y+20), (0,200,200), 2)
cv2.line(img, (320,y), (340,y+20), (0,200,200), 2)
src = np.float32([[230,50],[370,50],[500,400],[100,400]])
dst = np.float32([[200,0],[400,0],[400,400],[200,400]])
M = cv2.getPerspectiveTransform(src, dst)
bird = cv2.warpPerspective(img, M, (600,400))
for y in [50,150,250,350]:
row = bird[y, 200:400, 1]
nz = np.where(row > 100)[0]
if len(nz) >= 2:
print(f"y={y}: 车道宽度={nz[-1]-nz[0]}px")
cv2.imwrite('/var/www/ttl/cv/l06_bird.png', np.hstack([img, bird]))
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 方法A | 简单高效 | 精度有限 | 快速原型 |
| 方法B | 精度高 | 计算量大 | 离线处理 |
| 方法C | 平衡精度与速度 | 参数调优复杂 | 实际应用 |
本课涉及的核心数学公式汇总,方便快速参考:
本节深入探讨图像变换的进阶话题和工程实践中的关键考虑。
# 性能基准测试模板
import time, numpy as np
def benchmark(func, *args, n=100, **kwargs):
times = []
for _ in range(n):
t0 = time.time()
result = func(*args, **kwargs)
times.append(time.time() - t0)
return np.mean(times)*1000, np.std(times)*1000
print(f"性能基准测试: 多次运行取均值和标准差")
print(f"注意: 首次运行可能较慢(JIT编译/缓存加载)")
| 优化方向 | 方法 | 精度影响 | 加速比 |
|---|---|---|---|
| 模型压缩 | 剪枝/蒸馏 | 1-3%下降 | 2-4x |
| 量化 | INT8/FP16 | <1%下降 | 2-8x |
| 算子融合 | TensorRT/ONNX | 无 | 1.5-3x |
| 批处理 | 增大batch size | 无 | 线性 |
以下是一个完整的图像变换处理管道,从数据准备到结果评估,包含所有关键步骤和参数调优建议:
import cv2
import numpy as np
import time
class 图像变换Pipeline:
"""完整的图像变换处理管道"""
def __init__(self, params=None):
self.params = params or {}
self.results = {}
def preprocess(self, image):
"""预处理步骤"""
# 1. 去噪
if self.params.get('denoise', True):
image = cv2.GaussianBlur(image, (3, 3), 1.0)
# 2. 对比度增强
if self.params.get('enhance', False):
if len(image.shape) == 2:
image = cv2.equalizeHist(image)
else:
lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
lab[:,:,0] = cv2.equalizeHist(lab[:,:,0])
image = cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)
# 3. 尺寸调整
max_size = self.params.get('max_size', 1024)
h, w = image.shape[:2]
if max(h, w) > max_size:
scale = max_size / max(h, w)
image = cv2.resize(image, (int(w*scale), int(h*scale)))
return image
def process(self, image):
"""核心处理步骤"""
t0 = time.time()
preprocessed = self.preprocess(image)
t1 = time.time()
# 主处理逻辑
result = self._main_process(preprocessed)
t2 = time.time()
self.results['timing'] = {
'preprocess': (t1-t0)*1000,
'process': (t2-t1)*1000,
'total': (t2-t0)*1000
}
return result
def _main_process(self, image):
"""主处理逻辑(子类可重写)"""
return image
def evaluate(self, result, ground_truth=None):
"""评估处理结果"""
metrics = {}
if ground_truth is not None:
if len(result.shape) == 2:
mse = np.mean((result.astype(float) - ground_truth.astype(float))**2)
metrics['mse'] = mse
metrics['psnr'] = 10 * np.log10(255**2 / (mse + 1e-10))
metrics['timing'] = self.results.get('timing', {})
return metrics
def visualize(self, image, result):
"""可视化对比"""
if len(image.shape) == 2:
image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
if len(result.shape) == 2:
result = cv2.cvtColor(result, cv2.COLOR_GRAY2BGR)
h1, w1 = image.shape[:2]
h2, w2 = result.shape[:2]
h, w = max(h1,h2), w1+w2+10
vis = np.zeros((h, w, 3), dtype=np.uint8)
vis[:h1, :w1] = image
vis[:h2, w1+10:] = result
cv2.putText(vis, 'Input', (10, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,255,0), 2)
cv2.putText(vis, 'Output', (w1+20, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,255,0), 2)
return vis
# 使用示例
pipeline = 图像变换Pipeline({'denoise': True, 'enhance': False, 'max_size': 512})
test_img = np.random.randint(0, 256, (256, 256, 3), dtype=np.uint8)
result = pipeline.process(test_img)
metrics = pipeline.evaluate(result)
print(f"处理时间: {metrics['timing'].get('total', 0):.1f}ms")
不同参数对结果的影响:
建议从默认参数开始,根据结果逐步调整。先在少量数据上快速迭代,确定参数后再全量处理。
import numpy as np
class Metrics:
@staticmethod
def mse(img1, img2):
return np.mean((img1.astype(float) - img2.astype(float))**2)
@staticmethod
def psnr(img1, img2, max_val=255):
mse_val = Metrics.mse(img1, img2)
if mse_val == 0: return float('inf')
return 10 * np.log10(max_val**2 / mse_val)
@staticmethod
def iou(mask1, mask2):
inter = np.logical_and(mask1 > 0, mask2 > 0).sum()
union = np.logical_or(mask1 > 0, mask2 > 0).sum()
return inter / (union + 1e-10)
@staticmethod
def f1_score(pred, gt, threshold=0.5):
pred_bin = (pred > threshold)
gt_bin = (gt > 0)
tp = np.logical_and(pred_bin, gt_bin).sum()
precision = tp / (pred_bin.sum() + 1e-10)
recall = tp / (gt_bin.sum() + 1e-10)
return 2 * precision * recall / (precision + recall + 1e-10)
# 示例
img1 = np.random.randint(0, 256, (100,100), dtype=np.uint8)
img2 = img1 + np.random.randint(-10, 10, (100,100))
img2 = np.clip(img2, 0, 255).astype(np.uint8)
print(f"MSE: {Metrics.mse(img1,img2):.2f}")
print(f"PSNR: {Metrics.psnr(img1,img2):.2f} dB")
print(f"说明: PSNR>30dB为良好, >40dB为优秀")
你已经掌握了图像变换(仿射/透视)的核心知识,继续前进!