Mỗi mặt nạ đi qua cv2.moments với binaryImage bật, rồi cv2.HuMoments tạo vector bảy phần tử. Ta kiểm tra trên các giá trị thô. Biểu đồ mới dùng biến đổi −sign(h)log10(max(∣h∣,10−30)) để đưa các số rất khác độ lớn lên cùng một trục.
Giới hạn nhỏ trong log tránh log của không. Nó không tạo thêm thông tin về hình và cũng không biến một thành phần gần không thành đặc trưng ổn định. Khi h thay dấu rất gần không, giá trị log có dấu có thể nhảy mạnh dù thay đổi tuyệt đối ban đầu cực nhỏ.
Mã dưới đây là một chương trình hoàn chỉnh, tự tạo dữ liệu và chạy trên CPU. Cài các thư viện numpy, scipy, matplotlib và opencv-python trong một môi trường Python, rồi lưu mã bằng đúng tên tập tin được ghi bên dưới. Chạy tập tin bằng Python. Có thể truyền --output để chọn nơi lưu hình và --metrics để chọn nơi lưu kết quả số dạng JSON. Chương trình không mở cửa sổ tương tác nên cũng chạy được trong terminal. Các lệnh assert dừng chương trình nếu một tính chất cần kiểm tra không còn đúng. Khi đổi dữ liệu hoặc tham số, hãy đọc điều kiện kiểm tra trước khi thay ngưỡng để tránh che một lỗi thật.
"""Original CPU experiment. Run: python FILE.py --output result.png --metrics result.json"""
import argparse, json
from pathlib import Path
import numpy as np
import cv2
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from scipy import ndimage, signal, fft
parser=argparse.ArgumentParser()
parser.add_argument('--output',default=Path(__file__).stem+'.png')
parser.add_argument('--metrics',default=Path(__file__).stem+'.json')
args=parser.parse_args()
plt.rcParams.update({'font.family':'DejaVu Sans','font.size':11,'axes.titlesize':13,'figure.facecolor':'#f5f7f1','axes.prop_cycle':plt.cycler(color=['#28765a','#bd8f4f','#687dc1'])})
rng=np.random.default_rng(17)
metrics={}
def panels(images,titles,cmaps=None):
fig,axes=plt.subplots(1,len(images),figsize=(12,4.8),constrained_layout=True)
axes=np.atleast_1d(axes)
for i,(ax,im,title) in enumerate(zip(axes,images,titles)):
ax.imshow(im,cmap=(cmaps[i] if cmaps else 'viridis'),interpolation='nearest')
ax.set_title(title)
ax.set_axis_off()
return fig,axes
mask=np.zeros((180,180),np.uint8)
poly=np.array([[24,30],[124,36],[143,71],[104,89],[110,145],[57,122],[35,79]])
cv2.fillPoly(mask,[poly],1)
variants=[mask,np.roll(mask,(9,12),(0,1)),np.rot90(mask).copy(),np.fliplr(mask).copy()]
hu=np.array([cv2.HuMoments(cv2.moments(v,True)).ravel() for v in variants])
assert np.allclose(hu[0],hu[1],rtol=1e-7,atol=1e-15)
assert np.allclose(hu[0],hu[2],rtol=1e-7,atol=1e-15)
assert np.allclose(hu[0,:6],hu[3,:6],rtol=1e-7,atol=1e-15)
assert np.isclose(hu[0,6],-hu[3,6],rtol=1e-7,atol=1e-15)
fig,axes=plt.subplots(2,4,figsize=(12,6.5),constrained_layout=True)
names=['Gốc','Dịch nguyên pixel','Xoay đúng 90°','Phản chiếu']
for k in range(4):
axes[0,k].imshow(variants[k],cmap='Greens')
axes[0,k].set_title(names[k])
axes[0,k].axis('off')
h=-np.sign(hu[k])*np.log10(np.maximum(abs(hu[k]),1e-30))
axes[1,k].bar(np.arange(1,8),h,color='#28765a')
axes[1,k].set_ylim(-23,23)
axes[1,k].set_xticks([1,4,7])
axes[1,k].set_xlabel('Chỉ số Hu')
metrics={'hu_raw':hu.tolist(),'translation_max_error':float(abs(hu[0]-hu[1]).max()),'rotation90_max_error':float(abs(hu[0]-hu[2]).max()),'mirror_hu7_sum':float(hu[0,6]+hu[3,6])}
Path(args.output).parent.mkdir(parents=True,exist_ok=True)
Path(args.metrics).parent.mkdir(parents=True,exist_ok=True)
fig.savefig(args.output,dpi=150,facecolor=fig.get_facecolor())
plt.close(fig)
Path(args.metrics).write_text(json.dumps(metrics,ensure_ascii=False,indent=2),encoding='utf-8')
print(json.dumps(metrics,ensure_ascii=False))