본문 바로가기
포트폴리오/딥러닝 프로젝트

필굿 - 약품 정보 추출 EasyYOLO - OCR / 크롤링

by 유스베리이 2024. 11. 25.

필굿은 해외 약품 정보 플랫폼으로 해외의 약품들의 정보를 손쉽게 찾아볼 수 있는 서비스이다. 

일단 AI를 적용한 2개의 메인 기능이 있다.

 

1. 사용자가 자신의 상황과 원하는 종류의 약을 챗봇에 질문하면 프롬프팅된 GPT가 사용자의 질문을 해석하고 사용자의 건강정보를 고려한 맞춤 약품 정보를 리스트 형태로 제공한다 .

 

GPT 프롬프팅은 다음과 같이 이루어졌다.

 

하지면 멘토님의 면담을 통해 GPT 4만 사용을 해서 약품을 추천해주는 방법은 실제 서비스 관점에서 부정확한 정보를 제공할 가능성이 있다고 하셨다 .

따라서 챗봇 구현에 단순히 GPT 4를 사용하는 것이 아닌 RAG, 파인튜닝를 추가하여 직접 데이터를 가공하고 라벨링을 통해 직접 데이터를 입력하기로 했다.

 

 

2. 해외 약품의 사진을 찍어서 스캔하거나 , 직접 약품 이름을 입력하면 전 세계 다양한 국가의 약품 정보를 제공한다.

 

해외 약품의 사진을 찍어서 글자를 스캔하려면 일단 YOLO 를 통해 객체 인식을 하고, OCR을 통해 텍스트로 변환을 하는 과정을 거쳐야 한다. 그리고 약품의 텍스트 추출 후 텍스트 바탕으로 검색을 할 때, 임베딩 모델을 open api로 가져오는 방법을 고려 중이다.

 

일단 기술 검증을 위해 YOLO + OCR 을 통해 약품 사진을 추출하는 것을 시도하였다.

여기서 고려해야 될 점이 OCR 모델은 원하는 문서, 이미지 내의 전체 텍스트를 인식하는 데 효과적이지만, 이미지에서 특정 영역의 문자만 감지하려면 불필요한 영역도 감지하므로 감지 속도가 오래 걸린다는 것을 알고 다양한 이미지에서 특정 패턴이나 텍스트 영역만 감지하기 위해 EasyYolo OCR을 사용하기로 했다.

 

(왼)기존 OCR / (오) easy Yolo OCR

 

Easy Yolo OCR을 통한 텍스트 추출은 다음과 같은 과정으로 진행되었다.

 1) Easy Yolo OCR 모델 직접 학습 시키기 

https://github.com/aqntks/Easy-Yolo-OCR

 

GitHub - aqntks/Easy-Yolo-OCR: Proceed with text detection only in the selected area of ​​the image

Proceed with text detection only in the selected area of ​​the image - aqntks/Easy-Yolo-OCR

github.com

 

훈련 데이터에 필요한 약품 이미지를 AI 허브에서 약품, 화장품 라벨링 데이터를 다운 받아서 사용했다.

https://www.aihub.or.kr/aihubdata/data/view.do?currMenu=&topMenu=&aihubDataSe=data&dataSetSn=633

 

AI-Hub

샘플 데이터 ? ※샘플데이터는 데이터의 이해를 돕기 위해 별도로 가공하여 제공하는 정보로써 원본 데이터와 차이가 있을 수 있으며, 데이터에 따라서 민감한 정보는 일부 마스킹(*) 처리가 되

www.aihub.or.kr

 

하지만 약품의 라벨링 데이터가 내가 원하는 구조로 되어있지 않아서 데이터의 가공이 필요했다.

(ClassIndex) (BoxCenterX[value 0-1]) (BoxCenterY[value 0-1]) (BoxWidth[value 0-1]) (BoxHeight[value 0-1])

from google.colab import files
import json

# 1. 파일 업로드
uploaded = files.upload()

# 2. 업로드된 파일 경로 가져오기
file_path = list(uploaded.keys())[0]  # 업로드된 파일의 이름

# 3. JSON 데이터 로드
with open(file_path, 'r') as file:
    data = json.load(file)

# 4. 이미지 크기 및 주석(annotation) 데이터 추출
image_width = data["images"][0]["width"]
image_height = data["images"][0]["height"]
annotations = data["annotations"][0]["bbox"]

