WinForms hiển thị camera và kết quả OK/NG với OpenCvSharp
Xây giao diện vận hành: vòng lặp camera trên luồng nền, cập nhật giao diện an toàn, giải phóng Bitmap tránh rò rỉ bộ nhớ.
Nội dung bài viết
Một trạm kiểm tra cần giao diện cho người vận hành: thấy hình camera trực tiếp, kết quả OK/NG to rõ, bộ đếm và nút điều khiển. Bài này xây ứng dụng WinForms đơn giản hiển thị camera bằng OpenCvSharp mà không làm treo giao diện.
Tạo dự án
dotnet new winforms -n TramVision
cd TramVision
dotnet add package OpenCvSharp4
dotnet add package OpenCvSharp4.runtime.win
dotnet add package OpenCvSharp4.Extensions
Gói Extensions cung cấp hàm chuyển Mat sang Bitmap để hiển thị trong PictureBox.
Giao diện
PictureBox picCameravớiSizeMode = Zoom.Label lblKetQuachữ lớn, nền xanh/đỏ.Label lblDemhiển thị số OK/NG.- Nút
btnStart,btnStop.
Vòng lặp camera trên luồng nền
using OpenCvSharp;
using OpenCvSharp.Extensions;
public partial class Form1 : Form
{
private CancellationTokenSource? _cts;
private int _ok, _ng;
private async void btnStart_Click(object sender, EventArgs e)
{
_cts = new CancellationTokenSource();
btnStart.Enabled = false;
await Task.Run(() => CameraLoop(_cts.Token));
btnStart.Enabled = true;
}
private void btnStop_Click(object sender, EventArgs e) => _cts?.Cancel();
private void CameraLoop(CancellationToken token)
{
using var cap = new VideoCapture(0);
using var frame = new Mat();
using var gray = new Mat();
using var bin = new Mat();
while (!token.IsCancellationRequested && cap.Read(frame) && !frame.Empty())
{
Cv2.CvtColor(frame, gray, ColorConversionCodes.BGR2GRAY);
Cv2.Threshold(gray, bin, 0, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu);
double tiLeSang = Cv2.CountNonZero(bin) * 100.0 / bin.Total();
bool ok = tiLeSang > 20; // luật kiểm tra ví dụ
var bmp = frame.ToBitmap(); // tạo Bitmap mới cho mỗi khung
BeginInvoke(() => CapNhatGiaoDien(bmp, ok, tiLeSang));
}
}
private void CapNhatGiaoDien(Bitmap bmp, bool ok, double tiLe)
{
var cu = picCamera.Image;
picCamera.Image = bmp;
cu?.Dispose(); // giải phóng ảnh cũ, tránh rò rỉ GDI
if (ok) _ok++; else _ng++;
lblKetQua.Text = ok ? "OK" : "NG";
lblKetQua.BackColor = ok ? Color.SeaGreen : Color.Firebrick;
lblDem.Text = $"OK: {_ok} NG: {_ng} ({tiLe:F1}%)";
}
}
Những điểm quan trọng
- Không truy cập control từ luồng nền. Mọi cập nhật giao diện phải qua
BeginInvoke(hoặcInvoke). - Giải phóng Bitmap cũ mỗi khung hình, nếu không bộ nhớ GDI tăng dần đến khi ứng dụng sập.
- Dừng sạch: dùng
CancellationToken, giải phóng camera khi đóng form (xử lý sự kiệnFormClosing). - Giao diện chỉ cần cập nhật khoảng 15–30 lần/giây; nếu camera nhanh hơn, có thể bỏ bớt khung hiển thị nhưng vẫn kiểm tra đầy đủ.
Nâng cấp cho môi trường nhà máy
- Thay
VideoCapturebằng SDK camera công nghiệp, chụp theo trigger phần cứng. - Thêm màn hình cài đặt tham số có mật khẩu, lưu cấu hình ra file JSON.
- Lưu ảnh NG kèm thời gian vào thư mục theo ngày.
- Gửi kết quả cho PLC, xem Modbus TCP từ C#.


