Skip to content

Welding:SurfaceSampleDataGenerator

LVS로 부터 (x, z) 축만 있는 csv 데이터 1-Line을 y축으로 여러 라인을 추가한, "면 (Surface)" 가상 데이터로 ply로 생성하는 방법.

Sample Data

Micro-Epsilon:scanCONTROL 을 사용함:

ScanCONTROL_preview.png

다음과 같은 용접면을 스캔 하였다:

Welding_surface_sample_example.jpg

다음과 같은 csv 파일을 획득할 수 있다:

27.08,114.632
26.866,114.63
26.652,114.632
26.438,114.636
26.224,114.638

전체 데이터 샘플은 20260415-butt_bead_profile_sample.zip 이다.

X, Z 축만 있는 CSV 데이터 변환하기

LVS 같은 레이저 스캐너를 통해 데이터를 얻으면 (x, z) 좌표만 얻어진다. 이걸 3차원 ply 로 표현하기 위해 y 축을 추가해 준다:

import csv
import sys
import os

def convert_csv_to_ply(csv_path, ply_path):
    points = []
    with open(csv_path, 'r') as f:
        reader = csv.reader(f)
        for row in reader:
            if len(row) >= 2:
                try:
                    x = float(row[0])
                    y = 0.0  # 단일 프로파일이므로 y는 0으로 고정
                    z = float(row[1])
                    points.append((x, y, z))
                except ValueError:
                    continue

    with open(ply_path, 'w') as f:
        f.write("ply\n")
        f.write("format ascii 1.0\n")
        f.write(f"element vertex {len(points)}\n")
        f.write("property float x\n")
        f.write("property float y\n")
        f.write("property float z\n")
        f.write("end_header\n")
        for p in points:
            f.write(f"{p[0]} {p[1]} {p[2]}\n")

if __name__ == "__main__":
    files = [
        "20260415-butt_bead_profile_sample.csv",
        "20260415-fillet_bead_profile_sample.csv"
    ]

    for csv_file in files:
        if os.path.exists(csv_file):
            ply_file = csv_file.replace(".csv", ".ply")
            convert_csv_to_ply(csv_file, ply_file)
            print(f"Converted {csv_file} -> {ply_file}")

여러 스캔 라인 생성하기

제공해주신 CSV 데이터와 이미지를 확인했습니다. 현재 가지고 계신 데이터는 용접 비드의 단면(2D Profile) 정보입니다.

이를 기반으로 전체 면(Surface)을 스캔한 것과 같은 가상 데이터셋 (Synthetic Dataset) 을 생성하려면, 기존 프로파일에 일정한 변화를 주어 Y축 방향으로 확장하는 과정이 필요합니다.

데이터 생성 전략

품질 평가 알고리즘 테스트나 학습용으로 사용할 수 있는 3가지 데이터 생성 전략을 제안해 드립니다.

단순 보간 및 노이즈 추가 방식 (가장 현실적)

실제 용접 비드는 직선처럼 일정하지 않고, 미세하게 흔들리거나 높낮이가 변합니다. 기존 1장의 데이터를 복제하되, 각 라인마다 약간의 무작위성을 부여하는 방법입니다.

  1. 원본 CSV(X, Z)를 가져옵니다.
  2. Y축 좌표를 설정합니다 (예: 0.1mm 간격).
  3. 각 Y 좌표마다 Z값에 가우시안 노이즈 (Gaussian Noise) 를 추가합니다.
  4. 비드의 중심 위치(X축)를 좌우로 미세하게 Shift(이동) 시킵니다.

장점: 데이터 생성이 매우 빠르며, 센서의 측정 오차까지 시뮬레이션할 수 있습니다.

모핑(Morphing) 기법 활용

만약 '정상 비드' 데이터와 '불량(예: Overlap 또는 Underfill)' 데이터가 각각 1개씩 있다면, 두 데이터 사이를 단계적으로 변화시켜 여러 장을 만들 수 있습니다.

\(Znew​=(1−t)⋅Znormal​+t⋅Zdefect​\)

  • Znew​: 생성된 새로운 프로파일의 높이 데이터입니다.
  • Znormal​: 기준이 되는 정상 비드의 높이 데이터입니다.
  • Zdefect​: 목표로 하는 결함 비드의 높이 데이터입니다.
  • t: 보간 계수이며, 0≤t≤1 범위를 가집니다 (t=0이면 정상, t=1이면 결함 상태).

