Kiểm soát ánh sáng môi trường: nắng, đèn xưởng nhấp nháy, giám sát độ sáng
Mô phỏng nhấp nháy 100 Hz theo thời gian phơi sáng, ngân sách che chắn – kính lọc – đèn chớp bằng số và hàm giám sát vùng chuẩn có cảnh báo, dừng trạm, xu hướng EWMA.
Học xong bài này, bạn sẽ
- Nhận ra ảnh hưởng của nắng, đèn xưởng
- Che chắn, lọc, tăng cường độ đèn
- Giám sát độ sáng trong sản xuất
Nên học trước: Màu đèn, kính lọc và phân cực
Nội dung bài viết
- 1. Nhận diện ảnh hưởng của ánh sáng môi trường
- 2. Đèn xưởng nhấp nháy 100 Hz và thời gian phơi sáng
- 3. Ba biện pháp phần cứng và ngân sách ánh sáng
- 4. Thuật toán bền vững: tuyến phòng thủ thứ hai
- 5. Giám sát độ sáng trong sản xuất
- 6. Đừng quên các nguồn thay đổi "bên trong"
- 7. Sai lầm thường gặp
- 8. Bài tập
- Tóm tắt
Một kịch bản quen thuộc: hệ thống chạy hoàn hảo lúc nghiệm thu buổi sáng, đến khoảng hai giờ chiều tỉ lệ NG tăng vọt, tối lại bình thường. Không ai sửa gì trên máy. Thủ phạm là ánh sáng môi trường: nắng chiếu xiên qua cửa sổ theo giờ, đèn trần nhà xưởng nhấp nháy theo điện lưới, đèn xe nâng, áo phản quang của công nhân đi ngang. Ở bài Màu đèn, kính lọc và phân cực bạn đã tính kính lọc dải thông chặn được bao nhiêu ánh sáng xưởng; bài này ghép mọi biện pháp lại thành một ngân sách ánh sáng có số liệu, mô phỏng hiện tượng nhấp nháy 100 Hz theo thời gian phơi sáng, và xây phần giám sát độ sáng chạy suốt đời trạm. Đây là mảnh cuối trước Bài tập chặng 6: chọn camera, ống kính, đèn cho bài toán thật.
1. Nhận diện ảnh hưởng của ánh sáng môi trường
| Nguồn | Đặc điểm | Triệu chứng trên trạm |
|---|---|---|
| Nắng qua cửa sổ, mái lấy sáng | Rất mạnh, đổi chậm theo giờ, theo mùa, theo mây; chứa nhiều hồng ngoại | Kết quả thay đổi theo giờ trong ngày, khác nhau giữa ngày nắng và ngày mưa |
| Đèn trần (huỳnh quang, LED, cao áp) | Ổn định theo giờ nhưng có thể nhấp nháy 100 Hz (điện lưới 50 Hz) | Độ sáng ảnh dao động ngẫu nhiên từ ảnh này sang ảnh khác; sọc ngang với cảm biến rolling shutter |
| Xe nâng, người đi qua, áo phản quang | Đột ngột, ngắn (bóng che hoặc phản xạ) | Vài ảnh NG lẻ tẻ không giải thích được, không lặp lại khi chụp lại |
| Đèn của trạm bên cạnh, đèn chớp của máy khác | Theo nhịp máy khác | Lỗi chỉ xuất hiện khi máy bên cạnh chạy |
| Hàn hồ quang, laser đánh dấu | Cực mạnh, có UV/IR | Ảnh cháy sáng; cần che chắn riêng, vì lý do an toàn nữa |
Hai phép đo chẩn đoán làm ngay trong ngày đầu tiên tại hiện trường:
- Ảnh "tắt đèn trạm": giữ nguyên phơi sáng, gain, tắt đèn của trạm, chụp vài ảnh ở các giờ khác nhau (sáng, trưa, chiều nắng, ca đêm). Mức xám còn lại chính là đóng góp của ánh sáng môi trường. Tỉ số (ảnh có đèn − ảnh tắt đèn) / ảnh tắt đèn là tỉ số đèn/môi trường, con số trung tâm của cả bài.
- Log độ sáng vùng chuẩn: mỗi lần chụp, ghi mức xám trung bình của một vùng cố định không có sản phẩm (mục 5). Vẽ theo thời gian vài ngày là thấy ngay quy luật theo giờ, theo ca, hay đột biến.
2. Đèn xưởng nhấp nháy 100 Hz và thời gian phơi sáng
Điện lưới Việt Nam là 220 V, 50 Hz. Đèn huỳnh quang dùng chấn lưu sắt từ và nhiều đèn LED giá rẻ (bộ nguồn chỉ chỉnh lưu, lọc kém) sáng mạnh nhất ở cả đỉnh dương và đỉnh âm của điện áp, nên cường độ dao động với tần số 100 Hz, chu kỳ 10 ms. Độ sâu dao động có thể từ vài phần trăm (bộ nguồn LED tốt, chấn lưu điện tử tần số cao) tới vài chục phần trăm hoặc hơn. Mắt người không thấy, nhưng camera phơi sáng ngắn thì thấy rõ, vì mỗi ảnh "lấy mẫu" một pha khác nhau của chu kỳ. Mô phỏng với độ sâu điều biến 35%, camera được trigger ngẫu nhiên so với lưới điện:
import numpy as np
F = 100.0 # Hz: điện lưới 50 Hz → cường độ đèn dao động 100 Hz (2 lần mỗi chu kỳ điện)
M = 0.35 # độ sâu điều biến: ánh sáng = trung bình · (1 + M·cos(2π·F·t))
rng = np.random.default_rng(0)
pha = rng.uniform(0, 1 / F, 20000) # camera được trigger ở thời điểm ngẫu nhiên so với lưới điện
def phoi_sang(t0, T, n=200):
"""Năng lượng ánh sáng xưởng thu được trong [t0, t0 + T], chuẩn hóa theo giá trị trung bình."""
t = t0[:, None] + (np.arange(n)[None, :] + 0.5) * T / n # tích phân điểm giữa
return (1 + M * np.cos(2 * np.pi * F * t)).mean(axis=1)
print("Phơi sáng | dao động đỉnh-đỉnh (mô phỏng) | công thức 2M·|sin(πFT)/(πFT)|")
for T in (20e-6, 100e-6, 1e-3, 2.5e-3, 5e-3, 7.5e-3, 10e-3, 15e-3, 20e-3):
e = phoi_sang(pha, T)
x = np.pi * F * T
print(f"{T * 1e3:7.2f} ms | {100 * (e.max() - e.min()):6.1f} %"
f" | {200 * M * abs(np.sin(x) / x):6.1f} %")
# Ảnh thật = đèn trạm + ánh sáng xưởng. Đèn chớp: xung 40 µs cho mức xám 150 (không đổi);
# ánh sáng xưởng cho trung bình 30 mức xám nếu phơi sáng 2 ms (tỉ lệ thuận với thời gian phơi sáng).
print("\nĐèn chớp 150 + ánh sáng xưởng nhấp nháy, theo thời gian phơi sáng của camera:")
for T in (60e-6, 1e-3, 5e-3, 10e-3):
xuong = 30 * (T / 2e-3) * phoi_sang(pha, T)
tong = 150 + xuong
print(f" {T * 1e3:5.2f} ms: ánh sáng xưởng {xuong.mean():5.1f} ± {(xuong.max() - xuong.min()) / 2:4.1f}"
f" mức xám → ảnh dao động {100 * (tong.max() - tong.min()) / tong.mean():4.1f} % đỉnh-đỉnh")
Phơi sáng | dao động đỉnh-đỉnh (mô phỏng) | công thức 2M·|sin(πFT)/(πFT)|
0.02 ms | 70.0 % | 70.0 %
0.10 ms | 70.0 % | 70.0 %
1.00 ms | 68.9 % | 68.9 %
2.50 ms | 63.0 % | 63.0 %
5.00 ms | 44.6 % | 44.6 %
7.50 ms | 21.0 % | 21.0 %
10.00 ms | 0.0 % | 0.0 %
15.00 ms | 14.9 % | 14.9 %
20.00 ms | 0.0 % | 0.0 %
Đèn chớp 150 + ánh sáng xưởng nhấp nháy, theo thời gian phơi sáng của camera:
0.06 ms: ánh sáng xưởng 0.9 ± 0.3 mức xám → ảnh dao động 0.4 % đỉnh-đỉnh
1.00 ms: ánh sáng xưởng 15.0 ± 5.2 mức xám → ảnh dao động 6.3 % đỉnh-đỉnh
5.00 ms: ánh sáng xưởng 75.2 ± 16.7 mức xám → ảnh dao động 14.8 % đỉnh-đỉnh
10.00 ms: ánh sáng xưởng 150.0 ± 0.0 mức xám → ảnh dao động 0.0 % đỉnh-đỉnh
Bảng thứ nhất khớp với công thức giải tích: lấy trung bình cos trên khoảng T cho biên độ còn lại M·|sin(πFT)/(πFT)|. Ba điều rút ra:
- Phơi sáng ngắn (dưới khoảng 1 ms) lấy mẫu "tức thời": độ sáng phần ánh sáng xưởng dao động trọn ±35% giữa các ảnh.
- Phơi sáng đúng bội số của 10 ms (10, 20 ms…) tích phân trọn chu kỳ nên hết dao động. Ở nước dùng điện 60 Hz, con số tương ứng là bội số của 8,33 ms. Đây là chế độ "chống nhấp nháy" (anti-flicker) của webcam và camera an ninh. Nhưng 15 ms (1,5 chu kỳ) vẫn dao động 15%: lệch khỏi bội số là mất tác dụng.
- Trong máy công nghiệp, phơi sáng 10 ms thường không dùng được vì nhòe chuyển động. Bảng thứ hai cho cách tiếp cận đúng: làm cho ánh sáng xưởng nhỏ không đáng kể. Với đèn chớp và phơi sáng 60 µs, ánh sáng xưởng chỉ còn khoảng 1 mức xám, ảnh dao động 0,4% dù đèn trần nhấp nháy mạnh. Phơi sáng 10 ms hết nhấp nháy nhưng ánh sáng xưởng bằng cả đèn trạm (150): nắng chiều vào là ảnh đổi gấp đôi.
3. Ba biện pháp phần cứng và ngân sách ánh sáng
3.1. Che chắn (shroud, hộp che sáng)
Hộp hoặc vách bằng tấm nhựa đen, nhôm sơn đen mờ bao quanh vùng chụp. Rẻ và thường hiệu quả nhất. Khi thiết kế:
- Mặt trong đen mờ (không bóng) để không phản xạ đèn trạm thành chói phụ.
- Chỗ băng tải đi vào/ra: làm khe vừa đủ chiều cao sản phẩm, thêm rèm cao su đen hoặc chổi; kéo dài "đường hầm" ở hai đầu để ánh sáng phải phản xạ nhiều lần mới vào tới vùng chụp.
- Cửa thăm để vệ sinh, chỉnh ống kính; nếu cửa che bộ phận chuyển động thì phải có công tắc liên động an toàn theo yêu cầu của máy.
- Đèn và camera trong hộp kín nóng hơn: chừa đường tản nhiệt, nhất là với đèn chớp công suất lớn.
- Ưu tiên chặn hướng cửa sổ, mái lấy sáng; một tấm che nghiêng đúng hướng nắng chiều đôi khi đủ.
3.2. Kính lọc dải thông
Đèn đơn sắc + kính lọc dải thông đúng bước sóng giữ phần lớn ánh sáng đèn, chặn phần lớn ánh sáng trắng (bài trước: BP625 FWHM 25 nm giữ 76% đèn, chỉ cho lọt khoảng 6–7% nắng và đèn LED trần). Với trạm cạnh cửa sổ, nhớ rằng nắng có nhiều hồng ngoại: đèn IR 850 nm không thoát được nắng.
3.3. Đèn chớp cường độ cao và phơi sáng ngắn
Ánh sáng xưởng rơi vào cảm biến trong toàn bộ thời gian phơi sáng, còn đèn chớp dồn toàn bộ năng lượng vào một xung ngắn. Rút phơi sáng từ 2 ms xuống 60 µs, đồng thời chạy đèn ở chế độ xung (overdrive) để giữ nguyên mức xám của đèn, thì phần ánh sáng xưởng giảm 33 lần. Chi tiết về dòng overdrive, duty cycle, nối dây có ở bài Đèn strobe và đồng bộ trigger camera.
3.4. Ngân sách ánh sáng: tính hiệu quả từng biện pháp
Số đo ví dụ tại một trạm gần cửa sổ: đèn trạm một mình cho mức xám 160 (phơi sáng 2 ms); ánh sáng xưởng một mình cho 30 vào ban đêm và tới 150 vào buổi chiều có nắng. Các biện pháp tác động dạng nhân nên kết hợp chúng rất hiệu quả:
# Số đo tại hiện trường (mức xám, phơi sáng 2 ms, chưa có kính lọc, chưa che):
DEN = 160.0 # chỉ đèn trạm (đèn xưởng tắt, che kín tạm bằng vải đen)
XUONG_MIN, XUONG_MAX = 30.0, 150.0 # chỉ ánh sáng xưởng: ban đêm (đèn trần) / buổi chiều có nắng
LOC_DEN, LOC_XUONG = 0.76, 0.07 # kính lọc BP625 FWHM 25 nm (số liệu bài trước)
CHE = 0.05 # hộp che hở hai đầu băng tải: ánh sáng xưởng còn ~5% (đo thực tế)
def tinh(ten, che=False, loc=False, phoi_sang_us=2000):
"""Đèn chớp được chỉnh để cho cùng mức xám như đèn liên tục ở 2 ms (overdrive, xung ngắn)."""
den = DEN * (LOC_DEN if loc else 1)
k = (CHE if che else 1) * (LOC_XUONG if loc else 1) * phoi_sang_us / 2000
lo, hi = XUONG_MIN * k, XUONG_MAX * k
print(f"{ten:30s} | {den:4.0f} | {lo:7.2f} – {hi:7.2f} | {den / hi:8.1f} | {100 * (hi - lo) / (den + lo):5.1f} %")
print(f"{'Cấu hình':30s} | {'đèn':>4s} | {'xưởng đêm – chiều':>17s} | {'tỉ số':>8s} | ảnh đổi")
tinh("Ban đầu (liên tục 2 ms)")
tinh("Chỉ kính lọc", loc=True)
tinh("Chỉ đèn chớp, phơi sáng 60 µs", phoi_sang_us=60)
tinh("Chỉ hộp che", che=True)
tinh("Kính lọc + đèn chớp", loc=True, phoi_sang_us=60)
tinh("Che + lọc + chớp", che=True, loc=True, phoi_sang_us=60)
Cấu hình | đèn | xưởng đêm – chiều | tỉ số | ảnh đổi
Ban đầu (liên tục 2 ms) | 160 | 30.00 – 150.00 | 1.1 | 63.2 %
Chỉ kính lọc | 122 | 2.10 – 10.50 | 11.6 | 6.8 %
Chỉ đèn chớp, phơi sáng 60 µs | 160 | 0.90 – 4.50 | 35.6 | 2.2 %
Chỉ hộp che | 160 | 1.50 – 7.50 | 21.3 | 3.7 %
Kính lọc + đèn chớp | 122 | 0.06 – 0.32 | 386.0 | 0.2 %
Che + lọc + chớp | 122 | 0.00 – 0.02 | 7720.6 | 0.0 %
- Ban đầu, tỉ số đèn/môi trường xấu nhất chỉ 1,1: ảnh đổi 63% giữa đêm và chiều, không ngưỡng nào sống được.
- Từng biện pháp riêng lẻ đưa mức thay đổi xuống 2–7%; tùy thuật toán, như vậy có thể vẫn chưa đủ (ngưỡng cố định cho vết lỗi mờ thường cần dưới 2–3%).
- Kết hợp kính lọc và đèn chớp đã đủ (0,2%) ngay cả khi không che được; thêm hộp che thì ánh sáng xưởng thực tế bằng 0. Quy tắc kinh nghiệm: thiết kế để tỉ số đèn/môi trường trong trường hợp xấu nhất trên khoảng 50–100, rồi kiểm chứng bằng ảnh tắt đèn trạm.
- Hệ số 5% của hộp che là ví dụ; nó phụ thuộc thiết kế khe hở và phải đo, không đoán.
4. Thuật toán bền vững: tuyến phòng thủ thứ hai
Phần cứng giải quyết phần lớn, nhưng thuật toán nên chịu được dao động còn lại (LED lão hóa, bụi bám). Các cách phổ biến:
- Đo dựa trên cạnh và gradient (vị trí cạnh, khớp mẫu chuẩn hóa) thay vì mức xám tuyệt đối: vị trí cạnh ít phụ thuộc độ sáng chung.
- Ngưỡng thích nghi hoặc Otsu thay vì ngưỡng cố định, khi ảnh có hai lớp rõ ràng.
- Chuẩn hóa theo vùng chuẩn: nhân ảnh với hệ số REF0 / mức xám hiện tại của miếng chuẩn, nhưng chỉ trong giới hạn. Vượt giới hạn là có chuyện bất thường (đèn hỏng, nắng chiếu thẳng), phải báo động chứ không được âm thầm bù.
import numpy as np
import cv2
ROI_CHUAN, REF0 = (5, 5, 30, 30), 180.0 # miếng chuẩn xám: mức xám lúc cài đặt
NGUONG = 100 # ngưỡng cố định chọn lúc cài đặt
def tao_anh(do_sang, seed=0):
"""Nền sáng 200, chi tiết tối 60 (vết lỗi 8×8 mức 112), miếng chuẩn 180; nhân hệ số độ sáng."""
img = np.full((120, 160), 200.0)
img[5:35, 5:35] = 180
cv2.circle(img, (100, 70), 30, 60, -1)
img[60:68, 92:100] = 112 # vết lỗi sáng hơn chi tiết, tối hơn nền
img = img * do_sang + np.random.default_rng(seed).normal(0, 2, img.shape)
return np.clip(img, 0, 255).astype(np.uint8)
def dien_tich_toi(img, chuan_hoa):
g = img.astype(np.float64)
if chuan_hoa:
x, y, w, h = ROI_CHUAN
k = REF0 / g[y:y + h, x:x + w].mean()
if not 0.85 <= k <= 1.15:
return None # lệch > 15%: không bù, báo động
g = g * k
return int((g[40:110, 60:150] < NGUONG).sum())
print("Diện tích đúng = diện tích chi tiết, KHÔNG gồm vết lỗi 64 px")
for ds in (1.0, 0.95, 0.88, 1.12, 0.75):
img = tao_anh(ds)
a, b = dien_tich_toi(img, False), dien_tich_toi(img, True)
print(f"độ sáng ×{ds:4.2f}: ngưỡng cố định {a:5d} px | chuẩn hóa "
+ (f"{b:5d} px" if b is not None else "từ chối, BÁO ĐỘNG"))
Diện tích đúng = diện tích chi tiết, KHÔNG gồm vết lỗi 64 px
độ sáng ×1.00: ngưỡng cố định 2757 px | chuẩn hóa 2757 px
độ sáng ×0.95: ngưỡng cố định 2757 px | chuẩn hóa 2757 px
độ sáng ×0.88: ngưỡng cố định 2810 px | chuẩn hóa 2757 px
độ sáng ×1.12: ngưỡng cố định 2757 px | chuẩn hóa 2757 px
độ sáng ×0.75: ngưỡng cố định 2821 px | chuẩn hóa từ chối, BÁO ĐỘNG
Ở độ sáng ×0,88, vết lỗi mức 112 tụt xuống dưới ngưỡng 100 và bị đếm nhầm vào vùng tối (ngưỡng cố định: 2810 px thay vì 2757): đúng kiểu sai lệch kết quả đo theo giờ. Chuẩn hóa theo miếng chuẩn cho kết quả đúng ở mọi mức trong ±15%, và ở ×0,75 thì từ chối thay vì trả về một con số sai. Chuẩn hóa chỉ bù được thay đổi đều trên cả ảnh; nắng chiếu một góc ảnh thì miếng chuẩn ở góc kia không thấy gì, nên vẫn cần phần cứng.
5. Giám sát độ sáng trong sản xuất
5.1. Miếng chuẩn (reference patch)
- Một miếng vật liệu xám mờ, bền màu (gốm, nhôm anod xám, tấm nhựa kỹ thuật) gắn cố định trên đồ gá hoặc khung, luôn nằm trong FOV và không bị sản phẩm che. Tránh giấy trắng (ố vàng), vật bóng (chói theo góc).
- Mức xám lúc cài đặt nên ở khoảng giữa thang (100–180) để thấy được cả tăng lẫn giảm, không bão hòa.
- Nếu không có chỗ trong FOV: dùng một vùng nền cố định của đồ gá, hoặc chụp ảnh kiểm tra định kỳ khi băng tải trống.
- Lưu giá trị cài đặt (μ0) cùng cấu hình trạm (phơi sáng, dòng đèn, ngày nghiệm thu) để đối chiếu khi thay đèn.
5.2. Ba tầng phản ứng
| Tầng | Điều kiện | Hành động |
|---|---|---|
| Ảnh không tin cậy | Một ảnh lệch quá ngưỡng báo động (ví dụ ±15%) hoặc vùng chuẩn bão hòa | Không trả OK cho ảnh đó; chụp lại hoặc gửi PLC mã "không đánh giá được" để tách sản phẩm ra kiểm tra lại |
| Báo động, dừng trạm | Lệch quá ngưỡng N ảnh liên tiếp (ví dụ 3) | Bật bit báo động cho PLC, dừng trạm, gọi bảo trì |
| Cảnh báo xu hướng | Trung bình trượt EWMA lệch quá ±5% (hoặc ±3σ lúc nghiệm thu nếu lớn hơn) | Hiện cảnh báo vàng trên HMI, ghi log, lên lịch vệ sinh, kiểm tra đèn, kiểm tra che chắn |
EWMA (exponentially weighted moving average, trung bình trượt hàm mũ) cập nhật E ← λ·x + (1 − λ)·E với λ nhỏ (0,05): lọc nhiễu từng ảnh, phản ứng với thay đổi kéo dài, nên bắt được nắng chiều hay đèn yếu dần mà không báo nhầm vì một bóng xe nâng. Mô phỏng 3 ngày sản xuất, mỗi phút một ảnh: bụi và lão hóa làm tối 1,5%/ngày, nắng chiều tăng tới 9%, xe nâng che sáng vài ảnh, và 10:00 ngày thứ ba một dải LED hỏng (−20%):
import numpy as np
rng = np.random.default_rng(3)
# --- Mô phỏng 3 ngày sản xuất, 1 ảnh mỗi phút: mức xám trung bình của vùng chuẩn
phut = np.arange(3 * 24 * 60)
gio = (phut / 60) % 24
ngay = phut // (24 * 60)
y = 150 * (1 - 0.015 * phut / (24 * 60)) # bụi bám + LED lão hóa: −1,5%/ngày
y *= 1 + 0.09 * np.clip(np.cos((gio - 15) / 2.0 * np.pi / 2), 0, None) * ((gio > 13) & (gio < 17)) # nắng chiều
y[[570, 2070, 2071]] *= 0.75 # xe nâng che sáng (1–2 ảnh)
y[(ngay == 2) & (gio >= 10)] *= 0.80 # ngày 3, 10:00: hỏng một dải LED
y = y + rng.normal(0, 0.8, y.size)
# --- Giới hạn từ 60 ảnh lúc nghiệm thu
mu0, s0 = y[:60].mean(), y[:60].std()
canh_bao = max(3 * s0, 0.05 * mu0) # vàng: lệch > 5% (hoặc 3σ nếu lớn hơn)
bao_dong = 0.15 * mu0 # đỏ: lệch > 15% (theo dung sai của thuật toán)
print(f"Nghiệm thu: μ0 = {mu0:.1f}, σ0 = {s0:.2f} → cảnh báo ±{canh_bao:.1f}, báo động ±{bao_dong:.1f}")
ewma, lam_e, lien_tiep, dang_lech = mu0, 0.05, 0, False
for i, v in enumerate(y):
ewma = lam_e * v + (1 - lam_e) * ewma
t = f"ngày {ngay[i] + 1} {int(gio[i]):02d}:{int(phut[i] % 60):02d}"
if abs(v - mu0) > bao_dong:
lien_tiep += 1
if lien_tiep == 1:
print(f"{t}: ảnh lệch {100 * (v - mu0) / mu0:+.0f}% → kết quả ảnh này KHÔNG TIN CẬY, chụp lại")
if lien_tiep == 3:
print(f"{t}: 3 ảnh liên tiếp lệch > 15% → DỪNG TRẠM, gọi bảo trì")
break
else:
lien_tiep = 0
if abs(ewma - mu0) > canh_bao and not dang_lech:
print(f"{t}: xu hướng EWMA {100 * (ewma - mu0) / mu0:+.1f}% → CẢNH BÁO (vàng)")
dang_lech = True
elif abs(ewma - mu0) < 0.8 * canh_bao and dang_lech: # trễ 20% để không bật/tắt liên tục
print(f"{t}: xu hướng EWMA {100 * (ewma - mu0) / mu0:+.1f}% → hết cảnh báo")
dang_lech = False
lech = np.abs(y[:i + 1] - mu0)
for d in range(3):
m = ngay[:i + 1] == d
print(f"Ngày {d + 1}: {m.sum():4d} ảnh, vượt vàng {np.sum(lech[m] > canh_bao):4d}, vượt đỏ {np.sum(lech[m] > bao_dong):3d}")
Nghiệm thu: μ0 = 149.9, σ0 = 0.86 → cảnh báo ±7.5, báo động ±22.5
ngày 1 09:30: ảnh lệch -25% → kết quả ảnh này KHÔNG TIN CẬY, chụp lại
ngày 1 14:16: xu hướng EWMA +5.1% → CẢNH BÁO (vàng)
ngày 1 16:33: xu hướng EWMA +4.0% → hết cảnh báo
ngày 2 10:30: ảnh lệch -27% → kết quả ảnh này KHÔNG TIN CẬY, chụp lại
ngày 2 14:40: xu hướng EWMA +5.0% → CẢNH BÁO (vàng)
ngày 2 16:12: xu hướng EWMA +4.0% → hết cảnh báo
ngày 3 10:00: ảnh lệch -23% → kết quả ảnh này KHÔNG TIN CẬY, chụp lại
ngày 3 10:01: xu hướng EWMA -5.4% → CẢNH BÁO (vàng)
ngày 3 10:02: 3 ảnh liên tiếp lệch > 15% → DỪNG TRẠM, gọi bảo trì
Ngày 1: 1440 ảnh, vượt vàng 132, vượt đỏ 1
Ngày 2: 1440 ảnh, vượt vàng 86, vượt đỏ 2
Ngày 3: 603 ảnh, vượt vàng 4, vượt đỏ 3
- Bóng xe nâng (1–2 ảnh) chỉ làm vài ảnh bị đánh dấu "không tin cậy", không dừng máy.
- Nắng chiều kéo EWMA vượt +5% khoảng 14–16 giờ mỗi ngày: cảnh báo vàng lặp lại đúng giờ là dấu hiệu rõ ràng cần che chắn hướng cửa sổ. Cảnh báo tự tắt khi EWMA về dưới 80% ngưỡng (vùng trễ, hysteresis, để không bật tắt liên tục).
- Khi dải LED hỏng, sau 3 ảnh trạm dừng: không một sản phẩm nào được đánh giá bằng ảnh tối sai, trong khi một trạm không giám sát sẽ âm thầm tăng overkill (hoặc escape) cho tới khi có người phát hiện.
- Đếm số ảnh vượt vàng theo ngày là chỉ số đơn giản để báo cáo tình trạng trạm hàng tuần.
6. Đừng quên các nguồn thay đổi "bên trong"
- LED lão hóa: quang thông giảm dần theo giờ chạy (nhanh hơn khi chạy nóng, dòng cao). Đèn chớp chạy duty thấp lão hóa chậm hơn đèn liên tục. Xu hướng EWMA và log hàng tháng cho biết khi nào cần tăng dòng hoặc thay đèn.
- Nhiệt độ: LED nóng lên thì sáng yếu đi; sau khi bật máy, độ sáng còn trôi trong vài chục phút tới khi ổn định nhiệt. Khởi động trạm, chờ ổn định rồi mới chụp ảnh mẫu chuẩn đầu ca.
- Bụi, dầu trên đèn, kính bảo vệ, ống kính: đưa vệ sinh vào lịch bảo trì định kỳ và kiểm tra lại vùng chuẩn sau khi vệ sinh.
- Bộ điều khiển đèn: dùng nguồn dòng không đổi (constant current) chuyên dụng; nguồn áp rẻ tiền làm độ sáng đổi theo điện áp và nhiệt độ.
7. Sai lầm thường gặp
- Nghiệm thu một buổi rồi về: không chụp ảnh tắt đèn trạm ở các giờ khác nhau, không biết buổi chiều có nắng chiếu vào.
- Phơi sáng dài "cho chắc": phơi sáng dài hơn xung đèn chớp chỉ đón thêm ánh sáng xưởng.
- Giải quyết bằng phần mềm trước: tinh chỉnh ngưỡng hàng tuần cho một vấn đề mà tấm che vài trăm nghìn đồng xử lý dứt điểm. Nguyên tắc: phần cứng trước, thuật toán sau.
- Tự động bù sáng không giới hạn: auto-exposure hoặc chuẩn hóa không giới hạn che giấu đèn hỏng, ảnh tối nhiễu cao, rồi trạm vẫn trả OK.
- Vùng chuẩn đặt trên sản phẩm: màu sản phẩm thay đổi theo lô làm báo động giả.
- Mặt trong hộp che bóng: hộp che thành nguồn chói mới.
8. Bài tập
Bài 1. Viết hàm cảnh báo khi độ sáng trung bình của vùng chuẩn lệch quá 15% so với giá trị lúc cài đặt. Hàm nhận ảnh xám, ROI (x, y, w, h), giá trị cài đặt; trả về trạng thái, mức xám hiện tại và độ lệch. Xử lý thêm trường hợp vùng chuẩn bị bão hòa. Thử với 5 tình huống.
Xem đáp án
import numpy as np
def canh_bao_do_sang(gray, roi, gia_tri_cai_dat, nguong=0.15):
"""Kiểm tra độ sáng trung bình vùng chuẩn so với giá trị lúc cài đặt.
gray: ảnh xám uint8; roi: (x, y, w, h) của vùng chuẩn cố định trong ảnh;
gia_tri_cai_dat: mức xám trung bình của vùng chuẩn khi nghiệm thu.
Trả về (trạng thái, mức xám hiện tại, độ lệch tương đối)."""
x, y, w, h = roi
vung = gray[y:y + h, x:x + w]
if vung.size == 0:
raise ValueError("ROI nằm ngoài ảnh")
m = float(vung.mean())
lech = (m - gia_tri_cai_dat) / gia_tri_cai_dat
if (vung >= 255).mean() > 0.01:
return "CẢNH BÁO: vùng chuẩn bão hòa", m, lech
if abs(lech) > nguong:
return ("CẢNH BÁO: quá sáng" if lech > 0 else "CẢNH BÁO: quá tối"), m, lech
return "OK", m, lech
# Thử với ảnh tổng hợp: miếng chuẩn xám ở góc trái trên, mức cài đặt 128
ROI = (10, 10, 40, 40)
rng = np.random.default_rng(0)
for ten, he_so in (("bình thường", 1.00), ("đèn yếu 10%", 0.90), ("nắng chiếu +18%", 1.18),
("đèn hỏng một nửa", 0.55), ("cháy sáng", 2.20)):
img = np.clip(rng.normal(128 * he_so, 3, (240, 320)), 0, 255).astype(np.uint8)
tt, m, lech = canh_bao_do_sang(img, ROI, gia_tri_cai_dat=128.0)
print(f"{ten:18s}: trung bình {m:6.1f}, lệch {100 * lech:+6.1f}% → {tt}")
bình thường : trung bình 127.5, lệch -0.4% → OK
đèn yếu 10% : trung bình 114.7, lệch -10.4% → OK
nắng chiếu +18% : trung bình 150.5, lệch +17.6% → CẢNH BÁO: quá sáng
đèn hỏng một nửa : trung bình 69.9, lệch -45.4% → CẢNH BÁO: quá tối
cháy sáng : trung bình 255.0, lệch +99.2% → CẢNH BÁO: vùng chuẩn bão hòa
Lưu ý: kiểm tra bão hòa đứng trước kiểm tra độ lệch, vì khi vùng chuẩn bão hòa thì mức xám trung bình không còn phản ánh độ sáng thật (ảnh có thể sáng gấp 3 mà vẫn đọc là 255). Đèn yếu 10% vẫn "OK" theo ngưỡng 15%: đó là việc của cảnh báo xu hướng EWMA ở mục 5. Trong trạm thật, hàm này được gọi cho mọi ảnh trước khi đánh giá sản phẩm, và trạng thái khác "OK" chặn việc gửi kết quả OK cho PLC.
Bài 2. Một trạm chụp sản phẩm đứng yên (băng tải bước) dưới đèn trần nhấp nháy 100 Hz độ sâu 35%, không có đèn trạm riêng, phơi sáng 4 ms. Độ sáng ảnh dao động bao nhiêu? Đổi phơi sáng thành bao nhiêu để hết dao động? Vì sao đó vẫn không phải giải pháp tốt?
Xem đáp án
import math
M, F = 0.35, 100.0
for T in (4e-3, 10e-3, 20e-3):
x = math.pi * F * T
print(f"phơi sáng {T * 1e3:4.1f} ms: dao động đỉnh-đỉnh {200 * M * abs(math.sin(x) / x):5.1f} %")
phơi sáng 4.0 ms: dao động đỉnh-đỉnh 53.0 %
phơi sáng 10.0 ms: dao động đỉnh-đỉnh 0.0 %
phơi sáng 20.0 ms: dao động đỉnh-đỉnh 0.0 %
Phơi sáng 4 ms: dao động khoảng 53% đỉnh-đỉnh giữa các ảnh. Đổi sang 10 ms (hoặc 20 ms) là hết nhấp nháy, vì vật đứng yên nên không lo nhòe. Nhưng trạm vẫn phụ thuộc hoàn toàn vào đèn trần: nắng, đèn bị thay loại khác, một bóng đèn hỏng, người đứng che đều làm ảnh đổi. Giải pháp đúng là đèn trạm riêng, che chắn, kính lọc; khi đó phơi sáng 10 ms chỉ còn là biện pháp phụ.
Bài 3. Dùng bảng ngân sách ánh sáng ở mục 3.4. Thuật toán yêu cầu độ sáng ảnh đổi không quá 2% giữa ban đêm và buổi chiều nắng. Khách hàng không cho làm hộp che vì vướng robot. Phương án nào đạt? Nếu khách đổi ý cho che nhưng không cho thay đèn (không dùng được đèn chớp), có đạt không?
Xem đáp án
Không che: chỉ kính lọc (6,8%) hoặc chỉ đèn chớp (2,2%) đều không đạt; kính lọc + đèn chớp cho 0,2%, đạt với dư địa 10 lần. Có che nhưng không có đèn chớp: che + kính lọc cho ánh sáng xưởng 30–150 × 0,05 × 0,07 = 0,105–0,525 mức xám trên tín hiệu 122, thay đổi khoảng 0,34%, đạt. Hai phương án khác nhau đều đạt vì các biện pháp nhân với nhau; chọn theo chi phí và ràng buộc cơ khí, rồi kiểm chứng bằng ảnh tắt đèn trạm lúc nắng gắt nhất.
Tóm tắt
- Chẩn đoán bằng ảnh tắt đèn trạm ở nhiều giờ khác nhau và log độ sáng vùng chuẩn theo thời gian. Con số trung tâm: tỉ số đèn/môi trường trong trường hợp xấu nhất.
- Điện 50 Hz → đèn nhấp nháy 100 Hz. Dao động còn lại sau phơi sáng T: 2M·|sin(πFT)/(πFT)|; hết khi T là bội số 10 ms, nhưng cách bền vững là làm ánh sáng xưởng nhỏ không đáng kể.
- Che chắn, kính lọc dải thông, đèn chớp + phơi sáng ngắn tác động dạng nhân: kết hợp hai biện pháp thường đưa thay đổi ảnh từ hàng chục phần trăm xuống dưới 1%. Mục tiêu tỉ số đèn/môi trường > 50–100.
- Thuật toán bền vững (cạnh, Otsu, chuẩn hóa theo miếng chuẩn có giới hạn) là tuyến thứ hai, không thay phần cứng.
- Giám sát ba tầng: ảnh không tin cậy (lệch > 15%), dừng trạm (N ảnh liên tiếp), cảnh báo xu hướng (EWMA > 5%). Miếng chuẩn xám mờ, bền màu, luôn trong FOV.
Bài tiếp theo: Bài tập chặng 6: chọn camera, ống kính, đèn cho bài toán thật, nơi bạn ghép toàn bộ chặng 6 thành một cấu hình hoàn chỉnh có bảng vật tư và lý do bằng số.
Bài tập thực hành
- Viết hàm cảnh báo khi độ sáng trung bình vùng chuẩn lệch quá 15% so với giá trị lúc cài đặt.
Làm xong bài tập mới chuyển sang bài tiếp theo: tự viết code là cách duy nhất để nhớ lâu.


