챗지피티, 클로드, 퍼플렉시티, 제미나이, 그록으로 간단하게 요청해서 코딩을 시작했는데,
모두 제대로 검출을 하지 못했음. 문제 있는 부분 수정해 달라고 했지만, 내가 생각하는 바가 정확하게 전달이 되지 않는 느낌 받음.
챗지피티로 다시 시작했고 내가 "문제"를 인식하는 과정을 순서대로 알려주고 단계별로 결과를 출력하게 해서 점검하고 문제없으면 확장하는 방식으로 바이브 코딩 진행.
느낀 점은 구체적으로 조건을 주고, 단계별로 확인을 하면서 다음 단계로 넘어가야 한다는 점.
복잡할수록 단계 정해서 시켜야 함. 어떤 문제가 발생했을 때도 구체적으로 알려줘야 코드 개선이 됨.
"문제"는 이미지를 보고 이빨이 빠진 곳의 위치를 찾으라는 것이었는데, x, y 개수가 몇 개인지를 고정한 후부터 정확도가 올라갔음.
사람이라면 전체가 몇 개인지, x, y 개수가 몇 개인지 몰라도 점으로 선을 만들어 따라가면서 좌우 비교해 보면 빠진 느낌을 받을 수가 있고 그곳이 이빨 빠진 곳이라는 감을 잡을 수 있는데...
바이브 코딩에서는 x, y 개수가 몇 개인지를 알려주기 전까지는 정확도가 낮았음.
AGI가 나오기 전까지는 구체적으로 지시를 할 수 있는 사람이 능력 있는 사람일 거 같음.
대학에서도 바이브 코딩으로 문제를 해결하는 과목이 생길 것 같음.





