목적
face detection 사이드 프로젝트 모델로 detectron2를 사용하게 되었다.
detectron2에 대한 예제를 가지고 모델링, 작업 도중에 발생했던 오류 및 진행과정을 상세히 설명하고자 한다.
레퍼런스
Mac 사용시 주의 사항
- detectron2는 Cuda로 구현한 모델.
하지만 m1, m2는 Nvidia GPU를 제공하지 않기 때문에 로컬에서 Cuda를 사용할 수 없다.
해결방법으로는
①device를 mps로 변경 (했지만 아래 내용으로 오류 발생)
②device를 cpu로 변경. cpu는 연산하는 시간은 gpu의 10배 정도 걸렸기 때문에(직접 해봄) 추천하고 싶지 않다.
③google colab으로 학습 진행 (현재 내가 진행한 방법)
참고로 colaboratory는 하루에 12시간만 사용할 수 있으며 사용할 수 있는 용량이 제한되어 있다. 또한 가상머신으로 생성된 폴더는 재접속 할 경우 삭제되므로 훈련된 파일은 다운로드 받아 놓아야 한다.
Mac 오류 내용
- device를 mps로 변경하고 실행하였지만 실패
생전 처음 보는 에러. mps가 구현되지 않아서 github에 올리라는 메세지 - NotImplementedError: The operator 'aten::foreach_add.List' is not currently implemented for the MPS device. If you want this op to be added in priority during the prototype phase of this feature, please comment on https://github.com/pytorch/pytorch/issues/77764. As a temporary fix, you can set the environment variable `PYTORCH_ENABLE_MPS_FALLBACK=1` to use the CPU as a fallback for this op. WARNING: this will be slower than running natively on MPS.
- PYTORCH_ENT_MPS_FALLBACK=1 도 했지만 오류는 동일했다.
데이터 셋 오류 사항
- detectron2에서 제공하는 dataset은 현재 다운로드할 수 있는 접근이 없다.
이에 아래와 같은 방법으로 데이터를 가공하여 작업하였다.
https://big-data-analyst.tistory.com/16
Dataturks AccessDenied
목적 detectron2 실습을 하는 중간에 Dataturks에서 제공하는 Datasetaccess가 비활성화되어 접근을 할 수 없었다. 이에 비슷한 Dataset인 CelebFaces를 활용하여 내가 원하는 Format에 맞게 가공하여 작업하였
big-data-analyst.tistory.com
Detectron2 실습하기
Library version
CPython Python version : 3.9.7
IPython version : 8.15.0
numpy : 1.26.2
pandas : 2.1.4
pycocotools: 2.0
torch : 2.4.0.dev20240413
torchvision: 0.19.0.dev20240413
detectron2 : 0.6
코드
1. google colab 실행 후 detectron2 폴더에 이미지 및 annotation.txt 파일 업로드한다.
2. GPU를 사용하기 위해 런타임 유형을 T4 GPU 변경한다.
3. colab 파일 생성 후
google drive 사용을 위해 연결하는 코드를 작성한다.
from google.colab import drive
drive.mount('/content/drive')
4. detectron2 및 기타 라이브러리 설치하기
!git clone https://github.com/facebookresearch/detectron2 detectron2_repo
!pip install -q -e detectron2_repo
!pip install "cython<3.0.0" && pip install --no-build-isolation pyyaml==6.0
!pip install torch
!pip install torchvision
5. annotation.csv 파일 가져오기
이미지는 총 20만 장 정도 있으나 모든 이미지를 학습하기에는 오래 걸리고 colab으로 돌릴 경우 train 중간에 멈추게 된다.
500개에 대한 이미지로 annotation 파일을 만들고 학습하기로 했다.
#annotation 파일 가져오기
path = "/content/drive/MyDrive/detectron2/"
df = pd.read_csv(f'{path}annotations.csv', sep = ",")
df
6.shape 확인하기
print(faces_df.file_name.unique().shape[0], faces_df.shape[0])
7. 이미지 위에 바운딩 박스 그리는 함수 생성
이미지 크기는 default로 384 * 384로 동일하게 지정해야 한다.
Deep learning 네트워크는 고정된 크기의 input이 필요하기에 모든 이미지를 resize 함
# 정보, resize 기본값 = True
def annotate_image(annotations, resize=True):
# 파일명 numpy배열로 반환하여 첫번째 데이터 가져오기
file_name = annotations.file_name.to_numpy()[0]
# openCV는 BGR 형태로 읽음.
# BGR에서 RGB로 변환
img = cv2.cvtColor(cv2.imread(f'{path}faces/{file_name}'), cv2.COLOR_BGR2RGB)
# 바운딩 박스 그리기 (시작좌표, 종료좌표, color, 선 두께)
for i, a in annotations.iterrows():
cv2.rectangle(img, (a.x_min, a.y_min), (a.x_max, a.y_max), (0, 255, 0), 2)
# resize 값이 False
if not resize:
return img
# 이미지 크기 조정
# interpolation:보간법 (알려진 값을 가진 두점 사이 어느 지점의 값이 얼마일지 추정하는 기법)
return cv2.resize(img, (384, 384), interpolation = cv2.INTER_AREA)
8. 바운딩 박스 그리는 함수 테스트
#이미지 바운딩 박스 그리는 함수 적용 테스트
img_df = df[df.file_name == df.file_name.unique()[7]]
img = annotate_image(img_df, resize=False)
print(img_df)
plt.imshow(img)
# 축 지우기
plt.axis('off');
9. 95% 학습 데이터와 5% 평가 데이터셋으로 나누는 작업을 진행했다.
총 500건 중 475건이 train data로 분리되었다.
df = pd.read_csv('/content/drive/MyDrive/detectron2/annotations.csv')
IMAGES_PATH = f'/content/drive/MyDrive/detectron2/faces'
unique_files = df.file_name.unique()
#replace : 중복선택 허용 안함
train_files = set(np.random.choice(unique_files, int(len(unique_files) * 0.95), replace=False))
train_df = df[df.file_name.isin(train_files)]
test_df = df[~df.file_name.isin(train_files)]
train_df
10. 클래스 종류별 list를 classes라는 변수에 설정했다.
(현재 데이터셋은 face라는 클래스 하나만 설정되어 있다)
classes = df.class_name.unique().tolist()
11. bounding box의 좌표와 정보들을 dict type으로 함수를 만든다
# dataset dic 만드는 함수
def create_dataset_dicts(df, classes):
dataset_dicts = []
# enumerate : 인덱스와 값을 포함하여 리턴
for image_id, img_name in enumerate(df.file_name.unique()):
record = {}
image_df = df[df.file_name == img_name]
file_path = f'{IMAGES_PATH}/{img_name}'
record["file_name"] = file_path
record["image_id"] = image_id
record["height"] = int(image_df.iloc[0].height)
record["width"] = int(image_df.iloc[0].width)
objs = []
for _, row in image_df.iterrows():
xmin = int(row.x_min)
ymin = int(row.y_min)
xmax = int(row.x_max)
ymax = int(row.y_max)
poly = [
(xmin, ymin), (xmax, ymin),
(xmax, ymax), (xmin, ymax)
]
# 바운딩박스 좌표를 하나의 리스트로 만들어줌
poly = list(itertools.chain.from_iterable(poly))
obj = {
"bbox": [xmin, ymin, xmax, ymax],
"bbox_mode": BoxMode.XYXY_ABS,
"segmentation": [poly],
"category_id": classes.index(row.class_name),
"iscrowd": 0
}
objs.append(obj)
record["annotations"] = objs
dataset_dicts.append(record)
return dataset_dicts
12. 데이터셋을 등록한다
# 데이터셋 등록 과정
for d in ["train", "val"]:
# dataset catalog: custom dataset
# lambda 매개변수 : 표현식
# face+d : 데이터셋 이름 / d = train일 경우, train_df사용 아니면 dic 생성 함수 호출
DatasetCatalog.register("faces_" + d, lambda d=d: create_dataset_dicts(train_df if d == "train" else test_df, classes))
# 메타데이터 설정
# thing_classes : 데이터 셋에서 인식해야하는 클래스 지정
MetadataCatalog.get("faces_" + d).set(thing_classes=classes)
# 메타데이터를 가져오기
statement_metadata = MetadataCatalog.get("faces_train")
13. 데이터 평가 생성하는 클래스를 작성한다.
# 클래스 정의 후 해당 클래스에 build_evaluator 메서드 추가
class CocoTrainer(DefaultTrainer):
# classmethod는 객체 인스턴스를 수정할 수 없다. 대신 클래스 직접 호출, 직접 접근 가능
# 예. 각각 다른 인스턴스에 동일한 이자율 적용
# 생성자 랩핑 용도
@classmethod
# cfg: config(설정) 객체, 평가 데이터셋이름, 평가 저장 폴더 경로
def build_evaluator(cls, cfg, dataset_name, output_folder=None):
# 폴더 없을 경우 폴더 생성
if output_folder is None:
os.makedirs("coco_eval", exist_ok=True)
output_folder = "coco_eval"
# coco 데이터셋 평가하는 evaluator 반환
return COCOEvaluator(dataset_name, cfg, False, output_folder)
14. detectron2 config에 Mask R-CNN 모델과 훈련된 가중치를 가져와서 설정했다.
*model_zoo : 학습한 모델이 관리되는 폴더
- Mask R-CNN 모델의 경우 크고 무거워서 detectron2 레퍼런스 그대로 훈련을 시키기는 힘들었다. (google colab 사용)
그래서 batch 설정을 낮춰서 적용했다.
참고 : config definition(https://github.com/facebookresearch/detectron2/blob/main/detectron2/config/defaults.py#L55)
detectron2/detectron2/config/defaults.py at main · facebookresearch/detectron2
Detectron2 is a platform for object detection, segmentation and other visual recognition tasks. - facebookresearch/detectron2
github.com
#detectron2 라이브러리
cfg = get_cfg()
# 사전 정의된 모델 설정 파일 읽어와서 현재 설정 객체에 병합
cfg.merge_from_file(
# Mask R-CNN 모델 설정파일
model_zoo.get_config_file(
"COCO-InstanceSegmentation/mask_rcnn_X_101_32x8d_FPN_3x.yaml"))
# 사전 훈련된 가중치 가져오기
cfg.MODEL.WEIGHTS = model_zoo.get_checkpoint_url(
"COCO-InstanceSegmentation/mask_rcnn_X_101_32x8d_FPN_3x.yaml")
# Mask R-CNN X-101-FPN 모델 사용하여 coco 데이터셋 인스턴스 분할을 수행할 준비가 된 상태
15. config 설정
변경된 사항 : cfg.SOLVER.IMS_PER_BATCH = 2 (초기에는 4로 설정되어 있었음)
# 데이터셋 관련 설정 수정 () = 튜플
cfg.DATASETS.TRAIN = ("faces_train",)
cfg.DATASETS.TEST = ("faces_val",)
cfg.DATALOADER.NUM_WORKERS = 4
# 한번에 사용할 이미지 수
cfg.SOLVER.IMS_PER_BATCH = 2
# 학습률
cfg.SOLVER.BASE_LR = 0.001
# 워밍업 단계의 수 (초기 학습률을 점진적으로 증가시키는데 사용)
cfg.SOLVER.WARMUP_ITERS = 1600
# 전체 반복 횟수 (1500으로 지정할 경우 오류)
# val MAP가 상승중이면 조정, 과적합이면 하향 조정
cfg.SOLVER.MAX_ITER = 900
# 학습률 감소시킬 반복 횟수 (첫번째 학습률 감소 단계, 두번째 학습률 감소 단계)
cfg.SOLVER.STEPS = (1000, 1500)
# 학습률 감소시킬 비율
cfg.SOLVER.GAMMA = 0.05
# ROI 헤드 : 객체 인식 및 검출을 위해 입력 이미지에서 특정 영역을 추출하는데 사용
# 이미지당 ROI 헤드의 배치 크기
cfg.MODEL.ROI_HEADS.BATCH_SIZE_PER_IMAGE = 64
# 객체 인식 및 검출 모델의 클래스 수 (총 수를 나타내는 classes 사용함)
cfg.MODEL.ROI_HEADS.NUM_CLASSES = len(classes)
# 테스트 및 평가 과정을 수행하는 빈도. 500번 학습마다 평가 수행
# 모델 성능 평가하고 학습하는 동안 과적합 방지를 위해 주기적으로 수행
cfg.TEST.EVAL_PERIOD = 500
16. 모델 훈련 시작. 50분 정도 소요되었다.
os.makedirs(cfg.OUTPUT_DIR, exist_ok=True)
trainer = CocoTrainer(cfg)
trainer.resume_or_load(resume=False)
trainer.train()
17. output 폴더에 학습하면서 저장된 가중치 model_final.pth로 평가를 진행할 예정이다
임계값은 85%로 설정했다
# 모델 가중치를 model_final.pth로 설정
cfg.MODEL.WEIGHTS = os.path.join(cfg.OUTPUT_DIR, "/content/output/model_final.pth")
# IOU 최소 임계값을 85%로 설정
cfg.MODEL.ROI_HEADS.SCORE_THRESH_TEST = 0.85
# 설정한 cfg를 사용하여 이미지 객체 예측하는 defaultPredictor 객체 생성
predictor = DefaultPredictor(cfg)
18. 평가 데이터셋으로 모델의 성능을 측정했고 아래와 같이 측정되었다.
객체 탐지 (bbox) 성능:
- Average Precision (AP): 64.44%
- AP50 (IoU 0.50): 95.72%
- AP75 (IoU 0.75): 73.56%
- 작은 객체 (small)에 대한 AP: NaN (계산 불가능)
- 중간 크기 객체 (medium)에 대한 AP: NaN (계산 불가능)
- 큰 객체 (large)에 대한 AP: 64.48%
- Average Recall (AR):
- 모든 크기의 객체에 대한 AR: 68.4%
- 작은 객체에 대한 AR: NaN
- 중간 크기 객체에 대한 AR: NaN
- 큰 객체에 대한 AR: 68.4%
# faces_val: 검증 데이터 셋 / false: 중간결과 미표시 / output_dir : 평가 결과 저장 경로
# cocoEvaluator: coco 형식의 평가 수행
evaluator = COCOEvaluator("faces_val", cfg, False, output_dir="./output/")
# 해당 함수를 통해 cfg(설정) , 데이터셋 이름에 대한 데이터 로더 구성
# 검증 데이터 셋에서 이미지 및 관련된 주석(annotation) 로드
val_loader = build_detection_test_loader(cfg, "faces_val")
# trainer.model: 학습한 모델
# val_loader : 검증 데이터 로더
# evaluator : 평가자 객체
# inference_on_dateset은 데이터셋에 대한 추론 수행, 추론 결과의 성능 평가
inference_on_dataset(trainer.model, val_loader, evaluator)
19. 검증 데이터 결과를 저장할 폴더를 만들고, 검증 이미지 파일 이름을 가져왔다.
os.makedirs("annotated_results", exist_ok=True)
# 검증 데이터셋 이미지 파일 이름 가져오기
test_image_paths = test_df.file_name.unique()
20. 검증 데이터셋의 바운딩 박스 시각화하는 코드를 작성한다.
# 검증 데이터셋의 객체 검출 후 그 결과를 시각화 하여 저장하는 과정
for clothing_image in test_image_paths:
# 이미지 파일 경로
file_path = f'{IMAGES_PATH}/{clothing_image}'
im = cv2.imread(file_path)
# 이미지 읽기
outputs = predictor(im)
# 이미지 시각화
v = Visualizer(
im[:, :, ::-1], # 이미지 색상을 RGB -> BGR로 변경
metadata=statement_metadata, # 객체 메타데이터 (클래스 이름, 색상, 카테고리)
scale=1., #이미지 크기 조절(변경하지 않음)
instance_mode=ColorMode.IMAGE # 객체 검출 결과 시각화 모드 설정 (오버레이하여 시각화)
)
# 객체 검출 결과중 instances키에 해당하는 값을 cpu로 이동
instances = outputs["instances"].to("cpu")
# 해당 인스턴스에 예측 마스크 제거 = 바운딩 박스만 시각화
instances.remove('pred_masks')
# 객체 검출 결과를 이미지에 그리기
v = v.draw_instance_predictions(instances)
# 시각화 이미지 RGB로 변경
result = v.get_image()[:, :, ::-1]
# 현재 파일 이름 추출
file_name = ntpath.basename(clothing_image)
# 시각화 이미지 annotated_results 저장
write_res = cv2.imwrite(f'annotated_results/{file_name}', result)
21. 시각화 결과 경로 생성 및 결과 테스트
얼굴 인식이 정확하게 잘 나온다.
# 이미지 시각화 결과를 경로 생성
annotated_images = [f'annotated_results/{f}' for f in test_df.file_name.unique()]
img = cv2.cvtColor(cv2.imread(annotated_images[2]), cv2.COLOR_BGR2RGB)
plt.imshow(img)
plt.axis('off');
마무리
detectron2 레퍼런스를 참고하면서 코드를 구현해 보았다. 환경이 window였다면 조금 더 수월할 수도 있겠다는 생각을 했다.
현재 학습한 데이터셋이 detectron2에서 제공하는 데이터와 다르기에 이미지 분류는 class 하나만 학습하게 되었다.
내가 필요한 분류 작업에 대해 제대로 학습이 되었는지 판단할 수가 없었다. 하지만 해당 예제를 실습하면서 모델링 작업의 전반적인 과정을 공부할 수 있었고 사이드 프로젝트에 사용하는 데이터를 가지고 모델링 작업을 다시 진행할 예정이다.
'ML & DL > [CV] 나는 솔로 - 사이드프로젝트' 카테고리의 다른 글
[Mac] Detectron2 - AssertionError: Torch not compiled with CUDA enabled (0) | 2024.04.16 |
---|---|
Dataturks AccessDenied (0) | 2024.04.09 |
댓글