# 5. 바운딩 박스 처리
output = []
for box in annotations:
    class_index = 0  # 모든 바운딩 박스가 클래스 0이라고 가정
    center_x = (box["x"] + box["width"] / 2) / image_width
    center_y = (box["y"] + box["height"] / 2) / image_height
    width = box["width"] / image_width
    height = box["height"] / image_height
    output.append(f"{class_index} {center_x} {center_y} {width} {height}")

# 6. 결과 출력 (괄호 없이)
print("Processed Bounding Boxes:")
print("\n".join(output))

# Save the processed bounding box output to a text file
output_file = "image17.txt"

# Writing the output to a text file
with open(output_file, "w") as f:
    for item in output:
        f.write(item + "\n")

print(f"Output saved to {output_file}")

# Download the file in Colab
from google.colab import files
files.download(output_file)

 

추출한 값(medicine_0007.jpg)

0 0.7418245037847272 0.08292565873019336 0.2772143983521761 0.04947109868742951
0 0.2735478813229058 0.16171520807396308 0.18660275169698062 0.031159523367151536
0 0.5072602920170312 0.16194100172155113 0.2467971877282646 0.03341745984303208
0 0.7520936477716142 0.16284417631190334 0.18459627049593788 0.03161111066232762
0 0.12155693034391352 0.219292588208917 0.05919119543076266 0.05960952296324643
0 0.4265420729024194 0.2188410009137409 0.49459761605705066 0.05870634837289426
0 0.8005678824322864 0.22335687386550201 0.14346340587456036 0.05057777705972427
0 0.7025932442793675 0.20665149220464518 0.019761482382226324 0.01186027808021578
0 0.28816928517149876 0.2684815220871201 0.21828309946945793 0.030704833672513258
0 0.5211695292687135 0.3149225830167963 0.6770186756982405 0.03914866293245434
0 0.21611471738821214 0.41925804096321373 0.18298357282289804 0.039513486538471566
0 0.500481080558932 0.5294505808874022 0.7665528050688971 0.13746241091552786
0 0.22476934583253844 0.6741478555353261 0.19534732774336416 0.03394820674432062
0 0.23527853751493455 0.7125482861149676 0.2237839640604361 0.028382926950169748
0 0.622327623705822 0.7133842123701665 0.5037409257913933 0.027035362523099862
0 0.1767106508903586 0.7543733103890594 0.10074818515827867 0.029651687928560967
0 0.2929585568422186 0.7519750121007202 0.10462311535667404 0.02747141675734336
0 0.44795576477803195 0.7567716086773991 0.18212171932458068 0.031831959099778574

 

 

이런식으로 이미지를 업로드하면 이미지의 데이터 라벨링한 파일을 저장해서 테스트 데이터를 준비하고, 모델을 학습 시킨다음에 원하는 이미지를 입력하면 된다.

import os
import random

# 데이터셋 경로 설정
dataset_dir = "/content/custom_data"
train_file = "/content/custom_data/custom_train.txt"
valid_file = "/content/custom_data/custom_valid.txt"
test_file = "/content/custom_data/custom_test.txt"  # optional

# 파일 목록 가져오기
all_files = [os.path.join(dataset_dir, f) for f in os.listdir(dataset_dir) if f.endswith(".jpg")]

# 섞기
random.seed(42)  # 재현성을 위해 고정
random.shuffle(all_files)

# 데이터 나누기 (비율: 70% train, 20% valid, 10% test)
train_split = int(0.7 * len(all_files))
valid_split = int(0.9 * len(all_files))  # 70% + 20%

train_files = all_files[:train_split]
valid_files = all_files[train_split:valid_split]
test_files = all_files[valid_split:]  # optional

# 파일 쓰기 함수
def write_to_file(file_path, data):
    with open(file_path, "w") as f:
        for line in data:
            f.write(line + "\n")

# 파일 생성
write_to_file(train_file, train_files)
write_to_file(valid_file, valid_files)
write_to_file(test_file, test_files)  # optional

print(f"Train: {len(train_files)}, Valid: {len(valid_files)}, Test: {len(test_files)}")
print(f"Files created:\n{train_file}\n{valid_file}\n{test_file}")

 

하지만 다른 방법도 있었다.

 

2) easy Yolo OCR 라이브러리 사용

 

해외 약품의 정보를 추출하는 거라 EasyOCR 영어 버전만 로드 하였다.

import easyocr
import re

# EasyOCR 모델 로드
reader = easyocr.Reader(['en'], gpu=True)

def run_ocr_easyocr(image_path):
    """
    EasyOCR로 이미지에서 텍스트를 추출
    :param image_path: 이미지 경로
    :return: 감지된 텍스트 리스트
    """
    results = reader.readtext(image_path)
    return [result[1] for result in results]  # 텍스트 리스트 반환