import cv2
import numpy as np
# ============================================================
# Distorted LED Grid Analyzer (with debug exports)
# (modified for separate grid_row_n, grid_col_n)
# ============================================================
# 아래 코드는 카메라로 찍혀 왜곡(회전/기울어짐/렌즈왜곡)이 있는 LED 점등 사진에서
# 흰색으로 켜진 LED 점(블롭)을 검출하고
# 그 점들이 **50×50 격자(행/열)**라는 가정 하에
# **행 곡선(행 라인), 열 곡선(열 라인)**을 각각 모델링(다항식+RANSAC)해서
# 각 격자 교차점에 LED가 실제로 존재하는지(최근접 거리로) 판단하고
# **꺼진 위치(row,col + 이미지좌표)**를 저장하는 파이프라인입니다.
# ---------- colors (BGR) ----------
PINK = (255, 0, 255)
GREEN = (0, 255, 0)
RED = (0, 0, 255)
CYAN = (255, 255, 0) # rows
WHITE = (255, 255, 255)
YELLOW= (0, 255, 255) # cols
def detect_led_components(img_bgr,
thr_mode="otsu", fixed_thr=220,
morph_open=1,
area_band=0.60,
max_led=5000):
gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (3, 3), 0)
if thr_mode == "otsu":
_, bw = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
elif thr_mode == "fixed":
_, bw = cv2.threshold(gray, fixed_thr, 255, cv2.THRESH_BINARY)
else:
raise ValueError("thr_mode must be 'otsu' or 'fixed'")
if morph_open and morph_open > 0:
k = 2 * morph_open + 1
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (k, k))
bw = cv2.morphologyEx(bw, cv2.MORPH_OPEN, kernel, iterations=1)
nlab, labels, stats, centroids = cv2.connectedComponentsWithStats(bw, connectivity=8)
if nlab <= 1:
return bw, labels, stats, centroids, []
areas = stats[1:, cv2.CC_STAT_AREA].astype(np.float32)
med = float(np.median(areas))
low = med * (1 - area_band)
high = med * (1 + area_band)
keep = (areas >= low) & (areas <= high)
kept_labels = (np.where(keep)[0] + 1).astype(int)
if len(kept_labels) > max_led:
kept_areas = areas[keep]
diffs = np.abs(kept_areas - med)
order = np.argsort(diffs)[:max_led]
kept_labels = kept_labels[order]
return bw, labels, stats, centroids, kept_labels.tolist()
def minrect_top_edge(points_xy: np.ndarray):
rect = cv2.minAreaRect(points_xy.astype(np.float32))
box = cv2.boxPoints(rect).astype(np.float32)
idx = np.argsort(box[:, 1])
top2 = box[idx[:2]]
if top2[0, 0] <= top2[1, 0]:
A, B = top2[0], top2[1]
else:
A, B = top2[1], top2[0]
v = B - A
L = float(np.linalg.norm(v) + 1e-9)
e = (v / L).astype(np.float32)
n = np.array([-e[1], e[0]], dtype=np.float32)
n /= (np.linalg.norm(n) + 1e-9)
return box, A.astype(np.float32), B.astype(np.float32), e, n
def to_edge_coords(pts_xy, origin, e, n):
d = pts_xy - origin
x = d @ e
y = d @ n
return x.astype(np.float32), y.astype(np.float32)
def from_edge_coords(x, y, origin, e, n):
return (origin[None, :] + np.outer(x, e) + np.outer(y, n)).astype(np.float32)
def kmeans_1d(values, k, attempts=10, seed=0):
v = values.astype(np.float32).reshape(-1, 1)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 200, 1e-4)
cv2.setRNGSeed(int(seed))
_, labels, centers = cv2.kmeans(v, k, None, criteria, attempts, cv2.KMEANS_PP_CENTERS)
labels = labels.reshape(-1)
centers = centers.reshape(-1)
order = np.argsort(centers)
centers_sorted = centers[order]
inv = np.empty_like(order)
inv[order] = np.arange(k)
labels_sorted = inv[labels]
return centers_sorted, labels_sorted
def fit_poly_and_score(x, y, degree):
coef = np.polyfit(x, y, degree)
yhat = np.polyval(coef, x)
ss_res = float(np.sum((y - yhat) ** 2))
ss_tot = float(np.sum((y - np.mean(y)) ** 2) + 1e-9)
r2 = 1.0 - ss_res / ss_tot
rmse = float(np.sqrt(ss_res / max(1, len(y))))
return coef, r2, rmse
def polyfit_ransac(x, y, degree=2, iters=800, inlier_sigma=3.0, min_inliers=20, seed=0):
rng = np.random.default_rng(seed)
N = len(x)
if N < degree + 2:
return None, np.zeros(N, dtype=bool), -np.inf, np.inf
med = np.median(y)
mad = np.median(np.abs(y - med)) + 1e-6
thr = float(inlier_sigma * mad)
best = None
sample_n = degree + 2
for _ in range(iters):
try:
idx = rng.choice(N, size=sample_n, replace=False)
except ValueError:
break
try:
coef0 = np.polyfit(x[idx], y[idx], degree)
except np.linalg.LinAlgError:
continue
resid = np.abs(y - np.polyval(coef0, x))
mask = resid <= thr
if mask.sum() < min_inliers:
continue
try:
coef, r2, rmse = fit_poly_and_score(x[mask], y[mask], degree)
except np.linalg.LinAlgError:
continue
score = float(mask.sum()) + 5.0 * r2 - 0.02 * rmse
if best is None or score > best[0]:
best = (score, coef, mask, r2, rmse)
if best is None:
coef, r2, rmse = fit_poly_and_score(x, y, degree)
return coef, np.ones(N, dtype=bool), r2, rmse
_, coef, mask, r2, rmse = best
return coef, mask, r2, rmse
def fit_rows_cols(points_xy_img, origin_A, e, n,
grid_row_n=50,
grid_col_n=50,
row_degree=2,
col_degree=2,
row_band_factor=0.45,
col_band_factor=0.45,
inlier_sigma=3.0,
seed=0):
x_all, y_all = to_edge_coords(points_xy_img, origin=origin_A, e=e, n=n)
# 행/열 개수 분리
y_centers, _ = kmeans_1d(y_all, grid_row_n, seed=seed + 10)
x_centers, _ = kmeans_1d(x_all, grid_col_n, seed=seed + 20)
dy_step = float(np.median(np.diff(np.sort(y_centers))))
dx_step = float(np.median(np.diff(np.sort(x_centers))))
row_band = max(2.0, row_band_factor * dy_step)
col_band = max(2.0, col_band_factor * dx_step)
rows = []
cols = []
# rows
for r in range(grid_row_n):
target_y = float(y_centers[r])
idx = np.where(np.abs(y_all - target_y) <= row_band)[0]
if len(idx) < 12:
rows.append(None)
continue
xr = x_all[idx]
yr = y_all[idx]
coef, inliers, r2, rmse = polyfit_ransac(
xr, yr, degree=row_degree,
iters=1200, inlier_sigma=inlier_sigma,
min_inliers=max(12, int(0.35*len(idx))),
seed=seed + 100 + r
)
if coef is None or inliers.sum() < 8:
rows.append(None)
continue
rows.append({"r": r, "target_y": target_y, "coef": coef, "r2": float(r2), "rmse": float(rmse)})
# cols
for c in range(grid_col_n):
target_x = float(x_centers[c])
idx = np.where(np.abs(x_all - target_x) <= col_band)[0]
if len(idx) < 12:
cols.append(None)
continue
yc = y_all[idx]
xc = x_all[idx]
coef, inliers, r2, rmse = polyfit_ransac(
yc, xc, degree=col_degree,
iters=1200, inlier_sigma=inlier_sigma,
min_inliers=max(12, int(0.35*len(idx))),
seed=seed + 200 + c
)
if coef is None or inliers.sum() < 8:
cols.append(None)
continue
cols.append({"c": c, "target_x": target_x, "coef": coef, "r2": float(r2), "rmse": float(rmse)})
frame = {
"A": origin_A, "e": e, "n": n,
"x_all": x_all, "y_all": y_all,
"x_centers": x_centers, "y_centers": y_centers,
"dx_step": dx_step, "dy_step": dy_step,
"row_band": row_band, "col_band": col_band,
"grid_row_n": grid_row_n,
"grid_col_n": grid_col_n,
}
return frame, rows, cols
def draw_rows_cols_on_original(img_bgr, frame, rows, cols,
grid_row_n=50,
grid_col_n=50,
samples=300,
thickness=2):
out = img_bgr.copy()
A, e, n = frame["A"], frame["e"], frame["n"]
x_min = float(np.min(frame["x_all"]))
x_max = float(np.max(frame["x_all"]))
y_min = float(np.min(frame["y_all"]))
y_max = float(np.max(frame["y_all"]))
xs = np.linspace(x_min, x_max, samples).astype(np.float32)
for r in range(grid_row_n):
row = rows[r]
if row is None:
continue
ys = np.polyval(row["coef"], xs).astype(np.float32)
pts = from_edge_coords(xs, ys, origin=A, e=e, n=n).astype(np.int32).reshape(-1, 1, 2)
cv2.polylines(out, [pts], False, CYAN, thickness, cv2.LINE_AA)
ys = np.linspace(y_min, y_max, samples).astype(np.float32)
for c in range(grid_col_n):
col = cols[c]
if col is None:
continue
xs2 = np.polyval(col["coef"], ys).astype(np.float32)
pts = from_edge_coords(xs2, ys, origin=A, e=e, n=n).astype(np.int32).reshape(-1, 1, 2)
cv2.polylines(out, [pts], False, YELLOW, thickness, cv2.LINE_AA)
return out
def save_debug_lines(img_bgr, frame, rows, cols,
grid_row_n=50,
grid_col_n=50,
samples=500,
thickness=2,
prefix="line",
start_index=0):
A, e, n = frame["A"], frame["e"], frame["n"]
x_min = float(np.min(frame["x_all"]))
x_max = float(np.max(frame["x_all"]))
y_min = float(np.min(frame["y_all"]))
y_max = float(np.max(frame["y_all"]))
idx = int(start_index)
xs = np.linspace(x_min, x_max, samples).astype(np.float32)
for r in range(grid_row_n):
canvas = img_bgr.copy()
row = rows[r]
if row is not None:
ys = np.polyval(row["coef"], xs).astype(np.float32)
pts = from_edge_coords(xs, ys, origin=A, e=e, n=n).astype(np.int32).reshape(-1, 1, 2)
cv2.polylines(canvas, [pts], False, CYAN, thickness, cv2.LINE_AA)
cv2.putText(canvas, f"ROW {r:02d} R2={row['r2']:.3f} RMSE={row['rmse']:.2f}px",
(20, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8, WHITE, 2, cv2.LINE_AA)
else:
cv2.putText(canvas, f"ROW {r:02d} (NOT FOUND)",
(20, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8, RED, 2, cv2.LINE_AA)
cv2.imwrite(f"{prefix}{idx:04d}.jpg", canvas)
idx += 1
ys = np.linspace(y_min, y_max, samples).astype(np.float32)
for c in range(grid_col_n):
canvas = img_bgr.copy()
col = cols[c]
if col is not None:
xs2 = np.polyval(col["coef"], ys).astype(np.float32)
pts = from_edge_coords(xs2, ys, origin=A, e=e, n=n).astype(np.int32).reshape(-1, 1, 2)
cv2.polylines(canvas, [pts], False, YELLOW, thickness, cv2.LINE_AA)
cv2.putText(canvas, f"COL {c:02d} R2={col['r2']:.3f} RMSE={col['rmse']:.2f}px",
(20, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8, WHITE, 2, cv2.LINE_AA)
else:
cv2.putText(canvas, f"COL {c:02d} (NOT FOUND)",
(20, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8, RED, 2, cv2.LINE_AA)
cv2.imwrite(f"{prefix}{idx:04d}.jpg", canvas)
idx += 1
return idx
def compute_intersections_and_occupancy(frame, rows, cols,
grid_row_n=50,
grid_col_n=50,
iters=8,
nn_thr_factor=0.45):
A, e, n = frame["A"], frame["e"], frame["n"]
pts_base = np.stack([frame["x_all"], frame["y_all"]], axis=1).astype(np.float32)
flann = cv2.flann_Index(pts_base, dict(algorithm=1, trees=5))
nn_thr = float(nn_thr_factor * min(frame["dx_step"], frame["dy_step"]))
occ = np.zeros((grid_row_n, grid_col_n), dtype=bool)
node_xy_img = np.zeros((grid_row_n, grid_col_n, 2), dtype=np.float32)
nn_dist = np.full((grid_row_n, grid_col_n), np.inf, dtype=np.float32)
x_centers = frame["x_centers"]
y_centers = frame["y_centers"]
for r in range(grid_row_n):
row = rows[r]
if row is None:
continue
for c in range(grid_col_n):
col = cols[c]
if col is None:
continue
x = float(x_centers[c])
y = float(y_centers[r])
for _ in range(iters):
y = float(np.polyval(row["coef"], x))
x = float(np.polyval(col["coef"], y))
q = np.array([[x, y]], dtype=np.float32)
_, d2 = flann.knnSearch(q, 1, params={})
dist = float(np.sqrt(d2[0, 0]))
nn_dist[r, c] = dist
occ[r, c] = (dist <= nn_thr)
p_img = from_edge_coords(np.array([x], np.float32),
np.array([y], np.float32),
origin=A, e=e, n=n)[0]
node_xy_img[r, c] = p_img
return occ, node_xy_img, nn_dist, nn_thr
def render_regular_grid_image(occ, cell=20, margin=20, radius=6):
H = margin * 2 + (occ.shape[0] - 1) * cell
W = margin * 2 + (occ.shape[1] - 1) * cell
img = np.zeros((H, W, 3), dtype=np.uint8)
for r in range(occ.shape[0]):
y = margin + r * cell
for c in range(occ.shape[1]):
x = margin + c * cell
color = GREEN if occ[r, c] else RED
cv2.circle(img, (x, y), radius, color, 1, cv2.LINE_AA)
return img
def run_pipeline(img_path="distorsion_image.jpg",
grid_row_n=50,
grid_col_n=50,
thr_mode="otsu",
fixed_thr=220,
morph_open=1,
area_band=0.60,
max_led=5000,
row_degree=2,
col_degree=2,
inlier_sigma=3.0,
seed=0,
debug=False,
debug_prefix="line"):
img = cv2.imread(img_path, cv2.IMREAD_COLOR)
if img is None:
raise FileNotFoundError(img_path)
bw, labels_map, stats, centroids, kept_labels = detect_led_components(
img,
thr_mode=thr_mode,
fixed_thr=fixed_thr,
morph_open=morph_open,
area_band=area_band,
max_led=max_led
)
cv2.imwrite("binary.png", bw)
if len(kept_labels) < 200:
raise RuntimeError(f"검출된 점이 너무 적습니다: {len(kept_labels)}")
pts_img = centroids[np.array(kept_labels, dtype=int)].astype(np.float32)
box, A, B, e, n = minrect_top_edge(pts_img)
frame, rows, cols = fit_rows_cols(
pts_img,
origin_A=A, e=e, n=n,
grid_row_n=grid_row_n,
grid_col_n=grid_col_n,
row_degree=row_degree,
col_degree=col_degree,
row_band_factor=0.45,
col_band_factor=0.45,
inlier_sigma=inlier_sigma,
seed=seed
)
overlay_curves = draw_rows_cols_on_original(
img, frame, rows, cols,
grid_row_n=grid_row_n,
grid_col_n=grid_col_n,
samples=300,
thickness=1,
)
cv2.imwrite("overlay_curves.png", overlay_curves)
if debug:
save_debug_lines(
img_bgr=img,
frame=frame,
rows=rows,
cols=cols,
grid_row_n=grid_row_n,
grid_col_n=grid_col_n,
samples=500,
thickness=1,
prefix=debug_prefix,
start_index=0
)
occ, node_xy_img, nn_dist, nn_thr = compute_intersections_and_occupancy(
frame, rows, cols,
grid_row_n=grid_row_n,
grid_col_n=grid_col_n,
iters=8,
nn_thr_factor=0.45
)
np.save(f"occupancy_{grid_row_n}x{grid_col_n}.npy", occ)
missing = np.argwhere(~occ)
# 1) 기존: row col만 저장
# np.savetxt("missing_rc.txt", missing, fmt="%d", header="row col", comments="")
# 2) 추가: row col x y 같이 저장
# node_xy_img[r, c] 가 float32 (x,y) 이미지 좌표
if len(missing) > 0:
miss_xy = node_xy_img[missing[:, 0], missing[:, 1]] # (N,2)
miss_rcxy = np.hstack([missing.astype(np.int32), miss_xy.astype(np.float32)]) # (N,4)
np.savetxt(
"missing_rcxy.txt",
miss_rcxy,
fmt=["%d", "%d", "%.2f", "%.2f"],
header="row col x_img y_img",
comments=""
)
else:
# 빈 파일도 헤더는 남기고 싶으면:
with open("missing_rcxy.txt", "w", encoding="utf-8") as f:
f.write("row col x_img y_img\n")
print(f"NN threshold (base-frame): {nn_thr:.2f}")
print(f"Missing count: {len(missing)} / {grid_row_n*grid_col_n}")
overlay_nodes = img.copy()
for r in range(grid_row_n):
for c in range(grid_col_n):
x, y = node_xy_img[r, c]
if not np.isfinite(x) or not np.isfinite(y):
continue
color = GREEN if occ[r, c] else RED
cv2.circle(overlay_nodes, (int(round(x)), int(round(y))), 7, color, 1, cv2.LINE_AA)
cv2.imwrite("overlay_nodes_on_original.png", overlay_nodes)
grid_img = render_regular_grid_image(occ, cell=20, margin=20, radius=6)
cv2.imwrite("grid_regular.png", grid_img)
print("Saved:")
print("- binary.png")
print("- overlay_curves.png (rows=CYAN, cols=YELLOW) [ORIGINAL base]")
print("- overlay_nodes_on_original.png")
print("- grid_regular.png")
# print("- missing_rc.txt")
print("- missing_rcxy.txt (row,col + x_img,y_img)")
if debug:
print(f"- {debug_prefix}0000.jpg ... (per-line debug)")
return {"occ": occ, "missing_rc": missing, "frame": frame}
if __name__ == "__main__":
run_pipeline(
img_path="distorsion_image.jpg",
grid_row_n=50,
grid_col_n=50, # 예: 50 x 50 배열
thr_mode="otsu",
fixed_thr=220,
morph_open=1,
area_band=0.60,
max_led=5000,
row_degree=2,
col_degree=2,
inlier_sigma=3.0,
seed=0,
debug=False, # debug=True이면 과정들을 line0000.jpg ...로 출력
debug_prefix="line"
)
print('Defective LED inspection has been finished.')
목적
핵심 파라미터 튜닝
목적
튜닝 포인트
목적
튜닝
목적
언제 문제가 생기나 / 튜닝
목적
핵심 파라미터 튜닝
목적
가장 중요한 튜닝 포인트
목적
튜닝
목적
언제 켜나
목적
가장 중요한 튜닝 포인트
목적
튜닝
목적
실전에서 파라미터 조정 순서(추천)
| 폴더에서 이미지 읽고 GIF 또는 MP4 파일로 만드는 파이썬 코드 (0) | 2026.02.21 |
|---|---|
| 갤럭시 안드로이드폰에서 파이썬과 주피터 노트북 사용하기 (0) | 2026.02.21 |
| [파이썬] DXF CAD 형상 깨지지 않게 matplotlib 에서 plot하기 (0) | 2025.11.08 |
| [파이썬] 워피지, 휨 보정, 스케일링 코드 예제 (0) | 2025.07.23 |
| [파이썬] t-분포 데이터 만들기, t-검정, 모평균차에 대한 추론 (0) | 2025.05.06 |
댓글 영역