认真学习这些ppt的内容,后续我需要你帮我完成一项图像识别分类任务的机器学习算法实现,学习完毕后回复...
Créé le : 25 décembre 2025
Répondu en utilisant GPT-5.2 Thinking par Chat01
Créé le : 25 décembre 2025
Répondu en utilisant GPT-5.2 Thinking par Chat01
认真学习这些ppt的内容,后续我需要你帮我完成一项图像识别分类任务的机器学习算法实现,学习完毕后回复我已经学习完毕。认真学习这些ppt的内容,后续我需要你帮我完成一项图像识别分类任务的机器学习算法实现,学习完毕后回复我已经学习完毕。认真学习这些ppt的内容,后续我需要你帮我完成一项图像识别分类任务的机器学习算法实现,学习完毕后回复我已经学习完毕。
pythonimport os, re, math, textwrap, json, statistics paths = ["/mnt/data/1Digital Image Processing_ Introduction.pdf", "/mnt/data/2Digital Image Processing_ Digital Imaging Fundamentals.pdf", "/mnt/data/3Digital Image Processing.pdf", "/mnt/data/4Digital Image Processing.pdf", "/mnt/data/5Digital Image Processing.pdf", "/mnt/data/6Digital Image Processing.pdf", "/mnt/data/7Digital Image Processing.pdf", "/mnt/data/8Digital Image Processing.pdf"] [(os.path.basename(p), os.path.getsize(p)) for p in paths] [('1Digital Image Processing_ Introduction.pdf', 7291351), ('2Digital Image Processing_ Digital Imaging Fundamentals.pdf', 8556497), ('3Digital Image Processing.pdf', 5845283), ('4Digital Image Processing.pdf', 7800120), ('5Digital Image Processing.pdf', 5709009), ('6Digital Image Processing.pdf', 5135566), ('7Digital Image Processing.pdf', 11128817), ('8Digital Image Processing.pdf', 8982587)]
pythonimport fitz, PIL.Image, numpy as np, io, matplotlib.pyplot as plt, pandas as pd
pythondef first_page_text(path, max_chars=800): doc = fitz.open(path) page = doc[0] txt = page.get_text("text") doc.close() # normalize whitespace txt = re.sub(r'\s+', ' ', txt).strip() return txt[:max_chars] for p in paths: print(os.path.basename(p), ":", first_page_text(p, 200))
pythonshutil.which("tesseract"), pytesseract.get_tesseract_version() if shutil.which("tesseract") else None ('/usr/bin/tesseract', LooseVersion ('5.3.0 leptonica-1.82.0 libgif 5.2.1 : libjpeg 6b (libjpeg-turbo 2.1.2) : libpng 1.6.39 : libtiff 4.5.0 : zlib 1.2.13 : libwebp 1.2.4 : libopenjp2 2.5.0 Found AVX512BW Found AVX512F Found AVX512VNNI Found AVX2 Found AVX Found FMA Found SSE4.1 Found OpenMP 201511 Found libarchive 3.6.2 zlib/1.2.13 liblzma/5.4.1 bz2lib/1.0.8 liblz4/1.9.4 libzstd/1.5.4 Found libcurl/7.88.1 OpenSSL/3.0.17 zlib/1.2.13 brotli/1.0.9 zstd/1.5.4 libidn2/2.3.3 libpsl/0.21.2 (+libidn2/2.3.3) libssh2/1.10.0 nghttp2/1.52.0 librtmp/2.3 OpenLDAP/2.5.13 '))
pythondef ocr_page_title(path, page_no=0, dpi=200): doc = fitz.open(path) page = doc[page_no] mat = fitz.Matrix(dpi/72, dpi/72) pix = page.get_pixmap(matrix=mat, alpha=False) img = PIL.Image.frombytes("RGB", [pix.width, pix.height], pix.samples) doc.close() # crop top area? maybe whole. # Convert to grayscale and increase contrast gray = img.convert("L") # simple threshold arr = np.array(gray) thr = (arr > 200).astype(np.uint8)*255 proc = PIL.Image.fromarray(thr) text = pytesseract.image_to_string(proc, lang="eng") text = re.sub(r'\s+', ' ', text).strip() return text for p in paths[2:]: print(os.path.basename(p), "->", ocr_page_title(p)[:120])
pythonfor p in paths[2:]: text = ocr_page_title(p) print("\n", os.path.basename(p)) print(text)
我已经学习完毕(已通读你上传的 8 份《Digital Image Processing》课件)。(1Digital Image Processing_ Introduction.pdf) (2Digital Image Processing_ Digital Imaging Fundamentals.pdf) (3Digital Image Processing.pdf) (4Digital Image Processing.pdf) (5Digital Image Processing.pdf) (6Digital Image Processing.pdf) (7Digital Image Processing.pdf) (8Digital Image Processing.pdf)
根据你学习到的内容,我的训练数据集位置在"F:\手机屏幕表面缺陷:油污、划痕和斑点"其中有三类图像,第一类为Oil_开头,它就是手机屏幕表面有油污的数据集合,依次为Oil_0001.jpg一直到Oil_0033.jpg,第二类Scr_开头的是手机屏幕表面有划痕的数据集,依次为Scr_0001.jpg一直到Scr_0033.jpg,第三类Sta_开头的是手机屏幕表面有斑点的数据集,依次为Sta_0001.jpg一直到Sta_0033.jpg,手机屏幕表面缺陷:油污、划痕和斑点,共计100张图片。要求使用数字图像处理课程相关内容,设计图像识别程序,识别3种缺陷,准确实现3种缺陷的识别分类,使用python语言进行编程,给我完整的python代码,注意不能使用深度学习类算法。根据你学习到的内容,我的训练数据集位置在"F:\手机屏幕表面缺陷:油污、划痕和斑点"其中有三类图像,第一类为Oil_开头,它就是手机屏幕表面有油污的数据集合,依次为Oil_0001.jpg一直到Oil_0033.jpg,第二类Scr_开头的是手机屏幕表面有划痕的数据集,依次为Scr_0001.jpg一直到Scr_0033.jpg,第三类Sta_开头的是手机屏幕表面有斑点的数据集,依次为Sta_0001.jpg一直到Sta_0033.jpg,手机屏幕表面缺陷:油污、划痕和斑点,共计100张图片。要求使用数字图像处理课程相关内容,设计图像识别程序,识别3种缺陷,准确实现3种缺陷的识别分类,使用python语言进行编程,给我完整的python代码,注意不能使用深度学习类算法。根据你学习到的内容,我的训练数据集位置在"F:\手机屏幕表面缺陷:油污、划痕和斑点"其中有三类图像,第一类为Oil_开头,它就是手机屏幕表面有油污的数据集合,依次为Oil_0001.jpg一直到Oil_0033.jpg,第二类Scr_开头的是手机屏幕表面有划痕的数据集,依次为Scr_0001.jpg一直到Scr_0033.jpg,第三类Sta_开头的是手机屏幕表面有斑点的数据集,依次为Sta_0001.jpg一直到Sta_0033.jpg,手机屏幕表面缺陷:油污、划痕和斑点,共计100张图片。要求使用数字图像处理课程相关内容,设计图像识别程序,识别3种缺陷,准确实现3种缺陷的识别分类,使用python语言进行编程,给我完整的python代码,注意不能使用深度学习类算法。
我已经学习完毕。下面给你一套**“数字图像处理特征 + 传统机器学习(非深度学习)”的三分类完整实现:利用灰度直方图特征**(反映灰度分布)(3Digital Image Processing.pdf)、空间滤波平滑去噪/突出结构(5Digital Image Processing.pdf),再结合划痕线段特征(Hough)与斑点连通域特征(形态学+连通域),训练一个 SVM 分类器来识别 Oil / Scr / Sta。
说明:你没有提供图片样例,所以我无法在这里保证“必然100%准确”,但这套特征是针对“油污(低频大面积变化)/划痕(细长线性边缘)/斑点(小块/小圆斑连通域)”设计的,实际项目中通常只需少量调参就能达到很高准确率。
python# defect_classifier.py # 传统图像处理 + 机器学习(非深度学习)三分类:Oil / Scr / Sta # Author: ChatGPT # Python >= 3.8 import os import glob import argparse from dataclasses import dataclass from typing import List, Tuple, Dict import numpy as np import cv2 from sklearn.model_selection import train_test_split, StratifiedKFold, GridSearchCV from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.svm import SVC from sklearn.metrics import classification_report, confusion_matrix, accuracy_score import joblib # 可选:用 skimage 提供更稳的 LBP/HOG # 若你环境没有,可 pip install scikit-image try: from skimage.feature import local_binary_pattern, hog SKIMAGE_OK = True except Exception: SKIMAGE_OK = False # ---------------------------- # 1) 兼容中文路径读取 # ---------------------------- def imread_unicode(path: str, flags=cv2.IMREAD_COLOR): """ Windows 下 cv2.imread 对含中文路径可能失败,用 imdecode 规避。 """ data = np.fromfile(path, dtype=np.uint8) img = cv2.imdecode(data, flags) return img # ---------------------------- # 2) 特征提取 # ---------------------------- @dataclass class FeatureConfig: resize: Tuple[int, int] = (256, 256) hist_bins: int = 32 # 与课程中常见“32灰度级直方图”一致思路 lbp_P1: int = 8 lbp_R1: int = 1 lbp_P2: int = 16 lbp_R2: int = 2 hog_orientations: int = 9 hog_pixels_per_cell: int = 16 hog_cells_per_block: int = 2 def normalize_gray(gray: np.ndarray) -> np.ndarray: """ 轻量对比度归一:CLAHE(非深度学习,属于传统增强) """ clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) return clahe.apply(gray) def gray_hist_feature(gray: np.ndarray, bins: int) -> np.ndarray: hist = cv2.calcHist([gray], [0], None, [bins], [0, 256]).reshape(-1) hist = hist / (hist.sum() + 1e-12) return hist.astype(np.float32) def intensity_stats(gray: np.ndarray) -> np.ndarray: x = gray.astype(np.float32).ravel() mu = float(x.mean()) std = float(x.std() + 1e-12) z = (x - mu) / std skew = float(np.mean(z**3)) kurt = float(np.mean(z**4) - 3.0) p10, p50, p90 = np.percentile(x, [10, 50, 90]).astype(np.float32) return np.array([mu, std, skew, kurt, p10, p50, p90], dtype=np.float32) def lbp_hist(gray: np.ndarray, P: int, R: int) -> np.ndarray: """ LBP 纹理:油污/斑点/划痕在纹理统计上差异明显 """ if not SKIMAGE_OK: # 没有 skimage 则退化为简单纹理统计(灰度共生矩阵等也可,但这里保持依赖最小) # 用梯度能量替代一部分纹理信息 gx = cv2.Sobel(gray, cv2.CV_32F, 1, 0, ksize=3) gy = cv2.Sobel(gray, cv2.CV_32F, 0, 1, ksize=3) mag = cv2.magnitude(gx, gy) return np.array([mag.mean(), mag.std(), np.percentile(mag, 90)], dtype=np.float32) lbp = local_binary_pattern(gray, P=P, R=R, method="uniform") n_bins = P + 2 # uniform 模式下 bins = P+2 hist, _ = np.histogram(lbp.ravel(), bins=np.arange(0, n_bins + 1), range=(0, n_bins)) hist = hist.astype(np.float32) hist /= (hist.sum() + 1e-12) return hist def edge_orientation_feature(gray: np.ndarray, bins: int = 9) -> np.ndarray: gx = cv2.Sobel(gray, cv2.CV_32F, 1, 0, ksize=3) gy = cv2.Sobel(gray, cv2.CV_32F, 0, 1, ksize=3) mag = cv2.magnitude(gx, gy) ang = cv2.phase(gx, gy, angleInDegrees=True) # 0..360 # 只取 0..180(方向无符号) ang = np.mod(ang, 180.0) hist, _ = np.histogram(ang.ravel(), bins=bins, range=(0, 180), weights=mag.ravel()) hist = hist.astype(np.float32) hist /= (hist.sum() + 1e-12) # 额外给几个边缘强度统计 return np.concatenate([ hist, np.array([mag.mean(), mag.std(), np.percentile(mag, 90)], dtype=np.float32) ]) def scratch_line_feature(gray: np.ndarray) -> np.ndarray: """ 划痕:通常呈细长线,边缘明显 => Canny + HoughLinesP """ blur = cv2.GaussianBlur(gray, (5, 5), 0) edges = cv2.Canny(blur, 50, 150) lines = cv2.HoughLinesP( edges, rho=1, theta=np.pi / 180, threshold=60, minLineLength=30, maxLineGap=5 ) if lines is None: return np.array([0, 0.0, 0.0, 0.0], dtype=np.float32) lengths = [] angles = [] for x1, y1, x2, y2 in lines[:, 0]: dx = x2 - x1 dy = y2 - y1 lengths.append(np.hypot(dx, dy)) angles.append(np.degrees(np.arctan2(dy, dx))) lengths = np.array(lengths, dtype=np.float32) angles = np.mod(np.array(angles, dtype=np.float32), 180.0) # 线段数量、平均长度、最大长度、方向集中度(方向直方图峰值占比) hist, _ = np.histogram(angles, bins=9, range=(0, 180)) hist = hist.astype(np.float32) hist /= (hist.sum() + 1e-12) peak = float(hist.max()) return np.array([len(lengths), float(lengths.mean()), float(lengths.max()), peak], dtype=np.float32) def spot_blob_feature(gray: np.ndarray) -> np.ndarray: """ 斑点:小块/小圆斑 => 形态学顶帽/黑帽增强 + 阈值 + 连通域统计 """ # 形态学核 k = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (15, 15)) # 顶帽:突出“比背景更亮的小结构”;黑帽:突出“比背景更暗的小结构” tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, k) blackhat = cv2.morphologyEx(gray, cv2.MORPH_BLACKHAT, k) enh = cv2.max(tophat, blackhat) enh = cv2.GaussianBlur(enh, (5, 5), 0) # Otsu _, bw = cv2.threshold(enh, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) # 去掉小噪点 bw = cv2.morphologyEx(bw, cv2.MORPH_OPEN, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))) num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(bw, connectivity=8) # stats: [label, x, y, w, h, area] 其中 area=stats[:, cv2.CC_STAT_AREA] if num_labels <= 1: return np.array([0, 0.0, 0.0, 0.0], dtype=np.float32) areas = stats[1:, cv2.CC_STAT_AREA].astype(np.float32) # 去掉背景 # 过滤极小区域(可调) areas = areas[areas >= 10.0] if areas.size == 0: return np.array([0, 0.0, 0.0, 0.0], dtype=np.float32) H, W = gray.shape[:2] area_ratio = float(areas.sum() / (H * W)) return np.array([len(areas), float(areas.mean()), float(areas.max()), area_ratio], dtype=np.float32) def hog_feature(gray: np.ndarray, cfg: FeatureConfig) -> np.ndarray: if not SKIMAGE_OK: # 没 skimage 就用简化梯度统计替代 gx = cv2.Sobel(gray, cv2.CV_32F, 1, 0, ksize=3) gy = cv2.Sobel(gray, cv2.CV_32F, 0, 1, ksize=3) mag = cv2.magnitude(gx, gy) return np.array([mag.mean(), mag.std(), np.percentile(mag, 95)], dtype=np.float32) feat = hog( gray, orientations=cfg.hog_orientations, pixels_per_cell=(cfg.hog_pixels_per_cell, cfg.hog_pixels_per_cell), cells_per_block=(cfg.hog_cells_per_block, cfg.hog_cells_per_block), block_norm="L2-Hys", transform_sqrt=True, feature_vector=True ) return feat.astype(np.float32) def extract_features(img_bgr: np.ndarray, cfg: FeatureConfig) -> np.ndarray: if img_bgr is None: raise ValueError("图像读取失败(img is None)。请检查路径/文件是否存在。") img = cv2.resize(img_bgr, cfg.resize, interpolation=cv2.INTER_AREA) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 先做轻量平滑,符合“空间滤波去噪”的思想 gray = cv2.medianBlur(gray, 3) gray = normalize_gray(gray) feats = [] feats.append(gray_hist_feature(gray, cfg.hist_bins)) feats.append(intensity_stats(gray)) feats.append(edge_orientation_feature(gray, bins=9)) feats.append(scratch_line_feature(gray)) feats.append(spot_blob_feature(gray)) feats.append(lbp_hist(gray, cfg.lbp_P1, cfg.lbp_R1)) feats.append(lbp_hist(gray, cfg.lbp_P2, cfg.lbp_R2)) feats.append(hog_feature(gray, cfg)) return np.concatenate(feats).astype(np.float32) # ---------------------------- # 3) 数据集加载(按前缀打标签) # ---------------------------- LABEL_MAP = { "Oil": 0, "Scr": 1, "Sta": 2 } INV_LABEL_MAP = {v: k for k, v in LABEL_MAP.items()} def infer_label_from_name(filename: str) -> int: base = os.path.basename(filename) for prefix, y in LABEL_MAP.items(): if base.startswith(prefix + "_"): return y raise ValueError(f"无法从文件名推断类别:{filename}(需以 Oil_/Scr_/Sta_ 开头)") def load_dataset(data_dir: str, cfg: FeatureConfig) -> Tuple[np.ndarray, np.ndarray, List[str]]: patterns = ["*.jpg", "*.jpeg", "*.png", "*.bmp"] files = [] for p in patterns: files.extend(glob.glob(os.path.join(data_dir, p))) files = sorted(files) if len(files) == 0: raise FileNotFoundError(f"在目录中未找到图片:{data_dir}") X_list, y_list, paths = [], [], [] for fp in files: try: y = infer_label_from_name(fp) img = imread_unicode(fp, cv2.IMREAD_COLOR) feat = extract_features(img, cfg) X_list.append(feat) y_list.append(y) paths.append(fp) except Exception as e: print(f"[WARN] 跳过文件 {fp},原因:{e}") X = np.vstack(X_list).astype(np.float32) y = np.array(y_list, dtype=np.int64) return X, y, paths # ---------------------------- # 4) 训练 + 评估 + 保存 # ---------------------------- def train_model(X: np.ndarray, y: np.ndarray, seed: int = 42) -> GridSearchCV: pipe = Pipeline([ ("scaler", StandardScaler()), ("clf", SVC(kernel="rbf", probability=True, class_weight="balanced")) ]) # 网格搜索:小数据集用轻量 grid 即可 param_grid = { "clf__C": [0.1, 1, 10, 50, 100], "clf__gamma": ["scale", 0.01, 0.05, 0.1, 0.2] } cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=seed) gs = GridSearchCV( pipe, param_grid=param_grid, cv=cv, scoring="accuracy", n_jobs=-1, verbose=1 ) gs.fit(X, y) return gs def evaluate_holdout(best_estimator, X: np.ndarray, y: np.ndarray, seed: int = 42): Xtr, Xte, ytr, yte = train_test_split( X, y, test_size=0.25, random_state=seed, stratify=y ) best_estimator.fit(Xtr, ytr) pred = best_estimator.predict(Xte) print("\n===== Holdout Test =====") print("Accuracy:", accuracy_score(yte, pred)) print("\nClassification report:") print(classification_report(yte, pred, target_names=[INV_LABEL_MAP[i] for i in range(3)])) print("Confusion matrix:\n", confusion_matrix(yte, pred)) # ---------------------------- # 5) 预测 # ---------------------------- def predict_one(model, image_path: str, cfg: FeatureConfig): img = imread_unicode(image_path, cv2.IMREAD_COLOR) feat = extract_features(img, cfg).reshape(1, -1) pred = int(model.predict(feat)[0]) proba = model.predict_proba(feat)[0] if hasattr(model, "predict_proba") else None print(f"Image: {image_path}") print(f"Predicted: {INV_LABEL_MAP[pred]}") if proba is not None: for i in range(3): print(f" P({INV_LABEL_MAP[i]}) = {proba[i]:.4f}") # ---------------------------- # 6) CLI # ---------------------------- def main(): parser = argparse.ArgumentParser(description="Phone screen defect classifier (Oil/Scr/Sta) without deep learning.") sub = parser.add_subparsers(dest="mode", required=True) p_train = sub.add_parser("train", help="train a model and save it") p_train.add_argument("--data_dir", type=str, required=True, help=r'例如:F:\手机屏幕表面缺陷:油污、划痕和斑点') p_train.add_argument("--model_out", type=str, default="defect_svm.joblib") p_train.add_argument("--seed", type=int, default=42) p_pred = sub.add_parser("predict", help="predict one image using a saved model") p_pred.add_argument("--model", type=str, required=True) p_pred.add_argument("--image", type=str, required=True) args = parser.parse_args() cfg = FeatureConfig() if args.mode == "train": X, y, paths = load_dataset(args.data_dir, cfg) print(f"Loaded samples: {len(y)} | feature_dim={X.shape[1]}") print("Class counts:", {INV_LABEL_MAP[i]: int(np.sum(y == i)) for i in range(3)}) gs = train_model(X, y, seed=args.seed) print("\nBest params:", gs.best_params_) print("Best CV accuracy:", gs.best_score_) # 额外做一次留出法评估(便于你看到大致效果) evaluate_holdout(gs.best_estimator_, X, y, seed=args.seed) joblib.dump({"model": gs.best_estimator_, "cfg": cfg}, args.model_out) print(f"\nSaved model to: {args.model_out}") elif args.mode == "predict": pack = joblib.load(args.model) model = pack["model"] cfg = pack.get("cfg", FeatureConfig()) predict_one(model, args.image, cfg) if __name__ == "__main__": main()
bashpip install opencv-python numpy scikit-learn joblib scikit-image
bashpython defect_classifier.py train --data_dir r"F:\手机屏幕表面缺陷:油污、划痕和斑点" --model_out defect_svm.joblib
bashpython defect_classifier.py predict --model defect_svm.joblib --image r"F:\手机屏幕表面缺陷:油污、划痕和斑点\Oil_0001.jpg"
如果你愿意把其中每一类各发 1~2 张样例图(Oil/Scr/Sta 各几张),我可以基于真实外观把 阈值、形态学核大小、Hough 参数、特征权重再针对性调一下,让准确率更稳。
这是我提供给你的样例照片,请你重新认真思考,给我完整的代码。根据你学习到的内容,我的训练数据集位置在"F:\手机屏幕表面缺陷:油污、划痕和斑点"其中有三类图像,第一类为Oil_开头,它就是手机屏幕表面有油污的数据集合,依次为Oil_0001.jpg一直到Oil_0033.jpg,第二类Scr_开头的是手机屏幕表面有划痕的数据集,依次为Scr_0001.jpg一直到Scr_0033.jpg,第三类Sta_开头的是手机屏幕表面有斑点的数据集,依次为Sta_0001.jpg一直到Sta_0033.jpg,手机屏幕表面缺陷:油污、划痕和斑点,共计100张图片。要求使用数字图像处理课程相关内容,设计图像识别程序,识别3种缺陷,准确实现3种缺陷的识别分类,使用python语言进行编程,给我完整的python代码,注意不能使用深度学习类算法。这是我提供给你的样例照片,请你重新认真思考,给我完整的代码。根据你学习到的内容,我的训练数据集位置在"F:\手机屏幕表面缺陷:油污、划痕和斑点"其中有三类图像,第一类为Oil_开头,它就是手机屏幕表面有油污的数据集合,依次为Oil_0001.jpg一直到Oil_0033.jpg,第二类Scr_开头的是手机屏幕表面有划痕的数据集,依次为Scr_0001.jpg一直到Scr_0033.jpg,第三类Sta_开头的是手机屏幕表面有斑点的数据集,依次为Sta_0001.jpg一直到Sta_0033.jpg,手机屏幕表面缺陷:油污、划痕和斑点,共计100张图片。要求使用数字图像处理课程相关内容,设计图像识别程序,识别3种缺陷,准确实现3种缺陷的识别分类,使用python语言进行编程,给我完整的python代码,注意不能使用深度学习类算法。
这是我提供给你的一小部分样例照片,请你重新认真思考,给我完整的各个部分的代码。确保根据你学习到的内容,我的训练数据集位置在"F:\手机屏幕表面缺陷:油污、划痕和斑点"其中有三类图像,第一类为Oil_开头,它就是手机屏幕表面有油污的数据集合,依次为Oil_0001.jpg一直到Oil_0033.jpg,第二类Scr_开头的是手机屏幕表面有划痕的数据集,依次为Scr_0001.jpg一直到Scr_0033.jpg,第三类Sta_开头的是手机屏幕表面有斑点的数据集,依次为Sta_0001.jpg一直到Sta_0033.jpg,手机屏幕表面缺陷:油污、划痕和斑点,共计100张图片。要求使用数字图像处理课程相关内容,设计图像识别程序,识别3种缺陷,准确实现3种缺陷的识别分类,使用python语言进行编程,给我完整的python代码,注意不能使用深度学习类算法。这是我提供给你的样例照片,请你重新认真思考,给我完整的代码。根据你学习到的内容,我的训练数据集位置在"F:\手机屏幕表面缺陷:油污、划痕和斑点"其中有三类图像,第一类为Oil_开头,它就是手机屏幕表面有油污的数据集合,依次为Oil_0001.jpg一直到Oil_0033.jpg,第二类Scr_开头的是手机屏幕表面有划痕的数据集,依次为Scr_0001.jpg一直到Scr_0033.jpg,第三类Sta_开头的是手机屏幕表面有斑点的数据集,依次为Sta_0001.jpg一直到Sta_0033.jpg,手机屏幕表面缺陷:油污、划痕和斑点,共计100张图片。要求使用数字图像处理课程相关内容,设计图像识别程序,识别3种缺陷,准确实现3种缺陷的识别分类,使用python语言进行编程,给我完整的python代码,注意不能使用深度学习类算法。注意不能使用深度学习类算法。注意不能使用深度学习类算法。
这是我提供给你的一小部分样例照片,请你重新认真思考,给我完整的各个部分的代码。确保根据你学习到的内容,我的训练数据集位置在"F:\test\pythonProjectPhone screen\data"其中有三类图像,第一类为Oil_开头,它就是手机屏幕表面有油污的数据集合,依次为Oil_0001.jpg一直到Oil_0033.jpg,第二类Scr_开头的是手机屏幕表面有划痕的数据集,依次为Scr_0001.jpg一直到Scr_0033.jpg,第三类Sta_开头的是手机屏幕表面有斑点的数据集,依次为Sta_0001.jpg一直到Sta_0033.jpg,手机屏幕表面缺陷:油污、划痕和斑点,共计100张图片。要求使用数字图像处理课程相关内容,设计图像识别程序,识别3种缺陷,准确实现3种缺陷的识别分类,使用python语言进行编程,给我完整的python代码,注意不能使用深度学习类算法。这是我提供给你的一小部分样例照片,请你重新认真思考,给我完整的各个部分的代码。确保根据你学习到的内容,我的训练数据集位置在"F:\test\pythonProjectPhone screen\data"其中有三类图像,第一类为Oil_开头,它就是手机屏幕表面有油污的数据集合,依次为Oil_0001.jpg一直到Oil_0033.jpg,第二类Scr_开头的是手机屏幕表面有划痕的数据集,依次为Scr_0001.jpg一直到Scr_0033.jpg,第三类Sta_开头的是手机屏幕表面有斑点的数据集,依次为Sta_0001.jpg一直到Sta_0033.jpg,手机屏幕表面缺陷:油污、划痕和斑点,共计100张图片。要求使用数字图像处理课程相关内容,设计图像识别程序,识别3种缺陷,准确实现3种缺陷的识别分类,使用python语言进行编程,给我完整的python代码,注意不能使用深度学习类算法。
你其的全部给我改错了!!!斑点是黑色屏幕上的白斑点,你认真思考重新给出正确的代码。你其的全部给我改错了!!!斑点是黑色屏幕上的白斑点,你认真思考重新给出正确的代码。你其的全部给我改错了!!!斑点是黑色屏幕上的白斑点,你认真思考重新给出正确的代码。你其的全部给我改错了!!!斑点是黑色屏幕上的白斑点,你认真思考重新给出正确的代码。你其的全部给我改错了!!!斑点是黑色屏幕上的白斑点,你认真思考重新给出正确的代码。你其的全部给我改错了!!!斑点是黑色屏幕上的白斑点,你认真思考重新给出正确的代码。%% 手机屏幕表面缺陷检测系统(保存“之前那种2x3结果图”,但不弹窗显示;并按类别分文件夹)
clear; clc; close all;
% ★关键:全局关闭figure显示(防止任何图窗口弹出)
set(groot,'DefaultFigureVisible','off');
%% 参数设置
folder_path = 'C:\Users\lenovo\Desktop\data';
image_files = dir(fullfile(folder_path, '*.jpg'));
% ====== 结果保存路径(按检测类别自动建子文件夹)======
base_save_folder = 'C:\Users\lenovo\Desktop\result"';
if ~exist(base_save_folder, 'dir')
mkdir(base_save_folder);
end
classes = {'Oil','Scratch','Stain','Unknown'};
for i = 1:numel(classes)
class_dir = fullfile(base_save_folder, classes{i});
if ~exist(class_dir, 'dir')
mkdir(class_dir);
end
end
% 检测参数
params.median_size = 3; % 中值滤波窗口大小
params.gauss_size = 5; % 高斯滤波窗口大小
params.gauss_sigma = 1.5; % 高斯滤波标准差
params.threshold_offset = 20; % 阈值偏移量
params.min_area = 50; % 最小缺陷面积
params.se_size_close = 5; % 形态学闭运算结构元大小
params.se_size_open = 3; % 形态学开运算结构元大小
%% 统计变量初始化
total_images = length(image_files);
oil_count = 0;
scratch_count = 0;
stain_count = 0;
correct_count = 0;
fprintf('======================================\n');
fprintf('手机屏幕表面缺陷检测系统(只保存,不显示)\n');
fprintf('======================================\n');
fprintf('共有 %d 张图像待检测\n\n', total_images);
if total_images == 0
error('未在文件夹 "%s" 中找到 .jpg 图像。', folder_path);
end
%% 批量处理图像
for idx = 1:total_images
% 读取图像
filename = image_files(idx).name;
filepath = fullfile(folder_path, filename);
img_original = imread(filepath);
text% 获取真实标签(来自文件名) if contains(filename, 'Oil') true_label = 'Oil'; elseif contains(filename, 'Scr') true_label = 'Scratch'; elseif contains(filename, 'Sta') true_label = 'Stain'; else true_label = 'Unknown'; end % 执行缺陷检测 [defect_type, defect_mask, processed_info] = detect_defect(img_original, params); % 统计结果 if strcmp(defect_type, 'Oil') oil_count = oil_count + 1; elseif strcmp(defect_type, 'Scratch') scratch_count = scratch_count + 1; elseif strcmp(defect_type, 'Stain') stain_count = stain_count + 1; end % 检查准确性 if strcmp(defect_type, true_label) correct_count = correct_count + 1; result_mark = '✓'; else result_mark = '✗'; end % 输出结果 fprintf('[%3d/%3d] %s | 真实: %-8s | 检测: %-8s | %s\n', ... idx, total_images, filename, true_label, defect_type, result_mark); % ★只保存:按“检测类别”分子文件夹,保存“2x3综合结果图”(不弹窗) save_classified_result_figure(img_original, defect_mask, processed_info, ... filename, defect_type, true_label, idx, base_save_folder);
end
%% 显示统计结果
fprintf('\n======================================\n');
fprintf('检测统计结果\n');
fprintf('======================================\n');
fprintf('总图像数: %d\n', total_images);
fprintf('油污检测数: %d\n', oil_count);
fprintf('划痕检测数: %d\n', scratch_count);
fprintf('斑点检测数: %d\n', stain_count);
fprintf('--------------------------------------\n');
fprintf('正确识别数: %d\n', correct_count);
fprintf('识别准确率: %.2f%%\n', 100 * correct_count / total_images);
fprintf('======================================\n');
% 可选:脚本结束前恢复默认显示(不恢复也没问题)
set(groot,'DefaultFigureVisible','on');
%% ======================= 屏幕区域提取函数 =======================
function [screen_img, screen_mask, bbox] = extract_screen(img)
if size(img, 3) == 3
img_gray = rgb2gray(img);
else
img_gray = img;
end
text[h, w] = size(img_gray); threshold = graythresh(img_gray); bw = img_gray < threshold * 255; se = strel('rectangle', [20, 20]); bw = imclose(bw, se); bw = imfill(bw, 'holes'); bw = bwareaopen(bw, 5000); stats = regionprops(bw, 'Area', 'BoundingBox', 'Solidity'); if isempty(stats) screen_img = img; screen_mask = true(h, w); bbox = [1, 1, w, h]; return; end [~, idx] = max([stats.Area]); best_stat = stats(idx); bbox = best_stat.BoundingBox; x = round(bbox(1)); y = round(bbox(2)); box_w = round(bbox(3)); box_h = round(bbox(4)); x = max(1, x); y = max(1, y); x_end = min(w, x + box_w - 1); y_end = min(h, y + box_h - 1); margin_x = round(box_w * 0.05); margin_y = round(box_h * 0.05); x = max(1, x + margin_x); y = max(1, y + margin_y); x_end = min(w, x_end - margin_x); y_end = min(h, y_end - margin_y); screen_img = img(y:y_end, x:x_end, :); screen_mask = false(h, w); screen_mask(y:y_end, x:x_end) = true; bbox = [x, y, x_end - x + 1, y_end - y + 1];
end
%% ======================= 缺陷检测主函数 =======================
function [defect_type, defect_mask, info] = detect_defect(img, params)
[screen_img, screen_mask, bbox] = extract_screen(img);
textif size(screen_img, 3) == 3 img_gray = rgb2gray(screen_img); else img_gray = screen_img; end img_median = medfilt2(img_gray, [params.median_size params.median_size]); h_gauss = fspecial('gaussian', params.gauss_size, params.gauss_sigma); img_smooth = imfilter(img_median, h_gauss); se_bg = strel('disk', 15); background = imopen(img_smooth, se_bg); img_diff = imsubtract(img_smooth, background); img_enhanced = imadjust(img_diff); threshold = graythresh(img_enhanced) * 255; threshold = max(threshold, params.threshold_offset); bw = img_enhanced > threshold; se_close = strel('disk', params.se_size_close); se_open = strel('disk', params.se_size_open); bw_morph = imclose(bw, se_close); bw_morph = imopen(bw_morph, se_open); bw_filled = imfill(bw_morph, 'holes'); bw_clean = bwareaopen(bw_filled, params.min_area); edges = edge(img_enhanced, 'Canny'); [labeled, num_objects] = bwlabel(bw_clean); stats = regionprops(labeled, 'Area', 'Perimeter', 'MajorAxisLength', ... 'MinorAxisLength', 'Eccentricity', 'BoundingBox'); info.screen_img = screen_img; info.screen_mask = screen_mask; info.bbox = bbox; info.img_gray = img_gray; info.img_enhanced = img_enhanced; info.background = background; info.edges = edges; info.bw = bw; info.num_objects = num_objects; if num_objects == 0 defect_type = 'Unknown'; defect_mask = bw_clean; return; end [~, max_idx] = max([stats.Area]); dominant_stat = stats(max_idx); area = dominant_stat.Area; texture_std = std(double(img_enhanced(:))); if area > 80000 || (num_objects > 35 && texture_std > 40) defect_type = 'Stain'; elseif area > 25000 || num_objects > 15 defect_type = 'Scratch'; else defect_type = 'Oil'; end defect_mask = bw_clean;
end
%% ======================= 保存函数(按检测类别分文件夹;保存2x3综合结果图;不显示) =======================
function save_classified_result_figure(img, mask, info, filename, detected, truth, fig_idx, base_save_folder)
textvalid = {'Oil','Scratch','Stain','Unknown'}; if ~any(strcmp(detected, valid)) detected = 'Unknown'; end out_dir = fullfile(base_save_folder, detected); if ~exist(out_dir, 'dir') mkdir(out_dir); end % 不可见figure:不会弹窗 fig = figure('Visible','off','Position',[50, 50, 1200, 700]); % 原始图像 subplot(2, 3, 1); imshow(img); title('原始图像', 'FontSize', 12, 'FontWeight', 'bold'); % 屏幕区域识别 subplot(2, 3, 2); imshow(img); hold on; rectangle('Position', info.bbox, 'EdgeColor', 'g', 'LineWidth', 2); title('屏幕区域识别', 'FontSize', 12, 'FontWeight', 'bold'); hold off; % 截取屏幕区域 subplot(2, 3, 3); imshow(info.screen_img); title('截取屏幕区域', 'FontSize', 12, 'FontWeight', 'bold'); % 增强图像 subplot(2, 3, 4); imshow(info.img_enhanced); title('对比度增强', 'FontSize', 12, 'FontWeight', 'bold'); % 边缘检测(过滤边缘10%区域) [sh, sw] = size(info.screen_img(:,:,1)); center_mask = false(sh, sw); margin_h = round(sh * 0.10); margin_w = round(sw * 0.10); if sh - 2*margin_h >= 1 && sw - 2*margin_w >= 1 center_mask(margin_h+1:sh-margin_h, margin_w+1:sw-margin_w) = true; else center_mask(:) = true; end edges_filtered = info.edges & center_mask; subplot(2, 3, 5); imshow(edges_filtered); title(sprintf('边缘检测-中心区域 (对象数:%d)', info.num_objects), ... 'FontSize', 12, 'FontWeight', 'bold'); % 结果叠加(仅中心区域) subplot(2, 3, 6); imshow(info.screen_img); hold on; mask_filtered = mask & center_mask; [B, ~] = bwboundaries(mask_filtered, 'noholes'); for k = 1:length(B) boundary = B{k}; if strcmp(detected, 'Oil') plot(boundary(:,2), boundary(:,1), 'y-', 'LineWidth', 2); elseif strcmp(detected, 'Scratch') plot(boundary(:,2), boundary(:,1), 'r-', 'LineWidth', 2); else plot(boundary(:,2), boundary(:,1), 'g-', 'LineWidth', 2); end end if strcmp(detected, truth) result_symbol = '✓'; title_color = [0 0.6 0]; else result_symbol = '✗'; title_color = [0.85 0 0]; end title(sprintf('检测结果: %s | 真实: %s %s', detected, truth, result_symbol), ... 'FontSize', 12, 'FontWeight', 'bold', 'Color', title_color); hold off; sgtitle(sprintf('[%d] %s', fig_idx, filename), ... 'FontSize', 14, 'FontWeight', 'bold'); % 输出文件名 [name_only, ~] = fileparts(filename); out_name = sprintf('%03d_%s_det-%s_true-%s.png', fig_idx, name_only, detected, truth); out_path = fullfile(out_dir, out_name); % 保存(优先 exportgraphics;否则 getframe+imwrite) if exist('exportgraphics', 'file') == 2 exportgraphics(fig, out_path, 'Resolution', 200); else frame = getframe(fig); imwrite(frame.cdata, out_path); end close(fig); % ★关键:及时关闭,不残留窗口/句柄
end学习这个代码,这个代码唯一不足的地方就是无法准确识别到斑点,请你修正这个问题,重新给我完整的matlab代码!!!学习这个代码,这个代码唯一不足的地方就是无法准确识别到斑点,请你修正这个问题,重新给我完整的matlab代码!!!学习这个代码,这个代码唯一不足的地方就是无法准确识别到斑点,请你修正这个问题,重新给我完整的matlab代码!!!学习这个代码,这个代码唯一不足的地方就是无法准确识别到斑点,请你修正这个问题,重新给我完整的matlab代码!!!学习这个代码,这个代码唯一不足的地方就是无法准确识别到斑点,请你修正这个问题,重新给我完整的matlab代码!!!学习这个代码,这个代码唯一不足的地方就是无法准确识别到斑点,请你修正这个问题,重新给我完整的matlab代码!!!学习这个代码,这个代码唯一不足的地方就是无法准确识别到斑点,请你修正这个问题,重新给我完整的matlab代码!!!
pythonimport cv2, numpy as np, os, glob, matplotlib.pyplot as plt, pandas as pd, math paths = ["/mnt/data/Sta_0001.jpg","/mnt/data/Sta_0002.jpg","/mnt/data/Sta_0003.jpg", "/mnt/data/Scr_0001.jpg","/mnt/data/Scr_0002.jpg","/mnt/data/Scr_0003.jpg", "/mnt/data/Oil_0001.jpg","/mnt/data/Oil_0002.jpg","/mnt/data/Oil_0003.jpg"] imgs={} for p in paths: img=cv2.imread(p) imgs[os.path.basename(p)] = img { k: v.shape for k,v in imgs.items() } {'Sta_0001.jpg': (1080, 1920, 3), 'Sta_0002.jpg': (1080, 1920, 3), 'Sta_0003.jpg': (1080, 1920, 3), 'Scr_0001.jpg': (1080, 1920, 3), 'Scr_0002.jpg': (1080, 1920, 3), 'Scr_0003.jpg': (1080, 1920, 3), 'Oil_0001.jpg': (1080, 1920, 3), 'Oil_0002.jpg': (1080, 1920, 3), 'Oil_0003.jpg': (1080, 1920, 3)}
pythonimport cv2, numpy as np, os, math def extract_screen_like(img): gray=cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) h,w=gray.shape thr,_=cv2.threshold(gray,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU) # thr value bw = (gray < thr).astype(np.uint8)*255 se=cv2.getStructuringElement(cv2.MORPH_RECT,(20,20)) bw=cv2.morphologyEx(bw, cv2.MORPH_CLOSE, se) bw=cv2.morphologyEx(bw, cv2.MORPH_OPEN, cv2.getStructuringElement(cv2.MORPH_RECT,(3,3))) # fill holes: use flood fill im_flood=bw.copy() mask=np.zeros((h+2,w+2),np.uint8) cv2.floodFill(im_flood,mask,(0,0),255) inv=cv2.bitwise_not(im_flood) bw = bw | inv # remove small n,labels,stats,cent=cv2.connectedComponentsWithStats(bw,8) # choose largest non-background if n<=1: return img,(0,0,w,h) idx=1+np.argmax(stats[1:,cv2.CC_STAT_AREA]) x,y,ww,hh,area=stats[idx] # margin 5% mx=int(ww*0.05); my=int(hh*0.05) x2=max(0,x+mx); y2=max(0,y+my) x3=min(w,x+ww-mx); y3=min(h,y+hh-my) return img[y2:y3,x2:x3],(x2,y2,x3-x2,y3-y2) def detect_spot_simple(screen_gray): median=cv2.medianBlur(screen_gray,3) smooth=cv2.GaussianBlur(median,(5,5),1.5) # background open disk 15 approximated by ellipse size 31? k=cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(31,31)) bg=cv2.morphologyEx(smooth, cv2.MORPH_OPEN, k) diff=cv2.subtract(smooth,bg) # imadjust: normalize to 0-255 diff2=cv2.normalize(diff,None,0,255,cv2.NORM_MINMAX) # otsu _,bw=cv2.threshold(diff2,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU) # apply threshold_offset 20 # In matlab they take threshold=graythresh*255 ; threshold=max(threshold, offset); bw=diff2>threshold t=max(_,20) _,bw=cv2.threshold(diff2,t,255,cv2.THRESH_BINARY) # morph close open bw=cv2.morphologyEx(bw, cv2.MORPH_CLOSE, cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(11,11))) bw=cv2.morphologyEx(bw, cv2.MORPH_OPEN, cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(7,7))) # fill holes not necessary # remove small area 50 n,labels,stats,_=cv2.connectedComponentsWithStats(bw,8) areas=stats[1:,cv2.CC_STAT_AREA] return areas for name in ["Sta_0001.jpg","Sta_0002.jpg","Sta_0003.jpg"]: img=imgs[name] scr,bbox=extract_screen_like(img) gray=cv2.cvtColor(scr, cv2.COLOR_BGR2GRAY) areas=detect_spot_simple(gray) print(name, "components", len(areas), "min", areas.min() if len(areas) else None, "max", areas.max() if len(areas) else None, "top5", sorted(areas, reverse=True)[:5])
pythonfor name in ["Oil_0001.jpg","Oil_0002.jpg","Oil_0003.jpg"]: img=imgs[name] scr,bbox=extract_screen_like(img) gray=cv2.cvtColor(scr, cv2.COLOR_BGR2GRAY) areas=detect_spot_simple(gray) print(name,"components",len(areas),"max", areas.max() if len(areas) else None, "sum", areas.sum() if len(areas) else 0)
pythonimport cv2, numpy as np, os, math def spot_stats(img): scr,_ = extract_screen_like(img) gray = cv2.cvtColor(scr, cv2.COLOR_BGR2GRAY) gray = cv2.medianBlur(gray,3) smooth = cv2.GaussianBlur(gray,(5,5),1.5) k = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(19,19)) # disk ~9 toph = cv2.morphologyEx(smooth, cv2.MORPH_TOPHAT, k) toph = cv2.normalize(toph,None,0,255,cv2.NORM_MINMAX) # adaptive threshold: use Otsu on toph t,_=cv2.threshold(toph,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU) t=max(t,25) _,bw=cv2.threshold(toph,t,255,cv2.THRESH_BINARY) # center mask 10% h,w=gray.shape mh=int(h*0.1); mw=int(w*0.1) cm=np.zeros((h,w),np.uint8) cm[mh:h-mh, mw:w-mw]=1 bw = (bw>0).astype(np.uint8) & cm # remove small n,labels,stats,cent=cv2.connectedComponentsWithStats(bw,8) comps=[] for i in range(1,n): area=stats[i,cv2.CC_STAT_AREA] if area<20 or area>1500: # rough continue x,y,ww,hh=stats[i,:4] # eccentricity approximation by aspect ratio ar=max(ww,hh)/(min(ww,hh)+1e-6) comps.append((area,ar)) return comps for name in ["Sta_0001.jpg","Sta_0002.jpg","Sta_0003.jpg","Oil_0001.jpg","Oil_0002.jpg","Oil_0003.jpg"]: comps=spot_stats(imgs[name]) print(name, "spot-like comps", len(comps), "areas", [c[0] for c in comps][:10], "ar", [round(c[1],2) for c in comps][:10])
pythondef scratch_score(img): scr,_=extract_screen_like(img) gray=cv2.cvtColor(scr,cv2.COLOR_BGR2GRAY) gray=cv2.GaussianBlur(gray,(5,5),1) edges=cv2.Canny(gray,50,150) # center mask h,w=edges.shape mh=int(h*0.1); mw=int(w*0.1) mask=np.zeros((h,w),np.uint8); mask[mh:h-mh,mw:w-mw]=1 edges = edges & (mask*255) lines = cv2.HoughLinesP(edges,1,np.pi/180,threshold=80,minLineLength=80,maxLineGap=10) if lines is None: return 0,0 lens=[np.hypot(l[0][2]-l[0][0], l[0][3]-l[0][1]) for l in lines] return len(lens), max(lens) for name in ["Scr_0001.jpg","Scr_0002.jpg","Scr_0003.jpg","Sta_0001.jpg","Oil_0001.jpg"]: cnt,ml = scratch_score(imgs[name]) print(name,cnt,ml)
pythonfrom PIL import Image import matplotlib.pyplot as plt, numpy as np, os import cv2 img=cv2.cvtColor(imgs["Scr_0003.jpg"], cv2.COLOR_BGR2RGB) plt.figure(figsize=(8,4)) plt.imshow(img); plt.axis('off'); plt.show()
下面给你一份完整可直接运行的 MATLAB 脚本(含所有函数),在你原代码基础上做了关键修正:斑点(Sta)= 黑屏上的“白色小斑点”,并且把 **“Stain 的判别逻辑”**改成了:
你只要把
folder_path改成你的数据路径:
F:\test\pythonProjectPhone screen\data
就能批量跑,结果仍然按类别分文件夹保存 2x3 图,不弹窗。
matlab%% 手机屏幕表面缺陷检测系统(2x3结果图:只保存不弹窗;按检测类别分文件夹) % 重点修正:Sta(斑点) = 黑色屏幕上的白色小斑点(小、亮、近似圆) clear; clc; close all; % ★关键:全局关闭figure显示(防止任何图窗口弹出) set(groot,'DefaultFigureVisible','off'); %% =============== 路径设置(改成你的数据集路径) =============== folder_path = 'F:\test\pythonProjectPhone screen\data'; % <-- 你的数据集目录 image_files = dir(fullfile(folder_path, '*.jpg')); % 结果保存路径(按检测类别自动建子文件夹) base_save_folder = fullfile(folder_path, '..', 'result'); % 默认保存到 data 的同级 result 文件夹 if ~exist(base_save_folder, 'dir'); mkdir(base_save_folder); end classes = {'Oil','Scratch','Stain','Unknown'}; for i = 1:numel(classes) class_dir = fullfile(base_save_folder, classes{i}); if ~exist(class_dir, 'dir'); mkdir(class_dir); end end %% =============== 参数设置(可微调,但默认已针对你样例“白点Sta”修正) =============== params.median_size = 3; % 中值滤波窗口 params.gauss_size = 5; % 高斯核大小 params.gauss_sigma = 1.5; % 高斯sigma params.center_margin_ratio = 0.10; % 过滤屏幕四周边缘(10%) % 通用增强(用于 Oil / Scratch 粗检测) params.bg_disk_general = 15; % 背景估计结构元(disk半径) params.min_area_general = 50; % 通用最小连通域面积 params.se_size_close = 5; params.se_size_open = 3; params.general_thresh_min = 0.06; % 通用阈值下限(0~1) % ===== Sta(斑点=白点)专用参数:白顶帽 + 形状筛选 ===== params.spot_tophat_disk = 9; % 白顶帽结构元半径(越小越敏感于小白点) params.spot_thresh_offset = 0.05; % 在Otsu阈值基础上加一点偏移,抑制噪声 params.spot_open_disk = 1; % 去噪用的小开运算 params.spot_min_area = 20; % 白点最小面积(你样例约200~300,这里设小一点更稳) params.spot_max_area = 1500; % 白点最大面积(防止油污高亮大片被当成斑点) params.spot_max_ecc = 0.90; % 偏心率上限(越小越圆) params.spot_min_circ = 0.25; % 圆度下限(4*pi*A/P^2,越接近1越圆) params.stain_min_spots = 1; % 至少几个白点算 Stain params.stain_max_spots = 6; % 白点数量上限(油污常会产生很多杂亮点/高光) params.stain_max_total_area = 2500; % 白点总面积上限 % ===== Scratch(划痕)参数:边缘+Hough线段 ===== params.hough_peaks = 8; params.hough_thresh_ratio = 0.25; % Hough峰值阈值比例(越小越敏感) params.hough_fillgap = 15; params.hough_minlength = 60; % 最短线段长度(像素) params.scratch_min_lines = 1; % 至少检测到几条线段算 Scratch params.scratch_min_maxlen = 90; % 最长线段超过该值更可信 %% =============== 统计变量 =============== total_images = length(image_files); oil_count = 0; scratch_count = 0; stain_count = 0; correct_count = 0; fprintf('======================================\n'); fprintf('手机屏幕表面缺陷检测系统(只保存,不显示)\n'); fprintf('======================================\n'); fprintf('共有 %d 张图像待检测\n\n', total_images); if total_images == 0 error('未在文件夹 "%s" 中找到 .jpg 图像。', folder_path); end %% =============== 批量处理 =============== for idx = 1:total_images filename = image_files(idx).name; filepath = fullfile(folder_path, filename); img_original = imread(filepath); % 真实标签(来自文件名) if contains(filename, 'Oil') true_label = 'Oil'; elseif contains(filename, 'Scr') true_label = 'Scratch'; elseif contains(filename, 'Sta') true_label = 'Stain'; else true_label = 'Unknown'; end % 缺陷检测 [defect_type, defect_mask, processed_info] = detect_defect(img_original, params); % 统计 if strcmp(defect_type, 'Oil'); oil_count = oil_count + 1; end if strcmp(defect_type, 'Scratch'); scratch_count = scratch_count + 1; end if strcmp(defect_type, 'Stain'); stain_count = stain_count + 1; end % 正确性 if strcmp(defect_type, true_label) correct_count = correct_count + 1; result_mark = '✓'; else result_mark = '✗'; end fprintf('[%3d/%3d] %s | 真实: %-8s | 检测: %-8s | %s\n', ... idx, total_images, filename, true_label, defect_type, result_mark); % 保存2x3综合结果图(不弹窗;按检测类别分文件夹) save_classified_result_figure(img_original, defect_mask, processed_info, ... filename, defect_type, true_label, idx, base_save_folder); end %% =============== 输出统计 =============== fprintf('\n======================================\n'); fprintf('检测统计结果\n'); fprintf('======================================\n'); fprintf('总图像数: %d\n', total_images); fprintf('油污检测数: %d\n', oil_count); fprintf('划痕检测数: %d\n', scratch_count); fprintf('斑点检测数: %d\n', stain_count); fprintf('--------------------------------------\n'); fprintf('正确识别数: %d\n', correct_count); fprintf('识别准确率: %.2f%%\n', 100 * correct_count / total_images); fprintf('======================================\n'); % 可选:恢复默认显示 set(groot,'DefaultFigureVisible','on'); %% ======================= 屏幕区域提取函数 ======================= function [screen_img, screen_mask, bbox] = extract_screen(img) if size(img, 3) == 3 img_gray = rgb2gray(img); else img_gray = img; end [h, w] = size(img_gray); threshold = graythresh(img_gray); % 0~1 bw = img_gray < threshold * 255; % 黑色区域(屏幕通常更黑) se = strel('rectangle', [20, 20]); bw = imclose(bw, se); bw = imfill(bw, 'holes'); bw = bwareaopen(bw, 5000); stats = regionprops(bw, 'Area', 'BoundingBox'); if isempty(stats) screen_img = img; screen_mask = true(h, w); bbox = [1, 1, w, h]; return; end [~, idx] = max([stats.Area]); best_stat = stats(idx); bbox = best_stat.BoundingBox; x = round(bbox(1)); y = round(bbox(2)); box_w = round(bbox(3)); box_h = round(bbox(4)); x = max(1, x); y = max(1, y); x_end = min(w, x + box_w - 1); y_end = min(h, y + box_h - 1); % 往内缩一点,去掉边框影响 margin_x = round(box_w * 0.05); margin_y = round(box_h * 0.05); x = max(1, x + margin_x); y = max(1, y + margin_y); x_end = min(w, x_end - margin_x); y_end = min(h, y_end - margin_y); screen_img = img(y:y_end, x:x_end, :); screen_mask = false(h, w); screen_mask(y:y_end, x:x_end) = true; bbox = [x, y, x_end - x + 1, y_end - y + 1]; end %% ======================= 缺陷检测主函数(修正Stain识别) ======================= function [defect_type, defect_mask, info] = detect_defect(img, params) [screen_img, screen_mask, bbox] = extract_screen(img); if size(screen_img, 3) == 3 img_gray = rgb2gray(screen_img); else img_gray = screen_img; end % 仅分析屏幕中心区域,避免边缘高光/边框干扰 [sh, sw] = size(img_gray); margin_h = round(sh * params.center_margin_ratio); margin_w = round(sw * params.center_margin_ratio); center_mask = false(sh, sw); if sh - 2*margin_h >= 1 && sw - 2*margin_w >= 1 center_mask(margin_h+1:sh-margin_h, margin_w+1:sw-margin_w) = true; else center_mask(:) = true; end % 去噪平滑(数字图像处理:空域滤波) img_median = medfilt2(img_gray, [params.median_size params.median_size]); h_gauss = fspecial('gaussian', params.gauss_size, params.gauss_sigma); img_smooth = imfilter(img_median, h_gauss, 'replicate'); %% -------- (A) Stain/Sta:白点专用检测(关键修正)-------- % 白顶帽突出“小而亮”的白点(黑底白点非常适合) spot_enh = imtophat(img_smooth, strel('disk', params.spot_tophat_disk)); spot_enh = imadjust(spot_enh); spot_norm = mat2gray(spot_enh); % 0~1 T_spot = graythresh(spot_norm(center_mask)); % Otsu阈值(中心区域) T_spot = min(0.95, max(T_spot + params.spot_thresh_offset, 0.05)); bw_spot0 = spot_norm > T_spot; bw_spot0 = bw_spot0 & center_mask; bw_spot0 = imopen(bw_spot0, strel('disk', params.spot_open_disk)); bw_spot0 = bwareaopen(bw_spot0, params.spot_min_area); % 形状筛选:面积范围 + 圆度 + 偏心率(确保是真“点”) cc_spot = bwconncomp(bw_spot0, 8); stats_spot = regionprops(cc_spot, 'Area','Perimeter','Eccentricity','PixelIdxList'); spot_mask = false(size(bw_spot0)); spot_areas = []; for k = 1:numel(stats_spot) A = stats_spot(k).Area; P = stats_spot(k).Perimeter; ecc = stats_spot(k).Eccentricity; circ = 4*pi*A / (P*P + eps); if A >= params.spot_min_area && A <= params.spot_max_area && ... ecc <= params.spot_max_ecc && circ >= params.spot_min_circ spot_mask(stats_spot(k).PixelIdxList) = true; spot_areas(end+1) = A; %#ok<AGROW> end end stain_spot_count = numel(spot_areas); stain_total_area = sum(spot_areas); %% -------- (B) Scratch/Scr:划痕检测(边缘 + Hough线段)-------- edge_img = edge(imadjust(img_smooth), 'Canny'); % 自动阈值更稳 edge_img = edge_img & center_mask; scratch_lines = []; scratch_maxlen = 0; try [H,theta,rho] = hough(edge_img); Pk = houghpeaks(H, params.hough_peaks, ... 'Threshold', ceil(params.hough_thresh_ratio * max(H(:)))); lines = houghlines(edge_img, theta, rho, Pk, ... 'FillGap', params.hough_fillgap, 'MinLength', params.hough_minlength); scratch_lines = lines; for i = 1:numel(lines) p1 = lines(i).point1; p2 = lines(i).point2; L = hypot(p1(1)-p2(1), p1(2)-p2(2)); scratch_maxlen = max(scratch_maxlen, L); end catch % 某些版本可能没有hough相关工具箱,忽略不报错 scratch_lines = []; scratch_maxlen = 0; end scratch_line_count = numel(scratch_lines); %% -------- (C) 通用增强 + 二值化(用于 Oil 粗检测/叠加显示)-------- background = imopen(img_smooth, strel('disk', params.bg_disk_general)); img_diff = imsubtract(img_smooth, background); img_enhanced = imadjust(img_diff); enh_norm = mat2gray(img_enhanced); T_gen = graythresh(enh_norm(center_mask)); T_gen = max(T_gen, params.general_thresh_min); bw = enh_norm > T_gen; bw = bw & center_mask; bw = imclose(bw, strel('disk', params.se_size_close)); bw = imopen(bw, strel('disk', params.se_size_open)); bw = bwareaopen(bw, params.min_area_general); cc = bwconncomp(bw, 8); num_objects = cc.NumObjects; %% -------- 分类逻辑(修正:Stain不再用“大面积/很多目标”)-------- if (num_objects == 0) && (stain_spot_count == 0) && (scratch_line_count == 0) defect_type = 'Unknown'; defect_mask = bw; else % 1) 先判 Scratch:有明显直线结构 if (scratch_line_count >= params.scratch_min_lines) && (scratch_maxlen >= params.scratch_min_maxlen) defect_type = 'Scratch'; % 用边缘膨胀作为划痕mask更直观 defect_mask = imdilate(edge_img, strel('disk',1)); else % 2) 再判 Stain:白点(小、圆、数量不多、总面积不大) if (stain_spot_count >= params.stain_min_spots) && ... (stain_spot_count <= params.stain_max_spots) && ... (stain_total_area <= params.stain_max_total_area) defect_type = 'Stain'; defect_mask = spot_mask; else % 3) 剩下都归 Oil(你的数据集只有三类) defect_type = 'Oil'; defect_mask = bw; end end end %% -------- info 输出(用于2x3结果图)-------- info.screen_img = screen_img; info.screen_mask = screen_mask; info.bbox = bbox; info.img_gray = img_gray; info.img_smooth = img_smooth; info.background = background; info.img_enhanced = img_enhanced; info.center_mask = center_mask; info.edges = edge_img; info.bw_general = bw; info.spot_enh = spot_enh; info.bw_spot = spot_mask; info.stain_spot_count = stain_spot_count; info.stain_total_area = stain_total_area; info.num_objects = num_objects; info.scratch_line_count = scratch_line_count; info.scratch_maxlen = scratch_maxlen; end %% ======================= 保存函数(按检测类别分文件夹;保存2x3综合结果图;不显示) ======================= function save_classified_result_figure(img, mask, info, filename, detected, truth, fig_idx, base_save_folder) valid = {'Oil','Scratch','Stain','Unknown'}; if ~any(strcmp(detected, valid)) detected = 'Unknown'; end out_dir = fullfile(base_save_folder, detected); if ~exist(out_dir, 'dir'); mkdir(out_dir); end fig = figure('Visible','off','Position',[50, 50, 1200, 700]); % (1) 原始图像 subplot(2, 3, 1); imshow(img); title('原始图像', 'FontSize', 12, 'FontWeight', 'bold'); % (2) 屏幕区域识别 subplot(2, 3, 2); imshow(img); hold on; rectangle('Position', info.bbox, 'EdgeColor', 'g', 'LineWidth', 2); title('屏幕区域识别', 'FontSize', 12, 'FontWeight', 'bold'); hold off; % (3) 截取屏幕区域 subplot(2, 3, 3); imshow(info.screen_img); title('截取屏幕区域', 'FontSize', 12, 'FontWeight', 'bold'); % (4) 对比度增强(通用增强图) subplot(2, 3, 4); imshow(info.img_enhanced); title('对比度增强', 'FontSize', 12, 'FontWeight', 'bold'); % (5) 边缘检测(中心区域) edges_filtered = info.edges & info.center_mask; subplot(2, 3, 5); imshow(edges_filtered); title(sprintf('边缘检测-中心区域 (线:%d, maxL:%.0f)', ... info.scratch_line_count, info.scratch_maxlen), 'FontSize', 12, 'FontWeight', 'bold'); % (6) 结果叠加(仅中心区域) subplot(2, 3, 6); imshow(info.screen_img); hold on; mask_filtered = mask & info.center_mask; [B, ~] = bwboundaries(mask_filtered, 'noholes'); for k = 1:length(B) boundary = B{k}; if strcmp(detected, 'Oil') plot(boundary(:,2), boundary(:,1), 'y-', 'LineWidth', 2); elseif strcmp(detected, 'Scratch') plot(boundary(:,2), boundary(:,1), 'r-', 'LineWidth', 2); elseif strcmp(detected, 'Stain') plot(boundary(:,2), boundary(:,1), 'g-', 'LineWidth', 2); else plot(boundary(:,2), boundary(:,1), 'c-', 'LineWidth', 2); end end if strcmp(detected, truth) result_symbol = '✓'; title_color = [0 0.6 0]; else result_symbol = '✗'; title_color = [0.85 0 0]; end title(sprintf('检测: %s | 真实: %s %s | 白点:%d', detected, truth, result_symbol, info.stain_spot_count), ... 'FontSize', 12, 'FontWeight', 'bold', 'Color', title_color); hold off; sgtitle(sprintf('[%d] %s', fig_idx, filename), 'FontSize', 14, 'FontWeight', 'bold'); [name_only, ~] = fileparts(filename); out_name = sprintf('%03d_%s_det-%s_true-%s.png', fig_idx, name_only, detected, truth); out_path = fullfile(out_dir, out_name); if exist('exportgraphics', 'file') == 2 exportgraphics(fig, out_path, 'Resolution', 200); else frame = getframe(fig); imwrite(frame.cdata, out_path); end close(fig); end
你把 Stain 的判别条件写反了:用“大面积/很多目标”去判 Stain,而你真实的 Sta 是 小白点(面积小、圆、数量少),所以必然误判。
如果你跑完后发现 Oil_某些图片里也有很多“小亮点高光”导致误判成 Stain,你把下面这两个参数再收紧就行(更严格地只认“白点”):
params.stain_max_spots 从 6 改成 4params.spot_max_area 从 1500 改成 800你也可以把你跑出来“误判的几张图”的结果截图发我(只需 2~3 张),我再帮你把阈值一次性调到更稳。