def extract_medicine_names(detected_texts):
    """
    감지된 텍스트 리스트에서 한글과 영어 약품명을 추출
    :param detected_texts: OCR로 추출된 텍스트 리스트
    :return: 약품명 리스트
    """
    medicine_names = []
    for text in detected_texts:

        if re.match(r"^[A-Z][a-zA-Z0-9\-]+$", text):
            medicine_names.append(text)
    return medicine_names

# 테스트
image_path = "/content/drive/MyDrive/custom_data/image17.jpg"  # 이미지 경로
detected_texts = run_ocr_easyocr(image_path)
print("Detected Texts:", detected_texts)

# 약품명 추출
medicine_names = extract_medicine_names(detected_texts)
print("Extracted Medicine Names:", medicine_names)

 

텍스트를 추출했을 때 그 중에서도 약품명을 추출하기 위해 약품명의 패턴을 바탕으로 extract_medicine_names() 함수를 사용했다.

패턴: ^[A-Z][a-zA-Z0-9\-]+$

^[A-Z]: 대문자로 시작.

[a-zA-Z0-9\-]+: 알파벳, 숫자, 하이픈(-)으로 구성된 텍스트.

$: 텍스트 끝.

이 패턴은 약품명에 자주 사용되는 영어 대문자로 시작하는 텍스트를 필터링한다.

 

<결과값> 

Detected Texts: ['wdc 50580-226-51', 'Benadryl', 'ALLERGY', 'Diphenhydramine HCI', '[Antihistamine',
                  'Sneezing', 'Runny Nose', 'Itchy; Watery Eyes', 'Itchy Throat', "'small", 'UET BSTABS""',
                  'actual size', '24 TABLETS', '25mg']
Extracted Medicine Names: ['Benadryl','ALLERGY', 'Sneezing', 'Runny Nose', 'Itchy; Watery Eyes', 'Itchy Throat']

 

이미지에서 주로 강조된 텍스트나 약품명 데이터를 뽑아내긴 했으나, 약품명만 정확하게 출력하는 모델을 설계하기 위해 다른 방법이 필요할 것 같다.따라서 그부분에 대해 추가적으로 연구할 예정이다.

 


약품명이나 이미지로 약품정보를 검색했을 때 약품의 성분 정보를 가져올 수 있도록 약품 정보 데이터가 필요하다.

여기서 영국 약품 정보 사이트에서 텍스트를 크롤링하여 데이터를 뽑아오는 모델이 필요하다.

https://www.crummy.com/software/BeautifulSoup/bs4/doc/

 

Beautiful Soup Documentation — Beautiful Soup 4.12.0 documentation

Beautiful Soup Documentation Beautiful Soup is a Python library for pulling data out of HTML and XML files. It works with your favorite parser to provide idiomatic ways of navigating, searching, and modifying the parse tree. It commonly saves programmers h

www.crummy.com

Beoutiful Soup 모델을 사용하였다.

 

1. 웹페이지 요청: requests 라이브러리를 사용해 지정된 URL에서 HTML 콘텐츠를 가져옴.

2. HTML 파싱: BeautifulSoup을 사용하여 가져온 HTML을 분석 가능한 구조로 변환함.

3. 정보 추출: id='about-medicine'로 지정된 섹션을 찾아 텍스트를 추출하고 출력함.

 

import requests
from bs4 import BeautifulSoup

# URL 설정
url = "https://www.medicines.org.uk/emc/product/4657/pil#about-medicine"

# HTML 가져오기
response = requests.get(url)

# 요청 성공 여부 확인
if response.status_code == 200:
    # BeautifulSoup 객체 생성
    soup = BeautifulSoup(response.content, 'html.parser')

    # 특정 텍스트 추출
    # 페이지에서 "Nexium contains a medicine called esomeprazole" 부분을 찾기
    text_section = soup.find('div', {'id': 'about-medicine'})  # 'about-medicine' 섹션 찾기
    if text_section:
        extracted_text = text_section.get_text(strip=True)
        print("Extracted Text:")
        print(extracted_text)
    else:
        print("Unable to find the specified section.")
else:
    print(f"Failed to retrieve the page. Status code: {response.status_code}")

 

출력값 

Extracted Text: Nexium contains a medicine called esomeprazole. This belongs to a group of medicines called ‘proton pump inhibitors’. They work by reducing the amount of acid that your stomach produces.