장점: 양품에서 불량으로 넘어가는 과도기적 품질 상태를 연속적으로 생성할 수 있어 판정 임계치(Threshold) 설정에 유리합니다.

사인파(Sine Wave)를 이용한 비드 굴곡 생성

실제 용접은 진행 방향(Y축)으로 물결 모양의 리플 (Ripple) 이 생깁니다. 이를 수식으로 구현합니다.

\(Z(x,y)=Zbase​(x)+A⋅sin(2π⋅f⋅y+ϕ)\) Z(x,y): 최종적으로 생성된 3차원 좌표의 높이 값입니다.

  • Zbase​(x): 원본 CSV에서 가져온 기본 단면 프로파일의 높이입니다.
  • A: 사인파의 진폭(Amplitude)으로, 리플의 높낮이 정도를 결정합니다.
  • f: 주파수(Frequency)로, 단위 길이당 생성되는 리플의 개수를 결정합니다.
  • y: 용접 진행 방향의 좌표입니다.
  • ϕ: 위상(Phase)으로, 파형의 시작 지점을 조절합니다.

장점: 시각적으로 매우 실제와 유사한 비드 형상을 얻을 수 있습니다.

Python 샘플 코드

제공해주신 CSV 데이터를 기반으로 Y축 방향 2,000개 라인을 생성하여, 전체 용접면을 구성하는 3D 포인트 클라우드(PLY 파일)를 생성해 드립니다.

실제 용접부의 느낌을 살리기 위해 각 라인마다 미세한 가우시안 노이즈와 비드 위치의 유동성(Wandering) 을 추가하여 가상 데이터를 구성했습니다.

## 이 코드는 원본 데이터 1장을 읽어 Y축으로 2,000번 반복하며 데이터를 확장합니다. 각 좌표는 (X,Y,Z) 형태로 저장됩니다.

import pandas as pd
import numpy as np

# 1. 원본 데이터 로드 (X, Z)
# 사용자 데이터: 컬럼명 없이 x, z 순서로 구성됨
df = pd.read_csv('20260415-butt_bead_profile_sample.csv', names=['x', 'z'])

y_steps = 2000      # 요청하신 2,000개 라인
y_interval = 0.05   # Y축 간격 (mm) - 적절한 밀도를 위해 설정

points = []

print("포인트 생성 중...")
for i in range(y_steps):
    y_val = i * y_interval

    # 현실성을 위한 무작위 변위 추가
    # 1. Z축 노이즈 (센서 노이즈 모사: ±0.005mm)
    z_noise = np.random.normal(0, 0.005, len(df))
    # 2. X축 미세 흔들림 (용접 진행 시 좌우 유동 모사)
    x_shift = np.sin(i * 0.05) * 0.02 

    for idx, row in df.iterrows():
        points.append([row['x'] + x_shift, y_val, row['z'] + z_noise[idx]])

# 2. PLY 파일 형식으로 저장
def save_ply(filename, points):
    header = f"""ply
format ascii 1.0
element vertex {len(points)}
property float x
property float y
property float z
end_header
"""
    with open(filename, 'w') as f:
        f.write(header)
        for p in points:
            f.write(f"{p[0]:.4f} {p[1]:.4f} {p[2]:.4f}\n")

save_ply('welding_bead_3d.ply', points)
print(f"완료! 총 {len(points)}개의 포인트가 'welding_bead_3d.ply'로 저장되었습니다.")

데이터 생성 상세 설정 (Recipe)

단순 복제가 아닌 품질 평가 알고리즘 테스트에 적합하도록 다음 요소를 반영했습니다.

  • Y축 간격 (dy): 2,000개의 라인이 너무 띄엄띄엄 있지 않도록 0.05mm 간격을 기본값으로 설정했습니다 (총 길이 약 100mm).
  • 비드 사행 (Wandering): np.sin 함수를 이용해 비드가 아주 미세하게 좌우로 굽이치도록 설정했습니다. 이는 실제 용접 로봇이나 작업자의 손떨림을 모사합니다.
  • 표면 거칠기 (Roughness): 가우시안 노이즈를 통해 LVS 센서가 실제 금속 표면을 찍을 때 발생하는 난반사 노이즈를 재현했습니다.

결과 파일

ply로 만들어진 파일을 압축하였다: 20260415-butt_bead_profile_sample-all.zip

다음은 ?

Pointcept:Examples:Weld:LVS 항목 참조.

See also