1. 圖像獲取 → 2. 預處理 → 3. ROI 定位 → 4. 特徵提取 → 5. 缺陷分類 → 6. 結果輸出
cv::Mat preprocess(const cv::Mat& src) {
cv::Mat gray, blur, binary;
cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);
// 自適應直方圖均衡化 (CLAHE) 消除光照不均
cv::Ptr clahe = cv::createCLAHE(2.0, cv::Size(8,8));
clahe->apply(gray, gray);
// 高斯模糊去噪
cv::GaussianBlur(gray, blur, cv::Size(5,5), 0);
// 自適應二值化 (OTSU 或局部自適應)
cv::threshold(blur, binary, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);
return binary;
}
cv::Rect locateROI(const cv::Mat& binary) {
std::vector> contours;
cv::findContours(binary, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
double maxArea = 0;
cv::Rect bestRect;
for (auto& cnt : contours) {
double area = cv::contourArea(cnt);
if (area > maxArea && area > 1000) { // 面積閾值濾除噪點
maxArea = area;
bestRect = cv::boundingRect(cnt);
}
}
// 可選:最小外接矩形旋轉校正
// cv::RotatedRect rrect = cv::minAreaRect(cnt);
// cv::Mat rotMat = cv::getRotationMatrix2D(rrect.center, rrect.angle, 1.0);
// cv::warpAffine(src, aligned, rotMat, src.size());
return bestRect;
}
bool detectScratch(const cv::Mat& roiGray, const cv::Mat& roiBinary) {
// 形態學開運算去除雜訊
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3,3));
cv::Mat clean;
cv::morphologyEx(roiBinary, clean, cv::MORPH_OPEN, kernel);
// 連通域分析
std::vector> contours;
cv::findContours(clean, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
for (auto& cnt : contours) {
double area = cv::contourArea(cnt);
cv::Rect r = cv::boundingRect(cnt);
double aspectRatio = (double)r.width / r.height;
// 劃痕特徵:細長、面積小、灰度值低於背景
if (area > 10 && area < 500 && aspectRatio > 5.0) {
// 進一步驗證:灰度均值
cv::Mat mask = cv::Mat::zeros(roiGray.size(), CV_8UC1);
cv::drawContours(mask, std::vector>{cnt}, 0, 255, -1);
cv::Scalar meanVal = cv::mean(roiGray, mask);
if (meanVal[0] < 100) return true; // 判定為劃痕
}
}
return false;
}
bool detectDefect(const cv::Mat& roiBinary, const cv::Rect& expectedRect) {
// 方法一:標準矩形模板匹配
cv::Mat templateImg = cv::Mat::ones(expectedRect.height, expectedRect.width, CV_8UC1) * 255;
cv::Mat diff;
cv::absdiff(roiBinary, templateImg, diff);
// 統計差異像素
int diffPixels = cv::countNonZero(diff);
if (diffPixels > EXPECTED_DEFECT_THRESHOLD) return true;
// 方法二:凸包缺陷檢測
std::vector contour;
cv::findContours(roiBinary, contour, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
std::vector hull;
cv::convexHull(contour[0], hull);
std::vector defects;
cv::convexityDefects(contour[0], hull, defects);
for (auto& d : defects) {
float depth = d[3] / 256.0f; // 缺口深度
if (depth > 10.0f) return true; // 存在明顯缺口
}
return false;
}
enum ClassResult { NORMAL, SCRATCH_ONLY, DEFECT_ONLY, SCRATCH_AND_DEFECT };
ClassResult classify(const cv::Mat& src) {
cv::Mat binary = preprocess(src);
cv::Rect roiRect = locateROI(binary);
cv::Mat roiGray = src(roiRect);
cv::Mat roiBinary = binary(roiRect);
bool hasScratch = detectScratch(roiGray, roiBinary);
bool hasDefect = detectDefect(roiBinary, roiRect);
if (hasScratch && hasDefect) return SCRATCH_AND_DEFECT;
if (hasScratch) return SCRATCH_ONLY;
if (hasDefect) return DEFECT_ONLY;
return NORMAL;
}
// 對話框類成員
cv::Mat m_srcImage; // 當前圖像
CStatic m_picCtrl; // 圖片顯示控件 (IDC_PIC)
// 顯示 OpenCV Mat 到 CStatic
void ShowMatInStatic(const cv::Mat& img, CStatic& ctrl) {
if (img.empty()) return;
cv::Mat rgb;
if (img.channels() == 1) cv::cvtColor(img, rgb, cv::COLOR_GRAY2BGR);
else if (img.channels() == 3) cv::cvtColor(img, rgb, cv::COLOR_BGR2RGB);
else return;
CRect rect;
ctrl.GetClientRect(&rect);
cv::Mat display;
cv::resize(rgb, display, cv::Size(rect.Width(), rect.Height()));
CClientDC dc(&ctrl);
BITMAPINFO bmi = {0};
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = display.cols;
bmi.bmiHeader.biHeight = -display.rows; // 自上而下
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 24;
bmi.bmiHeader.biCompression = BI_RGB;
SetDIBitsToDevice(dc.GetSafeHdc(), 0, 0, display.cols, display.rows,
0, 0, 0, display.rows, display.data, &bmi, DIB_RGB_COLORS);
}
// 按鈕點擊處理
void OnBnClickedBtnDetect() {
CFileDialog dlg(TRUE, _T("bmp"), NULL, OFN_FILEMUSTEXIST, _T("圖片|*.bmp;*.jpg;*.png||"));
if (dlg.DoModal() == IDOK) {
m_srcImage = cv::imread(CT2A(dlg.GetPathName()));
if (!m_srcImage.empty()) {
ShowMatInStatic(m_srcImage, m_picCtrl);
ClassResult result = classify(m_srcImage);
CString msg;
switch(result) {
case NORMAL: msg = _T("正常"); break;
case SCRATCH_ONLY: msg = _T("有劃痕"); break;
case DEFECT_ONLY: msg = _T("有殘缺"); break;
case SCRATCH_AND_DEFECT: msg = _T("劃痕+殘缺"); break;
}
AfxMessageBox(_T("檢測結果: ") + msg);
}
}
}
結論:基於 OpenCV 的傳統圖像處理 (形態學、輪廓分析、模板匹配) 可快速實現石英片劃痕/殘缺分類,**MFC + OpenCV 是經典可行組合**。量產項目建議引入深度學習提升魯棒性。