EasyOCR
Import Library
import re
import cv2
import easyocr
import os
from glob import glob
import numpy as np
from PIL import Image, ImageDraw, ImageFont
# import torchre: ์ ๊ท์ ๊ฒ์ฌ(์ซ์ ํจํด ํ์ง)cv2: (OpenCV): ์ด๋ฏธ์ง ์ ์ถ๋ ฅ, ํด๋ฆฌ๊ณค(polygon) ๋ง์คํฌ, ๋ฆฌ์ฌ์ด์ฆ, ๋ธ๋ฌ ๋ฑeasyocr: OCR ์์ง (readtextโ[(bbox, text, confidence),...])numpy: ์ขํ/๋ฐฐ์ด ์ฐ์ฐPIL(Pillow): ํ ์คํธ ๋์ฒด ๋ชจ๋(replace_text)์์ ๊ธ์ ๋ ๋๋ง ๋ฐ ํ์ ํฉ์ฑglob, os: ํ์ผ ์ ์ถ๋ ฅ/ํด๋ ์ฒ๋ฆฌtorch: gpu ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ํ์ธ (ํ์ x)
์ฌ์ ์ค์
# EasyOCR ์ค์ ๊ฐ
OCR_LANGS = ['ko', 'en']
# ์ ๊ท์ (3๊ฐ์ง ์ ํ ์ปค๋ฒ)
rrn_combined_regex = re.compile(r"\b\d{6}\s*-\s*\d{7}\b")
rrn_nohyphen_regex = re.compile(r"\b\d{13}\b")
rrn_front_regex = re.compile(r"\b\d{6}\b")
rrn_back_regex = re.compile(r"\b\d{7}\b")OCR_LANGS = ['ko', 'en']: EasyOCR์ด ์ฌ์ฉํ ์ธ์ดrrn_combined_regex:\b\d{6}\s*-\s*\d{7}\b(6์๋ฆฌ, ํ์ดํ(์ต์ ๊ณต๋ฐฑ) ํ 7์๋ฆฌ)rrn_nohyphen_regex:\b\d{13}\b(13์๋ฆฌ ๋ถ์ด์์ ๋)rrn_front_regex:\b\d{6}\b(์ 6์๋ฆฌ)rrn_back_regex:\b\d{7}\b(๋ค 7์๋ฆฌ)
โ ๏ธํ์ฌ ์ ๊ท์ ํจํด์ ํ๊ณ
- ์ (
.), ์ฌ๋์(/) ๋ฑ ๋ค๋ฅธ ๊ตฌ๋ถ์์ ํน์๋ฌธ์, OCR ์คํ(์:oโ0,lโ1)๋ ํจํด์์ ์ ์ธ๋จ.
# ๊ฒฐํฉ ์๊ณ๊ฐ ์ค์
MAX_X_DISTANCE = 50
MAX_Y_DIFFERENCE = 10
# ๋น์๋ณํ ๋ฐฉ์ ๋ฐ ์ค์
MASK_METHOD = "black" # "black", "mosaic", "blur", "replace_text"
REPLACE_TEXT = "[ REDACTED ]"
MOSAIC_SCALE = 0.05
BLUR_KERNEL = (51, 51)
OCR_LOW_TEXT = 0.4-
MAX_X_DISTANCE = 50: ์์ ์ซ์(6์๋ฆฌ)์ ๋ค์ ์ซ์(7์๋ฆฌ) ์ฌ์ด x ๊ฑฐ๋ฆฌ ์ต๋ ํ์ฉ (ํฝ์ ๋จ์) -
MAX_Y_DIFFERENCE = 10: ์ค์ฌ์ y ์ฐจ์ด ์ต๋ ํ์ฉ -
MASK_METHOD: ๋ง์คํน ๋ฐฉ์. โblackโ, โmosaicโ, โblurโ, โreplace_textโ์ผ๋ก ์ด 4๊ฐ์ง -
MOSAIC_SCALE,BLUR_KERNEL,OCR_LOW_TEXT: ๋ณด์ ํ๋ผ๋ฏธํฐ
๋ง์คํน ์ ํธ๋ฆฌํฐ ํจ์
mask_bbox ํจ์
def mask_bbox(img, bbox, method=MASK_METHOD):
pts = np.array(bbox, dtype=np.int32)
...
return img โน๏ธ ๊ธฐ๋ฅ
- ์ด๋ฏธ์ง ์์์ ํน์ ๊ธ์ ์์ญ๋ง ๊ฐ๋ฆฌ๊ฑฐ๋ ๋ง์คํนํ๋ ํจ์
- EasyOCR์
readtext()์์ ๋ฐํ๋ **๊ธ์์ ์์น(bbox)**๋ฅผ ์ ๋ ฅ ๋ฐ์ ์ฒ๋ฆฌ
๋งค๊ฐ ๋ณ์
์ด๋ฆ ์๋ฏธ imgOpenCV ์ด๋ฏธ์ง ๋ฐฐ์ด(numpy array) bboxEasyOCR์ด ๋ฐํํ ๊ธ์ ์์น 4์ ์ขํ method๋ง์คํน ๋ฐฉ๋ฒ: "black","mosaic","blur","replace_text"
-
pts = np.array(bbox, dtype=np.int32)bbox์ ์๋ฅผ ๋ค์ด ๋ค์๊ณผ ๊ฐ์ ๊ฐ์ ๊ฐ๋๋ค.
bbox = [[np.int32(296), np.int32(188)], [np.int32(640), np.int32(188)], [np.int32(640), np.int32(264)], [np.int32(296), np.int32(264)]] -
OpenCV์์ ๋ํ ๊ทธ๋ฆฌ๊ธฐ ํจ์(
cv2.fillPoly)๋ numpy ๋ฐฐ์ด ํํ๋ก ์ขํ๋ฅผ ๋ฐ์์ผ ํ๋ค. EasyOCR์์๋ ์ขํ๊ฐ์ numpy(np)์ int32 ํ์ ์ผ๋ก ๋ฐํํ๋ฏ๋กdtype=np.in32๋ก ์ง์ ํ๋ค.๊ฒฐ๋ก ์ ์ผ๋ก pts๋ ๋ค์๊ณผ ๊ฐ์ 2์ฐจ์ numpy array๊ฐ ๋๋ค.
[[296 188] [640 188] [640 264] [296 264]]
๊ฒ์ ์ ๋ง์คํน(black)
if method == "black":
cv2.fillPoly(img, [pts], (0, 0, 0)) cv2.fillPoly(img, [pts], (0, 0, 0))- ์ ๋ ฅ ๋ฐ์ ์ด๋ฏธ์ง(img)์์ ์ง์ ๋ ๋ค๊ฐํ ์์ญ([pts])์ ๊ฒ์ ์((0, 0, 0))์ผ๋ก ์ฑ์ฐ๋ ํจ์
pts์ขํ๋ฅผ ๊ธฐ์ค์ผ๋ก ์ด๋ฏธ์ง์ ๋ค๊ฐํ ์์ญ์ ์ ์ํ๊ณ , ๋ค๊ฐํ ๋ด๋ถ ํฝ์ ๊ฐ์ ๊ฒ์ ์์ผ๋ก ์ฑ์ด๋ค.- ๊ณต์ ๋ฌธ์ ์ฐธ์กฐ
๋งค๊ฐ ๋ณ์
์ด๋ฆ ์๋ฏธ img๊ฒ์ ์ ๋ํ์ ๊ทธ๋ฆด ๋์ ์ด๋ฏธ์ง (OpenCV ์ด๋ฏธ์ง, numpy array) [pts]์ฑ์ธ ๋ค๊ฐํ ์ขํ. ๋ฆฌ์คํธ ์์ numpy array ํํ์ฌ์ผ ํจ (0,0,0)์์(B,G,R ์์). OpenCV๋ RGB๊ฐ ์๋ BGR ์ฌ์ฉ. (0,0,0) โ ๊ฒ์ ์
โ ์ [pts]์ฒ๋ผ ๋ฆฌ์คํธ ์์ ๋ฃ์๊น?
cv2.fillPoly()๋ ์ฌ๋ฌ ๊ฐ ๋ค๊ฐํ์ ํ ๋ฒ์ ์ฒ๋ฆฌํ ์ ์๋ค!!- ์๋ฅผ ๋ค์ด, ์ฌ๋ฌ ๋ค๊ฐํ์ ํ ๋ฒ์ ๊ทธ๋ฆฌ๋ ค๋ฉด ๋ค์๊ณผ ๊ฐ์ด ํ๋ฉด ๋๋ค.
cv2.fillPoly(img, [pts1, pts2, pts3], (0,0,0)) - ์ฆ, ๋จ์ผ ๋ค๊ฐํ์ด๋ผ๋ ๋ฆฌ์คํธ ์์ ๋ฃ์ด์ผ ํจ์ ๊ท๊ฒฉ์ ๋ง๋๋ค.
๋ชจ์์ดํฌ ๋ง์คํน(mosaic)
elif method == "mosaic":
""" ์์ ์ด๋ฏธ์ง๋ก ์ถ์ ํ ๋ค์ ํ๋ํ์ฌ ํฝ์
ํ (๋ชจ์์ดํฌ) """
mask = np.zeros(img.shape[:2], dtype=np.uint8)
cv2.fillPoly(mask, [pts], 255)
x, y, w, h = cv2.boundingRect(pts) # bbox ํฌ๊ธฐ ๊ณ์ฐ
roi = img[y:y+h, x:x+w]
mask_roi = mask[y:y+h, x:x+w]
if roi.size != 0:
small_w = max(1, int(w * MOSAIC_SCALE))
small_h = max(1, int(h * MOSAIC_SCALE))
small = cv2.resize(roi, (small_w, small_h))
mosaic = cv2.resize(small, (w, h), interpolation=cv2.INTER_NEAREST)
roi[mask_roi==255] = mosaic[mask_roi==255]-
๋ชจ์์ดํฌ๋ **๋์ ์์ญ(๋ค๊ฐํ)**๋ง ๋ฐ๋ก ์๋ผ์, ๊ทธ ๋ถ๋ถ์ ์์ฃผ ์๊ฒ ์ถ์ํ๋ค๊ฐ(์ ๋ณด ์์ค) ๋ค์ ์๋ ํฌ๊ธฐ๋ก ํ๋ํด์ ํฝ์ ์ ๋ญ๊ฐ๋ ๋ฐฉ์. ์์ ์ฝ๋์์
np.zeros๋ก ๋ง๋ **๋ง์คํฌ(mask)**๋ โ์ด๋์ ๋ชจ์์ดํฌ๋ฅผ ์ ์ฉํ ์งโ๋ฅผ ํ์ํ๋ ๋ํ์ง ์ญํ ์ ํ๋ค.
-
mask = np.zeros(img.shape[:2], dtype=np.unit8)img.shape[:2]๋ ์ ๋ ฅ ๋ฐ์ ์ด๋ฏธ์ง์ (height, width)๋ฅผ ๋ปํ๋ค. ์ฐธ๊ณ ๋ก OpenCV์ ์ด๋ฏธ์ง๋ (height, width, channels)์ ํํ๋ฅผ ๊ฐ๋๋ค.np.zeros(shape, dtype=None, order='C')๋ ์ฃผ์ด์ง ํํ(shape)์ ๋ฐฐ์ด์ ์์ฑํ๋ฉฐ, ๋ชจ๋ ์์๋ฅผ 0์ผ๋ก ์ด๊ธฐํํ๋ ํจ์๋ค. shape์ ์ ์ ๋๋ ์ ์๋ค์ ํํ ํํ๋ก ์ ๋ ฅ๋๋ค.- ์ฆ,
mask = np.zeros(...)๋ ๋ชจ๋ ์์๋ฅผ 0์ผ๋ก ๊ฐ๋ 2์ฐจ์ ๋ฐฐ์ด์ด๋ฉฐ, OpenCV ์ด๋ฏธ์ง์ ๊ด์ ์์๋ ์ ๋ ฅ ๋ฐ์ ์ด๋ฏธ์ง์ ๊ฐ์ ํฌ๊ธฐ์ ๋จ์ผ ์ฑ๋(2D) ์ด๋ฏธ์ง๋ค.
"๋ง์คํฌ(mask)"๋?
์ปดํจํฐ ๋น์ ์์ mask๋ โ์ด ์์ญ์ ์ฒ๋ฆฌํ๊ณ , ์ ์์ญ์ ๋ฌด์ํ๋ผโ๋ฅผ ํ์ํ๋ ์ด์ง ์ด๋ฏธ์ง(binary image)๋ค.
ยท ํฐ์(255) โ โ์ด ๋ถ๋ถ์ ํฌํจํ๋ผโ = ๊ด์ฌ ์์ญ(ROI: Region of Interest) ยท ๊ฒ์์(0) โ โ์ด ๋ถ๋ถ์ ์ ์ธํ๋ผโ = mask mask๊ฐ ๋จ์ผ ์ฑ๋(2D) ์ด๋ฏธ์ง์ธ ์ด์ ๋ ์(3์ฑ๋) ์ ๋ณด๋ ํ์ ์๊ณ , โ์ฌ๊ธฐ์ธ๊ฐ/์๋๊ฐโ๋ง ํ๋จํ๋ฉด ๋๊ธฐ ๋๋ฌธ์ด๋ค. (๋ฉ๋ชจ๋ฆฌ ์ ์ฝ ํจ๊ณผ๋ ์๋ค!)
-
cv2.fillPoly(mask, [pts], 255)- mask ์ด๋ฏธ์ง ์์์
pts(๊ด์ฌ ์์ญ์ ๊ผญ์ง์ ์ขํ๋ค)๋ก ๋๋ฌ์ธ์ธ ๋ถ๋ถ์ ํฐ์(255)์ผ๋ก ์ฑ์. - ๊ฒฐ๊ณผ์ ์ผ๋ก ์ด ๋ถ๋ถ์ด ๋ชจ์์ดํฌ ๋์(ํฐ์)์ด๋คโ ๋ผ๋ ์ ๋ณด๊ฐ mask ์์ ๋ด๊ธด๋ค.
- mask ์ด๋ฏธ์ง ์์์
-
x, y, w, h = cv2.boundingRect(pts)boundingRect()๋ ์ฃผ์ด์ง ์ ์ ๊ฐ์ธ๋ ์ต์ ํฌ๊ธฐ์ ์ฌ๊ฐํ(๋ฐ์ด๋ฉ ๋ฐ์ค)์ ๋ฐํํ๋ OpenCV์ ์ธ๊ฐ์ ํจ์๋ค. (x, y ์ข์๋จ, ๋๋น, ๋์ด)
-
roi = img[y:y+h, x:x+w]mask_roi = mask[y:y+h, x:x+w]roi๋ ์๋ณธ ์ด๋ฏธ์ง์์ ๊ด์ฌ ์์ญ๋ง ์๋ผ๋ธ ๋ถ๋ถ(๋์ด h, ๋๋น w, ์ฑ๋ 3) โ ์ฌ๊ธฐ์ ๋ชจ์์ดํฌ๋ฅผ ์ฒ๋ฆฌํ๋ค.mask_roi๋ ๋ง์คํฌ์์roi์ ๋์ผํ ์์ญ์ ์๋ผ๋ธ ๊ฒ(๋์ด h, ๋๋น w, ๋จ์ผ ์ฑ๋)
์ ์ฒด ํฐ ์ด๋ฏธ์ง์์ ์์ ํ๋ ๋์ , ์ด๋ฐ ์์ผ๋ก ๊ด์ฌ ์์ญ(ROI)๋ง ์๋ผ์ ์ฐ์ฐํ๋ฉด ์๋์ ๋ฉ๋ชจ๋ฆฌ ๋ฉด์์ ํจ์จ์ ์ด๋ค.
-
small_w = max(1, int(w * MOSAIC_SCALE))small_h = max(1, int(h * MOSAIC_SCALE))MOSAIC_SCALE์ ์๋ณธ ROI๋ฅผ ์ผ๋ง๋ ์๊ฒ ๋ง๋ค์ง ์ ํ๋ ๋น์จ ์: w=200, MOSAIC_SCALE=0.05 โ small_w=10max(1, ...)๋ ROI๊ฐ ์์ฃผ ์์์ 0์ด ๋๋ ๊ฒ์ ๋ฐฉ์งํ๊ธฐ ์ํจ(0์ด๋ฉด resize ์๋ฌ๋จ)- ์๊ฒ ์ค์ผ์๋ก ๋ชจ์์ดํฌ๊ฐ ๋์ฑ ์๋ณ ๋ถ๊ฐ๋ฅํด์ง
-
small = cv2.resize(roi, (small_w, small_h))roi(์๋ณธ ์นผ๋ผ ์ด๋ฏธ์ง)๋ฅผ(small_w, small_h)ํฌ๊ธฐ๋ก ์ถ์.
-
mosaic = cv2.resize(small, (w, h), interpolation=cv2.INTER_NEAREST)- ๋ค์ ์๋ ํฌ๊ธฐ
(w,h)๋ก ํ๋. - ๋ณด๊ฐ๋ฒ(interpolation)์ผ๋ก๋
INTER_NEAREST,INTER_AREA,INTER_CUBIC,INTER_LINEAR(๊ธฐ๋ณธ๊ฐ) ๋ฑ ๋ค์ํ๋ค.INTER_NEAREST๋ ๊ฐ์ฅ ๊ฐ๊น์ด ํฝ์ ์ ๋ณต์ ํ๋ ๋ณด๊ฐ๋ฒ์ด๋ค.
- ๋ค์ ์๋ ํฌ๊ธฐ
-
roi[mask_roi==255] = mosaic[mask_roi==255]- ์ง์ฌ๊ฐํ
roi์์์ **๋ง์คํฌ๊ฐ ํฐ์(255)**์ธ ํฝ์ ๋ค๋ง ๊ณจ๋ผ์, ๊ทธ ํฝ์ ๋ค์ ๊ฐ์ ์์น์mosaic๊ฐ์ผ๋ก ๊ต์ฒด
- ์ง์ฌ๊ฐํ
๋ธ๋ฌ ๋ง์คํน(blur)
elif method == "blur":
""" ๊ฐ์ฐ์์ ๋ธ๋ฌ ์ ์ฉ """
mask = np.zeros(img.shape[:2], dtype=np.uint8)
cv2.fillPoly(mask, [pts], 255)
x, y, w, h = cv2.boundingRect(pts)
roi = img[y:y+h, x:x+w]
mask_roi = mask[y:y+h, x:x+w]
if roi.size != 0:
blur = cv2.GaussianBlur(roi, BLUR_KERNEL, 0)
roi[mask_roi==255] = blur[mask_roi==255] cv2.GaussianBlurโ ์ง์ ํ ์ปค๋๋งํผ ์ด๋ฏธ์ง๋ฅผ ํ๋ฆฌ๊ฒ ์ฒ๋ฆฌ- ๋ชจ์์ดํฌ(mosaic)์ ๋ง์ฐฌ๊ฐ์ง๋ก mask ์์ญ๋ง ์ ์ฉ
ํ ์คํธ ๊ต์ฒด ๋ง์คํน(replace_text)
โญ ์ด ํจ์๊ฐ ํ๋ ์ผ
- ํด๋ฆฌ๊ณค(bbox) ์์ญ์ ํฐ์ ๋ฐ์ค๋ก ๋ฎ์ด ์๋ ๊ธ์๋ฅผ ์ง์ด๋ค. (๊ฒ์ ์ ๋ง์คํน๊ณผ ์๋ฆฌ ๋์ผ)
- ๊ทธ ์์ญ์ ๋นจ๊ฐ์ ๋์ฒด ํ ์คํธ ์ด๋ฏธ์ง๋ฅผ ๊ทธ๋ฆฐ๋ค.
- ํด๋ฆฌ๊ณค ์์ญ์ด ๊ธฐ์ธ์ด์ ธ ์๋ค๋ฉด ํ ์คํธ ์ด๋ฏธ์ง๋ ๊ทธ ๊ธฐ์ธ๊ธฐ์ ๋ง์ถฐ ๊ฐ์ ๊ฐ๋๋ก ํ์ ์์ผ ๋ฃ๋๋ค.
- PIL(RGBA)๋ฅผ ์ด์ฉํด ์ด๋ฏธ์ง ํฉ์ฑ ํ OpenCV(BGR) ์ด๋ฏธ์ง๋ก ๋ณํํด์ ๋ฐํํ๋ค.
elif method == "replace_text":
cv2.fillPoly(img, [pts], (255, 255, 255))
x, y, w, h = cv2.boundingRect(pts)
center_x = x + w // 2
center_y = y + h // 2
max_len = 0
angle = 0
for i in range(4):
dx = pts[(i+1)%4][0] - pts[i][0]
dy = pts[(i+1)%4][1] - pts[i][1]
length = np.hypot(dx, dy)
if length > max_len:
max_len = length
angle = -np.degrees(np.arctan2(dy, dx))
pil_img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(pil_img)
diagonal = np.hypot(w, h)
font_size = min(max(int(diagonal * 0.1), 12), h)
try:
font = ImageFont.truetype("malgunbd.ttf", font_size) # ๋ง์ ๊ณ ๋ bold
except:
font = ImageFont.load_default()
bbox_text = draw.textbbox((0, 0), REPLACE_TEXT, font=font)
text_w = bbox_text[2] - bbox_text[0]
text_h = bbox_text[3] - bbox_text[1]
offset = 5 # ํ
์คํธ ์๋ก ์ฌ๋ฆด ํฝ์
์
text_x = center_x - text_w / 2
text_y = center_y - text_h / 2 - offset
text_layer = Image.new("RGBA", pil_img.size, (255, 255, 255, 0))
text_draw = ImageDraw.Draw(text_layer)
text_draw.text((text_x, text_y), REPLACE_TEXT, font=font, fill=(255, 0, 0, 255))
rotated_layer = text_layer.rotate(angle, center=(center_x, center_y), resample=Image.BICUBIC)
pil_img = Image.alpha_composite(pil_img.convert("RGBA"), rotated_layer)
img[:] = cv2.cvtColor(np.array(pil_img.convert("RGB")), cv2.COLOR_RGB2BGR)๊ฐ๋นก์ธ๋ค
cv2.fillPoly(img, [pts], (255, 255, 255))- ์ ๋ ฅ ๋ฐ์ ์ด๋ฏธ์ง(img)์์ ์ง์ ๋ ๋ค๊ฐํ ์์ญ([pts])์ ํฐ์((255, 255, 255))์ผ๋ก ์ฑ์ฐ๋ ํจ์.
- ํ ์คํธ๋ฅผ ์ ๋ ฅํ๊ธฐ ์ํ ๋ฐฐ๊ฒฝ
center_x = x + w // 2center_y = y + h // 2- bbox ๊ฐ์ด๋ฐ ์ขํ ๊ณ์ฐ โ ํ ์คํธ๋ฅผ ์ค์์ ํ์ํ๊ธฐ ์ํจ.
max_len = 0
angle = 0
for i in range(4):
dx = pts[(i+1)%4][0] - pts[i][0]
dy = pts[(i+1)%4][1] - pts[i][1]
length = np.hypot(dx, dy)
if length > max_len:
max_len = length
angle = -np.degrees(np.arctan2(dy, dx))Info
OpenCV์์ ๊ฐ๋๋ฅผ ๋ฐํํ๋
cv2.minAreaRect()๋ฅผ ์ด์ฉํ๊ฑฐ๋,cv2.PCACompute2()์ ์ด์ฉํด ํ ์คํธ์ ๊ฐ๋๋ฅผ ๊ณ์ฐํด๋ดค์ง๋ง, ๊ฒฐ๊ณผ๊ฐ ์ข์ง ์์์.
- ๋ง์คํนํด์ผ ํ๋ ํ ์คํธ๊ฐ ๊ธฐ์ธ์ด์ ธ ์์ ๋ ๋์ฒดํ ํ ์คํธ ํ์ ์ ์ํด ๊ฐ๋๋ฅผ ๊ณ์ฐํ๊ธฐ ์ํ ๋ฃจํ
- ์ ์ฝ๋๋ **๊ฐ ๋ณ(์ฐ์๋ ์ ์)**์ ๋ํด
(dx, dy)์ ๊ทธ ๊ธธ์ดlength๋ฅผ ๊ตฌํ๊ณ , ๊ฐ์ฅ ๊ธด ๋ณ์ ์ฐพ์ ๋ค ๊ทธ ๋ณ์ ๋ฐฉํฅ์angle๋ก ์ง์ ํ๋ ๋ฃจํ๋ค.
numpy.hypot(x1, x2)์ง๊ฐ์ผ๊ฐํ์ ๋ ๋ณ์ ๊ธธ์ด๋ฅผ ์ ๋ ฅ๋ฐ์ ๋น๋ณ์ ๊ธธ์ด๋ฅผ ๊ณ์ฐํ๋ ํจ์. ์ฆ, numpy.hypot() = ๊ธธ์ด(
length)๋ฅผ ๊ตฌํ ๋, ์ด ๊ณต์์ ์ฌ์ฉํ๋ ๊ฒ์ **๋ ์ ์ฌ์ด์ ๊ฑฐ๋ฆฌ ๊ณต์ **์ ๊ทผ๊ฑฐํ๋ค.
numpy.arctan2(y, x)
arctan2(y, x)๋ 2์ฐจ์ ๋ฒกํฐ (x, y)๊ฐ **x์ถ๊ณผ ์ด๋ฃจ๋ ๋ฐฉํฅ๊ฐ(๊ฐ๋)**์ ์์ ํ๊ฒ ๊ณ์ฐํด์ฃผ๋ ํจ์.
โ ์ โ๊ฐ์ฅ ๊ธด ๋ณโ์ ๊ฐ๋๋ฅผ ์ฐ๋๊ฐ?
- ์ฃผ๋ฏผ๋ฑ๋ก๋ฒํธ์ ๊ฒฝ์ฐ, ๋ณดํต ์์ฑ๋๋ bounding box๋ ๊ฐ๋ก ๋ฐฉํฅ์ผ๋ก ๊ธธ๊ฒ ์์ฑ๋๋ค๊ณ ๊ฐ์ .
- ๋ฐ๋ผ์ ๊ฐ์ฅ ๊ธด ๋ณ์ ํ ์คํธ์ ๊ธฐ์ค์ ์ผ๋ก ๋ณด๊ณ , ๊ทธ ๋ณ์ ๋ฐฉํฅ์ผ๋ก ๋์ฒด ํ ์คํธ๋ฅผ ํ์ ์ํค๋ฉด ๋๋ค๋ ๋ ผ๋ฆฌ!
Example
pts์ ๊ฐ์ด ๋ค์๊ณผ ๊ฐ์ด ์ฃผ์ด์ก๋ค๊ณ ๊ฐ์ ํด๋ณด์.pts = np.array([[296, 188], [640, 188], [640, 264], [296, 264] ], dtype=np.int32)pts = np.array([[296, 188], [640, 188], [640, 264], [296, 264] ], dtype=np.int32)pts = np.array([[296, 188], [640, 188], [640, 264], [296, 264] ], dtype=np.int32)
์ฃผ์ด์ง pts ๊ฐ์ ๋ฐํ์ผ๋ก bounding box(bbox)๋ฅผ ๊ทธ๋ฆผ์ผ๋ก ํํํ๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
![]()
์์ ๋ฃจํ์ ๋์ผํ ๋ฐฉ์์ผ๋ก ๊ฐ ๋ณ์ ๊ณ์ฐํด๋ณด๋ฉด:
- i=0: p0โp1 = (640-296, 188-188) = (344, 0)
- length = = 344
- arctan2(dy, dx) = arctan2(0, 344) = 0.0 โ degrees = 0.0ยฐ(radian)
- angle = -0 = 0ยฐ
- i=1: p1โp2 = (640-640, 264-188) = (0, 76)
- length = 76
- arctan2 = arctan2(76, 0) = ๏ผโ 1.57 โ degrees = 90ยฐ
- angle = -90ยฐ (ํ์ง๋ง ๊ธธ์ด๊ฐ ๋ ์์ผ๋ฏ๋ก ์ฌ์ฉ๋์ง ์์)
- i=2: p2โp3 = (296-640, 264-264) = (-344, 0)
- length = 344
- arctan2 = arctan2(0, -344) โ 3.14 โ degrees = 180ยฐ
- angle = -180ยฐ
- ๊ธธ์ด๋ 344๋ก ์ต๋์ ๊ฐ์ง๋ง ์ผ๋ฐ์ ์ผ๋ก ์ฒซ ๋ฒ์งธ๋ก ๋ง๋ ์ต๋๋ฅผ ์ฌ์ฉํ๋ฏ๋ก i=0์ ๊ฐ(0ยฐ)์ด ๋จ์.
- i=3: p3โp0 = (296-296, 188-264) = (0, -76)
- length = 76
- arctan2 = arctan2(-76,0) โ -1.57 โ angle = 90ยฐ (๋ฏธ์ฌ์ฉ)
๊ฒฐ๋ก ์ ์ผ๋ก ๊ฐ์ฅ ๊ธด ๋ณ์ ๊ธธ์ด 344์ธ ๊ฐ๋ก ๋ณ โ
angle=0ยฐ
ํ ์คํธ๋ฅผ ํ์ ์ํฌ ํ์๊ฐ ์์ผ๋ฏ๋ก0ยฐ๋ฅผ ์ฌ์ฉํ๊ฒ ๋๋ค.
angle = -np.degrees(np.arctan2(dy, dx))pillow(PIL)์rotate๋ฉ์๋๋ ์์(+) ๊ฐ๋๋ฅผ ์ ๋ ฅ ๋ฐ์ผ๋ฉด, ๋ฐ์๊ณ ๋ฐฉํฅ์ผ๋ก ํ์ ํ๋ค. ๋ฐ๋ผ์angle์ ์ ์ ํ ๋ถํธ๋ฅผ ๋ง์ถฐ์ค์ผ ํ ์คํธ๊ฐ ๋์ผ ๋ฐฉํฅ์ผ๋ก ๋์ธ๋ค.- ์๋ฅผ ๋ค์ด, ๋ฐ์ค๊ฐ ์ผ์ชฝ์ผ๋ก 15ยฐ ๊ธฐ์ธ์ด์ก๋ค๋ฉด,
np.degrees(np.arctan2(dy, dx))๋ ์ฝ -15ยฐ๋ก ๋์ค๊ณ ,angle = 15๊ฐ ๋๋ค. ์ด๋pillow(PIL)์rotate์ ์ํด ๋์ฒด ํ ์คํธ๊ฐ ๋ฐ์๊ณ ๋ฐฉํฅ์ผ๋ก 15ยฐ ํ์ ํ๊ฒ ๋๋ฉด์ ๋ฐ์ค์ ๋์ฒด ํ ์คํธ๊ฐ ๋์ผ ๋ฐฉํฅ์ผ๋ก ๋์ด๊ฒ ๋๋ค.
โ ๏ธ ๊ฐ๋ ๊ณ์ฐ ๋ฃจํ์ ํ๊ณ
- ๋ค์๊ณผ ๊ฐ์ ์ํฉ์์ ์ ๋ฃจํ๋ ์คํจํ๋ค.
- ์ธ๋ก ๋ฐฉํฅ์ผ๋ก ๊ธธ๊ฒ ๋์ด๋ ํ ์คํธ(ํ์ง๋ง ์ ์ด์ EasyOCR์ด ์ธ๋ก ๋ฐฉํฅ ํ ์คํธ ์ธ์์ ์ฝํ๋ค.)
- ๋น์ ํ polygon(๋ค ์ ์ด ์ฌ๊ฐํ ์์๋ก ์ค์ง ์๋ ๊ฒฝ์ฐ)
- ์ ์ฌ๊ฐํ polygon (๊ฐ๋ฅ์ฑ์ด ๊ฑฐ์ ์๊ธด ํ๋ค)
- ๋ ์ข์ ๋ฐฉ์์ ์๊ฐํด๋ณผ ํ์๊ฐ ์๋ค..
# PIL ์ด๋ฏธ์ง ์์ฑ
pil_img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(pil_img)
diagonal = np.hypot(w, h)
font_size = min(max(int(diagonal * 0.1), 12), h)
# ํฐํธ ์ ํ๊ณผ ๋ก๋
try:
font = ImageFont.truetype("malgunbd.ttf", font_size) # ๋ง์ ๊ณ ๋ bold
except:
font = ImageFont.load_default() pil_img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))- OpenCV๋
img๋numpy.ndarray์ด๊ณ ์ ์ฑ๋ ์์๋ BGR, dtype์ ๋ณดํตunit8 - PIL(pillow)์ ์ ์ฑ๋ ์์๊ฐ RGB(๊ทธ๋ฆฌ๊ณ RGBA)๋ค.
- ๋ฐ๋ผ์ OpenCV โ PIL ๋ณํ ์ ๋ฐ๋์
cv2.cvtColor(img, cv2.COLOR_BGR2RGB)๋ก BGR โ RGB ๋ณํ์ ํ๊ณ , numpy ๋ฐฐ์ด์Image.fromarray()๋ฅผ ํตํด PIL ์ด๋ฏธ์ง๋ก ๋ณํํด์ผ ์์ด ์ฌ๋ฐ๋ฅด๊ฒ ๋ณด์ธ๋ค.
- OpenCV๋
draw = ImageDraw.Draw(pil_img)- ๋์ฒด ํ ์คํธ ์ด๋ฏธ์ง๋ฅผ ๊ทธ๋ฆฌ๊ธฐ ์ํ PIL ImageDraw ๊ฐ์ฒด ์์ฑ.
Image.fromarray(...)์ ๊ณผ์ ์numpy.ndarray์ด๋ฏธ์ง๋ฅผ Pillow๊ฐ ์ดํดํ ์ ์๋PIL.Image๊ฐ์ฒด๋ก ๋ณํํ๋ ๊ณผ์ ์ด์๋ค๋ฉด,ImageDraw.Draw()๋ ๊ทธ ์ด๋ฏธ์ง ์์ ๋ค๋ฅธ ์ด๋ฏธ์ง๋ฅผ ๊ทธ๋ฆฌ๊ธฐ ์ํ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ ์ญํ ์ ํ๋ค.
diagonal = np.hypot(w, h)font_size = min(max(int(diagonal * 0.1), 12), h)diagonal์ ํด๋ฆฌ๊ณค ์์ญ์ ๋๊ฐ์ ๊ธธ์ดint(diagonal * 0.1): ๋๊ฐ์ ๊ธธ์ด์ 10%๋ฅผ ํฐํธ ํฌ๊ธฐ๋ก.max(..., 12): ์๋ฌด๋ฆฌ ์์ ์ด๋ฏธ์ง๋ผ๋ ์ต์ 12px์ด์ ํฐํธ ํฌ๊ธฐ ์ ์ง.min(..., h): ํฐํธ ํฌ๊ธฐ๊ฐ ์ด๋ฏธ์ง ๋์ด๋ณด๋ค ์ปค์ง๋ ๊ฑธ ๋ฐฉ์ง.
bbox_text = draw.textbbox((0, 0), REPLACE_TEXT, font=font)
text_w = bbox_text[2] - bbox_text[0]
text_h = bbox_text[3] - bbox_text[1]
ImageDraw.textbbox((x,y), text, font, ...)์ฐธ์กฐ๋ฉ์๋ ์ด๋ฆ ๊ทธ๋๋ก ํ ์คํธ๋ฅผ ๊ทธ๋ ธ์ ๋์ ๋ฐ์ด๋ฉ ๋ฐ์ค ์ขํ๋ฅผ ๋ฐํํ๋ค. ๋ฐํ๊ฐ์ ๋ค์๊ณผ ๊ฐ์ 4-ํํ ํํ๋ค.
(x0, y0, x1, y1)
(x0, y0): ๋ฐ์ด๋ฉ ๋ฐ์ค์ ์ผ์ชฝ ์ ์ขํ
(x1, y1): ๋ฐ์ด๋ฉ ๋ฐ์ค์ ์ค๋ฅธ์ชฝ ์๋ ์ขํ๊ณต์ ๋ฌธ์์์๋ โ
(left,ย top,ย right,ย bottom)ย bounding boxโ ์ ๋ฐํํ๋ค๊ณ ์ค๋ช ํ๋ค.OpenCV, PIL ๋ฑ์์ ์ฌ์ฉํ๋ ์ขํ๊ณ๋ ์ํ ์ขํ๊ณ์ ๋ค๋ฅด๋ค!
ImageDraw.textbbox(...)์ ๋ฐํ๊ฐ์ ์ถ๋ ฅํด๋ณด๋ฉด ๋ค์์ ์์์ ๊ฐ์ ๊ฐ์ด ์ถ๋ ฅ๋๋ค.(0, 13, 229, 45))์ด๋ฌํ ๋ฐํ๊ฐ์ ์ํ์ ์ขํ๊ณ ๊ด์ ์์ ๋ฐ๋ผ๋ณด๋ฉด ๋ง์ด ๋์ง ์๋๋ค. ์๋ํ๋ฉด ์ผ์ชฝ(left)์ ์๋
x0์ ๊ฐ์ด ์ค๋ฅธ์ชฝ(right)์ ์๋x1์ ๊ฐ ๋ณด๋ค ์์ ๊ฒ์ ์ดํด๊ฐ ๊ฐ์ง๋ง, ์(top)์ ์๋y0์ ๊ฐ์ด ์๋(bottom)์ ์๋y1์ ๊ฐ๋ณด๋ค ์์ ๊ฒ์ ์ดํด๊ฐ ๋์ง ์๊ธฐ ๋๋ฌธ์ด๋ค. ์ด๋ OpenCV, Pillow(=PIL), ๊ทธ๋ฆฌ๊ณ ์ํ์ ์ขํ๊ณ๊ฐ ์๋ก โy์ถ ๋ฐฉํฅโ์ ๋ค๋ฅด๊ฒ ์ฐ๊ธฐ ๋๋ฌธ์ ์๊ธฐ๋ ํผ๋์ด๋ค.
- ๐ ์ํ์ ์ขํ๊ณ(์ฐ๋ฆฌ์๊ฒ ์ต์ํ ์ขํ๊ณ)
โ y (์๋ก ์ฆ๊ฐ) | | (0,0)โโโโโโโโโโโโ x (์ค๋ฅธ์ชฝ์ผ๋ก ์ฆ๊ฐ)
- y๊ฐ์ด ์๋ก ๊ฐ์๋ก ์ปค์ง โ ๊ทธ๋์ โ์๋์ ์๋ ์ โ์ y๊ฐ์ด ์์
- ๐ ์ด๋ฏธ์ง ์ขํ๊ณ
(0,0) โโโโโโโโโโ x (์ค๋ฅธ์ชฝ์ผ๋ก ์ฆ๊ฐ) | | โ y (์๋๋ก ์ฆ๊ฐ)
- ๋๋ถ๋ถ์ ์ด๋ฏธ์ง ์ฒ๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ(Pillow, OpenCV, numpy ๋ฐฐ์ด ๋ฑ)๋ ํ๋ฉด์ ๋งจ ์ ์ผ์ชฝ์ (0,0) ์ผ๋ก ๋๋ค.
- ์ฆ, y๊ฐ์ด ์๋๋ก ๊ฐ์๋ก ์ปค์ง (ํ๋ฉด์ ์์์ ์๋๋ก ์ค์บํ๋๊น!)
๋ฐ๋ฉด์ EasyOCR์ ์ํ์ ์ขํ๊ณ๋ฅผ ์ฌ์ฉํ๋ค. EasyOCR์ด ๋ฐํํ๋ ๋ฐ์ด๋ฉ ๋ฐ์ค์ ์ขํ(์ฝ๋์์๋
pts๊ฐ)๋ ์ํ์ ์ขํ๋ฅผ ์ฌ์ฉํ๋ ๊ฑธ ํ์ธํ ์ ์๋ค. ์ด๋ฏธ์ง ์ฒ๋ฆฌํ ๋ ๊ฐ์ฅ ํผ๋ํ๋ ํฌ์ธํธ ์ค ํ๋์ด๋ฏ๋ก ํท๊ฐ๋ฆฌ์ง ๋ง์~
ImageDraw.textbbox()๊ฐ ๋ฐํํ ๊ฐ์ ๋ฐํ์ผ๋ก ํ ์คํธ์ ๋๋น์ ๋์ด๋ฅผ ๊ตฌํ๋ค.
offset = 5 # ํ
์คํธ ์๋ก ์ฌ๋ฆด ํฝ์
์
text_x = center_x - text_w / 2
text_y = center_y - text_h / 2 - offsetcenter_x, center_y๋ bbox ์ค์ฌ์ ์ขํ.text_x = center_x - text_w / 2- ํ ์คํธ๋ฅผ ์ํ ์ค์ ์ ๋ ฌ: bbox์ ์ค์ฌ์์ ํ ์คํธ ๋๋น์ ์ ๋ฐ๋งํผ ์ผ์ชฝ์ ๋์.
text_y = center_y - text_h / 2 - offset- ํ
์คํธ๋ฅผ ์์ง ์ค์ ์ ๋ ฌํ,
offset๋งํผ ์๋ก ์ฌ๋ฆผ. - ์ด๋ฏธ์ง ์ขํ๊ณ์์ y๊ฐ์ ์๋๋ก ์ฆ๊ฐํ๋ฏ๋ก
-offset์ ์๊ฐ์ ์ผ๋ก ํ ์คํธ๋ฅผ ์๋ก ์ฌ๋ฆฌ๋ ๋์์ด๋ค. - ํ
์คํธ ๊ฒฐ๊ณผ ๋ฐ์ด๋ฉ๋ฐ์ค์ ์ค์ฌ์ ๋ฑ ๋ง์ถฐ ๋ฃ์ผ๋ฉด ํ
์คํธ๊ฐ bbox์ ์ค์๋ณด๋ค ์ด์ง ์๋์ ์์นํ๊ธฐ ๋๋ฌธ์
offset์ผ๋ก ํ ์คํธ๋ฅผ ์กฐ๊ธ ์๋ก ์ด๋์ํค๋ ๊ฒ.
- ํ
์คํธ๋ฅผ ์์ง ์ค์ ์ ๋ ฌํ,
# ํ
์คํธ ๋ ์ด์ด ์์ฑ
text_layer = Image.new("RGBA", pil_img.size, (255, 255, 255, 0))
text_draw = ImageDraw.Draw(text_layer)
text_draw.text((text_x, text_y), REPLACE_TEXT, font=font, fill=(255, 0, 0, 255))
# ํ
์คํธ ๋ ์ด์ด ํ์
rotated_layer = text_layer.rotate(angle, center=(center_x, center_y), resample=Image.BICUBIC)โ ์ ํ ์คํธ ๋ ์ด์ด๋ฅผ ๋ง๋๋?
- ๋ชฉ์ : ์๋ณธ ์ด๋ฏธ์ง์ ์ง์ ํ ์คํธ๋ฅผ ๊ทธ๋ฆฌ์ง ์๊ณ , โํฌ๋ช ํ ์บ๋ฒ์ค(RGBA)โ ์์ ํ ์คํธ๋ง ๊ทธ๋ฆฌ๊ธฐ ์ํด.
- ์ด์ :
- PIL ์ด๋ฏธ์ง๋ฅผ ์ด์ฉํ๋ ๊ทผ๋ณธ์ ์ธ ์ด์ ๋
- OpenCV๋ฅผ ์ด์ฉํ ๊ฒฝ์ฐ, ํ๊ธ ํฐํธ ์ฌ์ฉ ๋ถ๊ฐ๋ฅ.
- OpenCV๋ฅผ ์ด์ฉํ ๋์ฒด ํ ์คํธ ์์ฑ ์, ํ ์คํธ์ ํ์ง์ด ๋งค์ฐ ์ข์ง ์์ ๊ฒ์ด ํ์ธ๋จ.
- ๋ฐ๋ผ์ ํ ์คํธ ๋ ์ด์ด๋ฅผ ์์ฑ ํ, ํฉ์ฑํ๋ ๋ฐฉ์์ ํตํด ํ๊ธ ํฐํธ๋ฅผ ์ฌ์ฉํด์ผ ํ๋ ๊ฒฝ์ฐ๋ ๋๋นํ๊ณ , ํ ์คํธ ํ์ง ์ ํ ๋ฌธ์ ๋ ํด๊ฒฐ.
- PIL ์ด๋ฏธ์ง๋ฅผ ์ด์ฉํ๋ ๊ทผ๋ณธ์ ์ธ ์ด์ ๋
text_layer = Image.new("RGBA", pil_img.size, (255, 255, 255, 0))RGBA: A = ์ํ ์ฑ๋ ํฌ๋ช ๋(255, 255, 255, 0)์ ์์ ํ ํฌ๋ช ์์ ์๋ฏธ - ์ฆ ์๋ฌด ๊ฒ๋ ๊ทธ๋ ค์ง์ง ์์ ํฌ๋ช ์บ๋ฒ์ค
text_draw.text((text_x, text_y), REPLACE_TEXT, font=font, fill=(255, 0, 0, 255))fill=(255, 0, 0, 255)์์ ๋ง์ง๋ง ๊ฐ255๋ ์ํ=๋ถํฌ๋ช (์์ ํ์). ์ฆ, ํ ์คํธ๋ ๋ถํฌ๋ช ํ ๋นจ๊ฐ์์ผ๋ก ๊ทธ๋ ค์ง.
rotated_layer = text_layer.rotate(angle, center=(center_x, center_y), resample=Image.BICUBIC)angle: ํ ์คํธ ํ์ ๊ฐ๋ ๊ณ์ฐ ๋ฃจํ๋ฅผ ํตํด ๊ตฌํ ๊ฐ๋.center=(center_x, center_y): ํ์ ์ ์ค์ฌ์ resample: ํ์ ์ ์ฌ์ฉํ ๋ณด๊ฐ๋ฒ์ ์ง์ ํ๋ ํ๋ผ๋ฏธํฐ. ์ฃผ์ ์ต์ ์ ๋ค์๊ณผ ๊ฐ๋ค.Image.NEAREST: ๊ฐ์ฅ ๊ฐ๊น์ด ํฝ์ ๋ณต์ (๊ณ์ฐ ๋น ๋ฆ, ์์ผ๋ฆฌ์ด์ฑ ์ฌํจ)Image.BILINEAR: 2ร2 ์ ํ ๋ณด๊ฐ (๋ถ๋๋ฌ์ โ)Image.BICUBIC: 4ร4 ๋นํ๋น ๋ณด๊ฐ (๋ ๋ถ๋๋ฝ๊ณ ํ์ง ์ข์)Image.LANCZOS: ๊ณ ๊ธ ํํฐ (๋ณดํต ์ถ์์์ ์ข์)
- ํ
์คํธ๋ฅผ ํ์ ํ๋ฉด ๊ณ๋จํ์(aliasing)์ด ์๊ธฐ๊ธฐ ์ฌ์.
BICUBIC์ ํฝ์ ๊ฒฝ๊ณ๋ฅผ ๋ถ๋๋ฝ๊ฒ ๋ณด๊ฐํด ํ์ ํ ํ ์คํธ๊ฐ ์ข ๋ ์ผ๊ธฐ ์ข๊ฒ ๋ณด์ด๋๋ก ํด์ฃผ๋ฏ๋ก ํด๋น ์ต์ ์ฌ์ฉ.
pil_img = Image.alpha_composite(pil_img.convert("RGBA"), rotated_layer)
img[:] = cv2.cvtColor(np.array(pil_img.convert("RGB")), cv2.COLOR_RGB2BGR)
Image.alpha_composite(base, overlay)์๋ ์๋ฆฌ
alpha_composite๋ ๋ RGBA ์ด๋ฏธ์ง๋ฅผ ๋ฐ์์ ์ ๊ฒฝ(over) ์ํ ์ฑ๋์ ๋ฐ๋ผ ํฝ์ ๋จ์๋ก ํฉ์ฑ์์งํ ์ ๋ชจ๋ฆ- ์ ์ ์กฐ๊ฑด: ๋ ์ด๋ฏธ์ง์ ํฌ๊ธฐ๊ฐ ๊ฐ์์ผ ํจ(
pil_img.size์ ๋์ผ ํฌ๊ธฐ์text_layer๋ฅผ ๋ง๋ ์ด์ )pil_img.convert("RGBA"):
- ์๋ณธ PIL ์ด๋ฏธ์ง๊ฐ RGB์ด๋ฉด ์ํ ์ฑ๋์ด ์์ผ๋ฏ๋ก RGBA๋ก ๋ฐ๊ฟ์ผ ํฉ์ฑ ๊ฐ๋ฅ.
convert("RGBA")๋ก ์ํ=255(๋ถํฌ๋ช )์ธ base๊ฐ ๋ง๋ค์ด์ง.
alpha_composite๋ฅผ ์ฐ๋ ์ด์
rotated_layer๋ ํ ์คํธ ์์ญ์ด ๋ถํฌ๋ช (์: ์ํ=255)์ด๊ณ , ๋๋จธ์ง ์์ญ์ ํฌ๋ช (์ํ=0)์ธ RGBA ์ด๋ฏธ์ง.- ์ด ๋ ์ด์ด๋ฅผ base์ alpha_compositeํ๋ฉด ํ ์คํธ๋ง base์ ๋ฎ์ด๊ณ , ํฌ๋ช ๋ถ๋ถ์ base๊ฐ ์ ์ง๋๋ฏ๋ก ๊นจ๋ํ๊ณ ๋นํ๊ดด์ ์ธ ๋ฐฉ๋ฒ. โ ํ ์คํธ ์ด๋ฏธ์ง๋ง ๊น๋ํ๊ฒ ๋ฎ์ธ๋ค~
img[:] = cv2.cvtColor(np.array(pil_img.convert("RGB")), cv2.COLOR_RGB2BGR)pil_img.convert("RGB"): RGBA โ RGB๋ก ๋ฐ๊พผ ๋ค์ numpy๋ก ๋ณํํ๋ค.cv2.cvtColor(..., cv2.COLOR_RGB2BGR): Pillow๋ RGB, OpenCV๋ BGR ์ด๋ฏ๋ก ์ฑ๋ ์์๋ฅผ ๋ง์ถฐ์ค๋ค.img[:] = ...: ์๋ณธimg(NumPy ๋ฐฐ์ด)๋ฅผ ์ ์ฒด ๋ฐ๊พผ๋ค.
combine_bboxes ํจ์
def combine_bboxes(bbox1, bbox2):
xs1 = [p[0] for p in bbox1]
ys1 = [p[1] for p in bbox1]
xs2 = [p[0] for p in bbox2]
ys2 = [p[1] for p in bbox2]
x_min = min(min(xs1), min(xs2))
x_max = max(max(xs1), max(xs2))
y_min = min(min(ys1), min(ys2))
y_max = max(max(ys1), max(ys2))
return [[x_min, y_min], [x_max, y_min], [x_max, y_max], [x_min, y_max]] โน๏ธ ๊ธฐ๋ฅ
- EasyOCR์ด ์ฃผ๋ฏผ๋ฑ๋ก๋ฒํธ๋ฅผ ๋ ์กฐ๊ฐ์ผ๋ก ์ธ์ํ๋ ๊ฒฝ์ฐ๋ฅผ ๋๋นํ๊ธฐ ์ํ ํจ์
๐งฉ ์ํฉ ๋ฐฐ๊ฒฝ: EasyOCR์ ๋ถ๋ฆฌ ์ธ์ ๋ฌธ์ ์๋ฅผ ๋ค์ด ์ด๋ฏธ์ง ์์ ์๋์ ๊ฐ์ ์ฃผ๋ฏผ๋ฑ๋ก๋ฒํธ๊ฐ ์๋ค๊ณ ํ์.
123456-7891011
EasyOCR์ด ์ฃผ๋ฏผ๋ฑ๋ก๋ฒํธ๋ฅผ ์ธ์ํ๋ ๊ณผ์ ์์ ๋ค์๊ณผ ๊ฐ์ด ์ธ์ํ๋ ๊ฒฝ์ฐ๊ฐ ์๋ค.
| ์ธ์๋ ํ ์คํธ | bbox(์ฌ๊ฐํ ์ขํ) | ์ค๋ช |
|---|---|---|
123456 | bbox1 | ์ 6์๋ฆฌ๋ง ์ธ์ |
7891011 | bbox2 | ๋ค 7์๋ฆฌ๋ง ์ธ์ |
์ด๋ ๊ฒ ๋๋์ด ์ธ์ํ๊ฒ ๋๋ฉด ํด๋น ํ ์คํธ๊ฐ ์ฃผ๋ฏผ๋ฑ๋ก๋ฒํธ์ธ์ง ์ ๊ท์์ผ๋ก ๊ฑธ๋ฌ๋ผ ์๊ฐ ์๋ค. ๋ฐ๋ผ์
- ์์๋ฆฌ ํ๋ณด(
front_parts)์ ๋ท์๋ฆฌ ํ๋ณด(back_parts)๋ฅผ ๊ฐ๊ฐ ๋ฐ๋ก ์์ง - x์ถ ๊ฑฐ๋ฆฌ(
MAX_X_DISTANCE)์ y์ถ ๊ฑฐ๋ฆฌ(MAX_Y_DISTANCE)๋ฅผ ๊ธฐ์ค์ผ๋ก โ๋ถ์ด ์๋์งโ ํ๋ณ (์์๋ฆฌ ํ๋ณด์ ๋ท์๋ฆฌ ํ๋ณด๊ฐ ๋๋ฌด ๋ฉ๋ฆฌ ๋จ์ด์ ธ ์๋ค๋ฉด ์ฃผ๋ฏผ๋ฑ๋ก๋ฒํธ๊ฐ ์๋๋ผ๊ณ ํ์ ) - โ๋ถ์ด ์๋คโ๊ณ ํ๋จ๋๋ฉด ๋ bbox๋ฅผ ํ๋๋ก ๊ฒฐํฉ(
combine_bboxes(bbox1, bbox2)์ฌ์ฉ)
โ๏ธ ์๋ ๋ฐฉ์
def combine_bboxes(bbox1, bbox2):
xs1 = [p[0] for p in bbox1]
ys1 = [p[1] for p in bbox1]
xs2 = [p[0] for p in bbox2]
ys2 = [p[1] for p in bbox2]bbox ์์
bbox = [[np.int32(296), np.int32(188)], [np.int32(640), np.int32(188)], [np.int32(640), np.int32(264)], [np.int32(296), np.int32(264)]]
- ๊ฐ bbox์ x, y ์ขํ๋ค์ ๊ฐ๊ฐ ๋ถ๋ฆฌํด ๋ฆฌ์คํธ๋ก ์ถ์ถ
x_min = min(min(xs1), min(xs2))
x_max = max(max(xs1), max(xs2))
y_min = min(min(ys1), min(ys2))
y_max = max(max(ys1), max(ys2)) - ๋ bbox์ ๋ชจ๋ ์ ์ ๋น๊ตํ์ฌ,
x_min,x_max,y_min,y_max๊ฐ์ ๊ตฌํ๋ค. - ๋ ํ ์คํธ๋ฅผ ์์ ํ ๋ฎ๋ ์ต์ ํฌ๊ธฐ์ ์ง์ฌ๊ฐํ์ ๊ผญ์ง์ ์ขํ๋ ๋ค์๊ณผ ๊ฐ๋ค.
return [[x_min, y_min], [x_max, y_min], [x_max, y_max], [x_min, y_max]]๋ฉ์ธ ์ฒ๋ฆฌ ํจ์
process_image ํจ์
def process_image(image_path, reader):โญ ์ด ํจ์๊ฐ ํ๋ ์ผ
- ์ด๋ฏธ์ง๋ฅผ ์
๋ ฅ ๋ฐ์(
image_path) EasyOCR๋ก ์ด๋ฏธ์ง์์ ํ ์คํธ๋ฅผ ์ฝ๊ณ - ์ฃผ๋ฏผ๋ฑ๋ก๋ฒํธ(์ ์ฒด ์ธ์๋๊ฑฐ๋, ๋ถ๋ฆฌ ์ธ์๋ ๊ฒ)๋ฅผ ์ ๊ท์์ผ๋ก ์ฐพ์
- ํด๋น ๋ถ๋ถ์ ๋ง์คํน(
mask_bbox()) ์ฒ๋ฆฌํ ๋ค - ๊ฒฐ๊ณผ ์ด๋ฏธ์ง๋ฅผ ์ ์ฅํ๋ ํต์ฌ ํจ์
์ด๋ฏธ์ง ์ฝ๊ธฐ ๋ฐ ์์ธ ์ฒ๋ฆฌ
image = cv2.imread(image_path)
if image is None or image.size == 0:
print(f"[ERROR] ์ด๋ฏธ์ง๋ฅผ ์ฝ์ ์ ์์: {image_path}")
return 0image = cv2.imread()โ OpenCV๋ก ์ ๋ ฅ ์ด๋ฏธ์ง ์ฝ๊ธฐ- ํ์ผ์ด ๊นจ์ ธ ์๊ฑฐ๋ ์๋ ๊ฒฝ์ฐ๋ฅผ ๋๋นํด ์์ธ ์ฒ๋ฆฌ.
cv2.imread()๋ ์คํจ ์None์ ๋ฐํํ๊ธฐ ๋๋ฌธ์ ์ด ์กฐ๊ฑด๋ฌธ์ผ๋ก ์์ ์ฑ ํ๋ณด.- ์ด ํจ์์ ๋ฐํ๊ฐ(
found)์ โํ์ง๋ ์ฃผ๋ฏผ๋ฒํธ ์โ์ด๋ฏ๋ก, 0์ โ์์โ์ผ๋ก ์๋ฏธ ์๊ฒ ์๋
์ด๋ฏธ์ง ์ฝ๊ธฐ ๋ฐ ์์ธ ์ฒ๋ฆฌ
try:
ocr_results = reader.readtext(image, low_text=OCR_LOW_TEXT)
except Exception as e:
print(f"[ERROR] EasyOCR ์คํ ์ค ์ค๋ฅ ๋ฐ์: {e}")
return 0- EasyOCR ๋ชจ๋ธ(
reader.readtext())์ ์ด์ฉํด ์ด๋ฏธ์ง ๋ด์ ๋ชจ๋ ํ ์คํธ ๋ฐ์ค์ ์ธ์ ๊ฒฐ๊ณผ ์ป์. ocr_results๋ ๋ฆฌ์คํธ ํํ์ด๋ฉฐ, ๊ฐ ํญ๋ชฉ์(bbox, text, confidence)ํํ.ocr_results์์[(([[np.int32(87), np.int32(215)], [np.int32(119), np.int32(215)], [np.int32(119), np.int32(235)], [np.int32(87), np.int32(235)]], '์ฃผ๋ฏผ๋ฑ๋ก์ฆ', np.float64(0.35778852344696405)), ([[np.float64(16.990464937253567), np.float64(101.67997636823249)], [np.float64(180.36775881930328), np.float64(36.506987062360956)], [np.float64(198.00953506274644), np.float64(93.32002363176751)], [np.float64(34.63224118069672), np.float64(158.49301293763904)]], 'ํ๊ธธ๋', np.float64(0.12592596196615638)), ...)]- ์ค๋ฅ๊ฐ ๋ ๊ฐ๋ฅ์ฑ์ ๋๋นํ๊ธฐ ์ํด try-except๋ก ๊ฐ์ธ ์์ ์ฑ ํ๋ณด.
์ด๊ธฐ ๋ณ์ ์ค์
found = 0
masked_bboxes_coords = [] found: ํ์ฌ ์ด๋ฏธ์ง์์ ๋ง์คํน๋ ์ฃผ๋ฏผ๋ฑ๋ก๋ฒํธ์ ๊ฐ์ ์นด์ดํธmasked_bboxes_coords: ๋์ค์ ๋ง์คํน๋ bbox๋ค์ ์ขํ๋ฅผ ์ ์ฅํ ๋ฆฌ์คํธ
์์ ํ ์ฃผ๋ฏผ๋ฑ๋ก๋ฒํธ ํจํด ํ์ง
for bbox, text, conf in ocr_results:
m_combined = rrn_combined_regex.search(text)
if m_combined:
image = mask_bbox(image, bbox, method=MASK_METHOD)
found += 1
masked_bboxes_coords.append(bbox)
print(f"[MASKED] Combined RRN='{m_combined.group()}' | conf={conf:.2f}")
continue
m_nohyphen = rrn_nohyphen_regex.search(text)
if m_nohyphen:
image = mask_bbox(image, bbox, method=MASK_METHOD)
found += 1
masked_bboxes_coords.append(bbox)
print(f"[MASKED] NoHyphen RRN='{m_nohyphen.group()}' | conf={conf:.2f}")
continue
re.search(pattern, text(string), flags=0)text๊ฐ pattern(์ ๊ท ํํ์ ํจํด)๊ณผ ์ผ์นํ๋์ง ๊ฒ์ํ๋ ๋ฉ์๋. flags๋ ์ ํ์ ์ธ์๋ก ์ ๊ท ํํ์์ ๋์์ ์์ ํ๋ ํ๋๊ทธ. ์ ๊ทํํ์๊ณผ ํจํด์ด ์ผ์นํ๋ ๊ฒฝ์ฐ์
re.match๊ฐ์ฒด ๋ฐํ, ์ผ์นํ์ง ์์ผ๋ฉดNone๋ฐํ.๋ฐํ๊ฐ ์์
rrn_combined_regex = re.compile(r"\b\d{6}\s*-\s*\d{7}\b") text_1 = "501111-1234566" text_2 = "์ฃผ๋ฏผ๋ฑ๋ก์ฆ" m_combined_1 = rrn_combined_regex.search(text) m_combined_2 = rrn_combined_regex.search(text_2) print(m_combined_1) print(m_combined_2)์์ ์ฝ๋๋ฅผ ์คํํ๊ฒ ๋๋ฉด ๋ค์๊ณผ ๊ฐ์ ๊ฒฐ๊ณผ๊ฐ์ ์ป๋๋ค.
<re.Match object; span=(0, 14), match='501111-1234566'> None์ฐธ๊ณ ๋ก
group()์ ๊ฒ์๋ ๊ฐ์ฒด ์์ฒด๋ฅผ ๊ฐ์ ธ์ฌ ์ ์๋ ๋ฉ์๋๋ค. ์๋ฅผ ๋ค์ด,m_combined_1.group()์๋ฅผ ์คํํ๋ฉด ๊ฒฐ๊ณผ๋ ๋ค์๊ณผ ๊ฐ๋ค.
'501111-1234566'
โน๏ธ ๊ธฐ๋ฅ
- EasyOCR ๊ฒฐ๊ณผ ์ค โ์ฃผ๋ฏผ๋ฑ๋ก๋ฒํธ ์ ์ฒด(6์๋ฆฌ-7์๋ฆฌ)โ๊ฐ ๋ค์ด ์๋ ํ ์คํธ ๊ฒ์.
- 2๊ฐ์ ์ ๊ท์ ์ฌ์ฉ
rrn_combined_regexโ -rrn_nohyphen_regexโ (ํ์ดํ ์๋ 13์๋ฆฌ)
None์ด ์๋re.match๊ฐ์ฒด ๋ฐํ ์mask_bbox()ํธ์ถ โ ์ง์ ๋ ๋ฐฉ์(๊ฒ์ , ๋ชจ์์ดํฌ, ๋ธ๋ฌ, ํ ์คํธ ๊ต์ฒด)๋ก ๋ง์คํน ์ํcontinue๋ฅผ ์จ์ ํ ๋ฒ ๋ง์คํน๋ bbox๋ ๋ค์ ๊ฒ์ฌ ์๋ต โ ์ค๋ณต ๋ง์คํน ๋ฐฉ์ง (์๋ง java์continue์ ๊ธฐ๋ฅ์ ์ผ๋ก ์ผ์น)continue๋ ๋ฐ๋ณต๋ฌธ(for, while)์์ ์๋์ ์ฝ๋๋ฅผ ์คํ ์ํค์ง ์๊ณ , ๋ค์ ๋ฐ๋ณต ๊ตฌ๋ฌธ์ผ๋ก ๋์ด๊ฐ๋๋ก ํ๋ ์ ์ด์
๋ถ๋ฆฌ ์ธ์๋ ์ฃผ๋ฏผ๋ฒํธ ํ์ง
front_parts, back_parts = [], []
for bbox, text, conf in ocr_results:
clean_text = re.sub(r'[- ]', '', text)
if rrn_front_regex.search(clean_text) and len(clean_text) == 6:
front_parts.append({'bbox': bbox, 'text': text, 'conf': conf})
elif rrn_back_regex.search(clean_text) and len(clean_text) == 7:
back_parts.append({'bbox': bbox, 'text': text, 'conf': conf})
re.sub(pattern, replace, text)text ์ค pattern์ ํด๋นํ๋ ๋ถ๋ถ์ replace๋ก ๋์ฒดํ๋ ๋ฉ์๋.
clean_text = re.sub(r'[- ]', '', text): ํ์ดํ(-)์ด๋ ๊ณต๋ฐฑ์ ์ ๊ฑฐํด์ ์ซ์๋ง ๋จ๊น โ"123456 -"์ ๊ฐ์ ํ ์คํธ๋ฅผ"123456"์ผ๋ก ๋ณํ- **rrn_front_regex(6์๋ฆฌ)์ rrn_back_regex(7์๋ฆฌ)**์ ์ํด ์ซ์ ๊ธธ์ด์ ํจํด์ ๋ง์กฑํ๋ ํญ๋ชฉ์ ๊ฐ๊ฐ
front_parts์back_parts์ ๋ณด๊ด - ๊ฐ ํญ๋ชฉ์
{'bbox': bbox, 'text': text, 'conf': conf}ํํ๋ก ์ ์ฅ๋์ด ์ขํ์ ์๋ฌธ์ ์ ์ง
์/๋ค ํ์ด๋ง ๋ฐ bbox ๊ฒฐํฉ
masked_fronts = set()
masked_backs = set()
for i, front in enumerate(front_parts):
if i in masked_fronts:
continue
front_xs = [p[0] for p in front['bbox']]
front_ys = [p[1] for p in front['bbox']]
front_x_max = max(front_xs)
front_y_center = (min(front_ys) + max(front_ys)) / 2
for j, back in enumerate(back_parts):
if j in masked_backs:
continue
back_xs = [p[0] for p in back['bbox']]
back_ys = [p[1] for p in back['bbox']]
back_x_min = min(back_xs)
back_y_center = (min(back_ys) + max(back_ys)) / 2
x_distance = back_x_min - front_x_max
y_diff = abs(front_y_center - back_y_center)
if 0 <= x_distance <= MAX_X_DISTANCE and y_diff <= MAX_Y_DIFFERENCE:
combined_bbox = combine_bboxes(front['bbox'], back['bbox'])
image = mask_bbox(image, combined_bbox, method=MASK_METHOD)
found += 1
# ์ค๋ณต ๋ฐฉ์ง
masked_fronts.add(i)
masked_backs.add(j)
combined_text = f"{front['text']}-{back['text']}"
print(f"[MASKED] Combined (Split) RRN='{combined_text}' | dist={x_distance:.1f} | y_diff={y_diff:.1f}")
breakโน๏ธ ๊ธฐ๋ฅ
- โ์ ์ซ์ ๋ฐ์ค์ ์ค๋ฅธ์ชฝ ๋๊ณผ, ๋ค ์ซ์ ๋ฐ์ค์ ์ผ์ชฝ ์์์ด ๊ฐ๊น๊ณ (์ํ ๊ฑฐ๋ฆฌ), ๋ ๋ฐ์ค์ ์ธ๋ก ์ค์ฌ์ด ๊ฑฐ์ ๊ฐ์ ๊ฒฝ์ฐ(๊ฐ์ ํ)โ, ์ด๋ฅผ ํ๋์ ์ฃผ๋ฏผ๋ฒํธ๋ก ํ๋จํ๋ค.
set()Python ๋ด์ฅ ํจ์๋ก, ์งํฉ์ ๊ด๋ จ๋ ๊ฒ์ ์ฝ๊ฒ ์ฒ๋ฆฌํ ์ ์๋ค.
Example
s1 = set([1, 2, 3]) s2 = set("hello") print(s1) # result : {1,2,3} print(s2) # result : {'l', 'h', 'e', 'o'}
abs(x)Python ๋ด์ฅ ํจ์๋ก, x์ ์ ๋๊ฐ ๋ฐํ
front_x_max = max(fronts_xs): ์ ๋ฐ์ค์ ์ค๋ฅธ์ชฝ ๋(x ์ขํ)back_x_min = min(back_xs): ๋ค ๋ฐ์ค์ ์ผ์ชฝ ์์(x์ขํ)x_distance = back_x_min - front_x_max- ์ด ๊ฐ์ด 0 ์ด์์ด๋ฉด ๋ค ๋ฐ์ค๊ฐ ์ ๋ฐ์ค ์ค๋ฅธ์ชฝ์ ์์น(๋ ผ๋ฆฌ์ ๋์น)
- โ MAX_X_DISTANCE ์ด๋ฉด ๋ถ์ด ์๋ค(ํ์ดํ์ด๋ ๊ณต๋ฐฑ ์ ๋์ ๊ฑฐ๋ฆฌ).
front_y_center,back_y_center: ๊ฐ ๋ฐ์ค์ ์์ง ์ค์ฌ.y_diff = abs(...)๊ฐ โ MAX_Y_DISTANCE์ด๋ฉด ๊ฐ์ ํ์ ์๋ค๊ณ ์ทจ๊ธ.
๊ฒฐ๊ณผ ์ ์ฅ
fname = os.path.basename(image_path)
out_path = os.path.join(OUTPUT_DIR, f"EasyOCR_masked_{MASK_METHOD}_{fname}")
cv2.imwrite(out_path, image)
print(f"์๋ฃ: {out_path} (์ด {found}๊ฐ ์ฃผ๋ฏผ๋ฑ๋ก๋ฒํธ ์ฒ๋ฆฌ)")
return fou- ๋ง์คํน ์๋ฃ๋ ์ด๋ฏธ์ง๋ฅผ
OUTPUT_DIR์ ์ ์ฅ - ํ์ผ๋ช
์
MASK_METHOD๋ฅผ ํฌํจ์์ผ ๊ตฌ๋ถ
๋ฉ์ธ ํจ์
main ํจ์
def main():โญ ์ด ํจ์๊ฐ ํ๋ ์ผ
- OCR Reader ์ด๊ธฐํ๋ถํฐ ์ด๋ฏธ์ง ํด๋ ์ ์ฒด ์ํ
process_image()ํธ์ถ- ํต๊ณ ์ถ๋ ฅ๊น์ง ์ ์ฒด ๋ง์คํน ํ์ดํ๋ผ์ธ์ ์ ์ดํ๋ ์ง์ ์ (entry point) ํจ์
EasyOCR Reader ์ด๊ธฐํ
reader = easyocr.Reader(OCR_LANGS, gpu=True)- EasyOCR์ Reader ๊ฐ์ฒด ์ด๊ธฐํ
์ด๋ฏธ์ง ๋ชฉ๋ก ์์ง
img_files = []
for ext in ("*.jpg", "*.jpeg", "*.png"):
img_files.extend(glob(os.path.join(INPUT_DIR, ext)))
if not img_files:
print(f"[INFO] ์
๋ ฅ ํด๋์ ์ด๋ฏธ์ง๊ฐ ์์ต๋๋ค: {INPUT_DIR}")
return- ์ง์ ๋
INPUT_DIR์์ ์ด๋ฏธ์ง ํ์ผ ๋ชจ๋ ์์ง - JPG, JPEG, PNG ํ์ฅ์๋ง ์ธ์ ๋์์ผ๋ก ํฌํจ.
- ์ ๋ ฅ ํด๋์ ์ด๋ฏธ์ง๊ฐ ์์ ๊ฒฝ์ฐ ์กฐ๊ธฐ ์ข ๋ฃ
์ ์ฒด ํ์ผ ๋ฃจํ ๋ฐ ์ฒ๋ฆฌ
total_masked = 0
for img_path in img_files:
print(f"\n์ฒ๋ฆฌ ์ค: {img_path}")
total_masked += process_image(img_path, reader)
print(f"\n=== ์ ์ฒด ์๋ฃ ===\n์ด {len(img_files)}์ฅ ์ฒ๋ฆฌ, {total_masked}๊ฐ ์ฃผ๋ฏผ๋ฑ๋ก๋ฒํธ ์ฒ๋ฆฌ")- ์์งํ ์ด๋ฏธ์ง ํ์ผ๋ค์ ํ๋์ฉ ์ํํ๋ฉฐ ์ฒ๋ฆฌ
process_image()ํจ์์ ๊ฐ ์ด๋ฏธ์ง ๊ฒฝ๋ก์reader๊ฐ์ฒด๋ฅผ ์ ๋ฌ- ํจ์๊ฐ ๋ฐํํ โ๋ง์คํน๋ ์ฃผ๋ฏผ๋ฑ๋ก๋ฒํธ ๊ฐ์โ(
found)๋ฅผ ๋์ - ๋ง์ง๋ง์ผ๋ก ์ ์ฒด ์์ฝ ๊ฒฐ๊ณผ๋ฅผ ์ฝ์์ ์ถ๋ ฅ
์คํฌ๋ฆฝํธ ์ง์ ์
if __name__ == "__main__":
main()๋!
PaddleOCR
Import Library
import re
import os
import cv2
import numpy as np
from paddleocr import PaddleOCR
from PIL import Image, ImageDraw, ImageFont
# import paddlepaddleocr.PaddleOCR: OCR ๋ชจ๋ธ ๋ก๋ยท์์ธกpaddle: GPU ๋ฉ๋ชจ๋ฆฌ ์กฐํํ๋ ค๋ฉดpaddleํ์ (ํ์ x)
(์ฃผ์ ์ฒ๋ฆฌ๋) GPU ๋ฉ๋ชจ๋ฆฌ ์ถ๋ ฅ ์ฝ๋
# GPU ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ํ์ธ
# mem_alloc = paddle.device.cuda.memory_allocated()
# mem_reserved = paddle.device.cuda.max_memory_reserved()
# print(f"์์ ์ ๋ฉ๋ชจ๋ฆฌ: {mem_alloc/1024**2:.2f} MB")
# print(f"์ต๋ ์์ฝ๋ ๋ฉ๋ชจ๋ฆฌ: {mem_reserved / 1024**2:.2f} MB")- GPU ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ ์ถ๋ ฅํ๋ ์ฝ๋(์ฃผ์ ์ฒ๋ฆฌ๋จ).
OCR ์ด๊ธฐํ
ocr = PaddleOCR(
lang='korean',
use_doc_orientation_classify=False,
use_doc_unwarping=False,
use_textline_orientation=False,
device='gpu:0', # 'cpu' ๋ก ๋ณ๊ฒฝ ๊ฐ๋ฅ
)- PaddleOCR ๊ฐ์ฒด๋ฅผ ์์ฑํ์ฌ ๋ชจ๋ธ์ ๋ก๋ํ๋ค.
paddleocr.PaddleOCR()
lang
- ์ฌ์ฉํ OCR ๋ชจ๋ธ ์ธ์ด.
- PaddleOCR์ด ์ง์ํ๋ ์ธ์ด ๋ชฉ๋ก
use_doc_orientation_classify
- ๋ฌธ์ ๋ฐฉํฅ ๋ถ๋ฅ ๋ชจ๋ ํ์ฑํ ์ฌ๋ถ. ๊ธฐ๋ณธ๊ฐ์
True.- ์ค์บํ ๋ฌธ์ ์ด๋ฏธ์ง๊ฐ ์ฌ๋ฐ๋ฅธ ๋ฐฉํฅ์ธ์ง(ํ์ ๋์ด ์๋์ง) ์๋์ผ๋ก ํ๋ณ.
use_doc_unwarping
- text image unwarping ๋ชจ๋ ํ์ฑํ ์ฌ๋ถ. ๊ธฐ๋ณธ๊ฐ์
True.- โtext image unwarpingโ์ด๋ ๊ตฌ๋ถ๋ฌ์ง๊ฑฐ๋ ์๊ณก๋ ๋ฌธ์ ์ด๋ฏธ์ง๋ฅผ ํํํ๊ณ ์ฌ๋ฐ๋ฅธ ํํ๋ก ๋ณต์ํ๋ ๊ธฐ์ ์ ๋ปํ๋ค.
use_textline_orientation
- ํ ์คํธ ๋ฐฉํฅ ๋ถ๋ฅ ๋ชจ๋ ํ์ฑํ ์ฌ๋ถ. ๊ธฐ๋ณธ๊ฐ์
True- ์ด๋ฏธ์ง ์ ํ ์คํธ๊ฐ ์ฌ๋ฐ๋ฅธ ๋ฐฉํฅ์ธ์ง(ํ์ ๋์ด ์๋์ง) ์๋์ผ๋ก ํ๋ณ.
๋งค๊ฐ๋ณ์์ ๋ํ ๋ ์์ธํ ๋ด์ฉ์ PaddleOCR Documentation ์ฐธ์กฐ.
use_doc_orientation_classify- ๋ฌธ์ ๋ฐฉํฅ ๋ถ๋ฅ ๋ชจ๋ ํ์ฑํ ์ฌ๋ถ๋ฅผ ๋ํ๋ด๋ ๋งค๊ฐ๋ณ์. ๊ธฐ๋ณธ๊ฐ์
True. - ์ค์บํ ๋ฌธ์ ์ด๋ฏธ์ง๊ฐ ์ฌ๋ฐ๋ฅธ ๋ฐฉํฅ์ธ์ง(ํ์ ๋์ด ์๋์ง) ์๋์ผ๋ก ํ๋ณ.
- ๋ฌธ์ ๋ฐฉํฅ ๋ถ๋ฅ ๋ชจ๋ ํ์ฑํ ์ฌ๋ถ๋ฅผ ๋ํ๋ด๋ ๋งค๊ฐ๋ณ์. ๊ธฐ๋ณธ๊ฐ์
use_doc_unwarping- text image unwarping ๋ชจ๋ ํ์ฑํ ์ฌ๋ถ๋ฅผ ๋ํ๋ด๋ ๋งค๊ฐ๋ณ์. ๊ธฐ๋ณธ๊ฐ์
True. - โtext image unwarpingโ์ด๋ ๊ตฌ๋ถ๋ฌ์ง๊ฑฐ๋ ์๊ณก๋ ๋ฌธ์ ์ด๋ฏธ์ง๋ฅผ ํํํ๊ณ ์ฌ๋ฐ๋ฅธ ํํ๋ก ๋ณต์ํ๋ ๊ธฐ์ ์ ๋ปํ๋ค.
- text image unwarping ๋ชจ๋ ํ์ฑํ ์ฌ๋ถ๋ฅผ ๋ํ๋ด๋ ๋งค๊ฐ๋ณ์. ๊ธฐ๋ณธ๊ฐ์
use_textline_orientation- ํ
์คํธ ๋ฐฉํฅ ๋ถ๋ฅ ๋ชจ๋ ํ์ฑํ ์ฌ๋ถ๋ฅผ ๋ํ๋ด๋ ๋งค๊ฐ๋ณ์. ๊ธฐ๋ณธ๊ฐ์
True - ์ด๋ฏธ์ง ์ ํ ์คํธ๊ฐ ์ฌ๋ฐ๋ฅธ ๋ฐฉํฅ์ธ์ง(ํ์ ๋์ด ์๋์ง) ์๋์ผ๋ก ํ๋ณ.
- ํ
์คํธ ๋ฐฉํฅ ๋ถ๋ฅ ๋ชจ๋ ํ์ฑํ ์ฌ๋ถ๋ฅผ ๋ํ๋ด๋ ๋งค๊ฐ๋ณ์. ๊ธฐ๋ณธ๊ฐ์
์ฃผ๋ฏผ๋ฑ๋ก๋ฒํธ ์ ๊ท์
patterns = [
re.compile(r"\b\d{6}\s*-\s*\d{7}\b"),
re.compile(r"\b\d{13}\b")
]๋ง์คํน ํจ์๋ค
paddleocr.PaddleOCR.predict๊ฐ ๋ฐํํ๋dt_polys(ํ ์คํธ ๋ฐ์ด๋ ๋ฐ์ค ์ขํ, 4๊ฐ ๊ผญ์ง์ ) ํฌ๋งท์ ๊ทธ๋๋ก ๋ฐ์์ ์ฒ๋ฆฌํ๋ค. ์๋์ ํ์ ํ ๊ฐ ํจ์๋ ํด๋ฆฌ๊ณค์ ๋ง์คํฌ๋ก ๋ง๋ค๊ณ ๊ทธ ๋ด๋ถ๋ฅผ ์กฐ์ํ๋ ๋ฐฉ์์ผ๋ก ๋ง์คํน ์ฒ๋ฆฌ๋ฅผ ํ๋ฉฐ, EasyOCR์ ๋ง์คํน ํจ์๋ค๊ณผ ๊ฑฐ์ ๋์ผํ ๋ฐฉ์์ผ๋ก ์ด๋ฃจ์ด์ง๋ค.
๊ฒ์ ์ ์ฑ์ฐ๊ธฐ
def mask_black(img, box):
pts = np.array(box, dtype=np.int32)
cv2.fillPoly(img, [pts], color=(0,0,0))
return img- ์ฃผ์ด์ง
boxํด๋ฆฌ๊ณค ๋ด๋ถ๋ฅผ (B,G,R)=(0,0,0)์ผ๋ก ์ฑ์.
๋ธ๋ฌ ์ฒ๋ฆฌ
def mask_blur(img, box):
pts = np.array(box, dtype=np.int32)
mask = np.zeros(img.shape[:2], dtype=np.uint8)
cv2.fillPoly(mask, [pts], 255)
x, y, w, h = cv2.boundingRect(pts)
roi = img[y:y+h, x:x+w]
roi_mask = mask[y:y+h, x:x+w]
if roi.size > 0:
kernel = (51, 51) # kernel: ๋ธ๋ฌ ๊ฐ๋ ์กฐ์ (ํ์๊ฐ ์
๋ ฅ, ๊ฐ์ด ํด์๋ก ๊ฐ๋ โ)
blurred = cv2.GaussianBlur(roi, kernel, 30)
roi[roi_mask==255] = blurred[roi_mask==255]
img[y:y+h, x:x+w] = roi
return imgEasyOCR์ ๋ธ๋ฌ ๋ง์คํน๊ณผ ๋์ผ
mask๋ฅผ ๋ง๋ค์ด ROI์๋ง ๋ธ๋ฌ๋ฅผ ์ ์ฉ.kernel์ ๋ฐ๋์ ํ์. ๊ฐ์ด ํด์๋ก ๋ธ๋ฌ ๊ฐํด์ง. (51์ ๋งค์ฐ ๊ฐํจ)
๋ชจ์์ดํฌ ์ฒ๋ฆฌ
def mask_mosaic(img, box, ratio=0.05): # ratio: ๋ชจ์์ดํฌ ๊ฐ๋ ์กฐ์ (๊ฐ์ด ๋ฎ์์๋ก ๊ฐ๋ โ)
pts = np.array(box, dtype=np.int32)
mask = np.zeros(img.shape[:2], dtype=np.uint8)
cv2.fillPoly(mask, [pts], 255)
x, y, w, h = cv2.boundingRect(pts)
roi = img[y:y+h, x:x+w]
roi_mask = mask[y:y+h, x:x+w]
if roi.size > 0:
small = cv2.resize(roi, (max(1,int(w*ratio)), max(1,int(h*ratio))), interpolation=cv2.INTER_LINEAR)
mosaic = cv2.resize(small, (w,h), interpolation=cv2.INTER_NEAREST)
roi[roi_mask==255] = mosaic[roi_mask==255]
img[y:y+h, x:x+w] = roi
return imgEasyOCR์ ๋ชจ์์ดํฌ ๋ง์คํน๊ณผ ๋์ผ
ํ ์คํธ๋ก ๋์ฒด (ํ์ ๋ฐ์)
def mask_replace_text(img, box, text="[ REDACTED ]"):
pts = np.array(box, dtype=np.int32)
x, y, w, h = cv2.boundingRect(pts)
center_x = x + w // 2
center_y = y + h // 2
cv2.fillPoly(img, [pts], (255, 255, 255))
max_len = 0
angle = 0
for i in range(4):
dx = pts[(i+1)%4][0] - pts[i][0]
dy = pts[(i+1)%4][1] - pts[i][1]
length = np.hypot(dx, dy)
if length > max_len:
max_len = length
angle = -np.degrees(np.arctan2(dy, dx))
pil_img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(pil_img)
diagonal = np.hypot(w, h)
font_size = min(max(int(diagonal * 0.1), 12), h)
try:
font = ImageFont.truetype("malgunbd.ttf", font_size) # ๋ง์ ๊ณ ๋ bold
except:
font = ImageFont.load_default()
bbox_text = draw.textbbox((0, 0), text, font=font)
text_w = bbox_text[2] - bbox_text[0]
text_h = bbox_text[3] - bbox_text[1]
offset = 5 # ํ
์คํธ ์๋ก ์ฌ๋ฆด ํฝ์
์
text_x = center_x - text_w / 2
text_y = center_y - text_h / 2 - offset
text_layer = Image.new("RGBA", pil_img.size, (255, 255, 255, 0))
text_draw = ImageDraw.Draw(text_layer)
text_draw.text((text_x, text_y), text, font=font, fill=(255, 0, 0, 255))
rotated_layer = text_layer.rotate(angle, center=(center_x, center_y), resample=Image.BICUBIC)
pil_img = Image.alpha_composite(pil_img.convert("RGBA"), rotated_layer)
img[:] = cv2.cvtColor(np.array(pil_img.convert("RGB")), cv2.COLOR_RGB2BGR)
return imgEasyOCR์ ํ ์คํธ ๊ต์ฒด ๋ง์คํน๊ณผ ๋์ผ
๋ง์คํน ๋ฉ์๋ ๋งคํ
masking_methods = {
"black": mask_black,
"blur": mask_blur,
"mosaic": mask_mosaic,
"replace_text": mask_replace_text
}- ๋ฌธ์์ด๋ก ์ฒ๋ฆฌ ๋ฐฉ์ ์ ํ์ ์ฝ๊ฒ ํ๊ธฐ ์ํด ๋์ ๋๋ฆฌ ๋งคํ(dictionary mapping).
- ํ์ ํ
masking์ธ์๊ฐ ๊ฐํธํ๊ฒ ์ ํ ๊ฐ๋ฅ.
์ด๋ฏธ์ง ์ฒ๋ฆฌ ํจ์
def process_image(img_path, save_path, masking="black"):
img = cv2.imread(img_path)
if img is None:
print(f"์ด๋ฏธ์ง๋ฅผ ์ฝ์ ์ ์์ต๋๋ค: {img_path}")
return
result = ocr.predict(img_path)[0] # result[0] ์ฌ์ฉ
rec_texts = result['rec_texts']
#print(rec_texts) # OCRํ ํ
์คํธ๋ฅผ ์ถ๋ ฅํด๋ณด๊ณ ์ถ์ผ๋ฉด ์ฃผ์์ ์ง์ฐ์ธ์ฉ
dt_polys = result['dt_polys']
for text, box in zip(rec_texts, dt_polys):
if any(p.search(text) for p in patterns):
if masking in masking_methods:
img = masking_methods[masking](img, box)
cv2.imwrite(save_path, img)
print(f"์ ์ฅ ์๋ฃ: {save_path}")-
cv2.imread(img_path): ์ด๋ฏธ์ง ๋ก๋ (BGR numpy array). -
if img is None: ํ์ผ ์ฝ๊ธฐ ์คํจ ์ฒดํฌ. -
result = ocr.predict(img_path)[0]: OCR ์คํ ๋ฐ ๊ฒฐ๊ณผ ๋ถ๋ฌ์ค๊ธฐ. (์ฌ์ฉํ์ ๋ฒ์ ์์predict๊ฐ path๋ฅผ ๋ฐ์์ ๊ฒฐ๊ณผ ๋ฆฌ์คํธ๋ฅผ ๋ฐํํ๋ค๊ณ ๊ฐ์ ) -
rec_texts = result['rec_texts'],dt_polys = result['dt_polys']: ํ ์คํธ ๋ชฉ๋ก๊ณผ ๊ฐ ํ ์คํธ์ ํด๋ฆฌ๊ณค ์ขํ ์ถ์ถ. -
for text, box in zip(...): ํ ์คํธ์ ์ขํ๋ฅผ ๋ฌถ์ด ์ํ. -
if any(p.search(text) for p in patterns): ์ ๊ท์์ผ๋ก ์ฃผ๋ฏผ๋ฑ๋ก๋ฒํธ ํจํด์ ์ฐพ์ผ๋ฉด true. -
img = masking_methods[masking](img, box): ์ ํํ ๋ง์คํน ํจ์๋ก ์ด๋ฏธ์ง ๊ฐฑ์ . -
cv2.imwrite(save_path, img): ๊ฒฐ๊ณผ ์ ์ฅ.
๋ฉ์ธ ์คํ ๋ธ๋ก
if __name__ == "__main__":
input_dir = "idc_samples"
output_dir = "idc_results"
os.makedirs(output_dir, exist_ok=True)
# ์ ํ: "black", "blur", "mosaic", "replace_text"
masking_type = "black"
for file in os.listdir(input_dir):
if file.lower().endswith((".jpg", ".jpeg", ".png")):
input_path = os.path.join(input_dir, file)
output_path = os.path.join(output_dir, f"PaddleOCR_{masking_type}_{file}")
process_image(input_path, output_path, masking=masking_type)-
์ ๋ ฅ/์ถ๋ ฅ ๋๋ ํ ๋ฆฌ ์ง์ ๋ฐ ์ถ๋ ฅ ๋๋ ํ ๋ฆฌ ์์ฑ.
-
masking_type์ผ๋ก ๋ง์คํน ๋ฐฉ์ ์ ํ. -
์ ๋ ฅ ๋๋ ํ ๋ฆฌ์ ์ด๋ฏธ์ง๋ค์ ์ํํ๋ฉด์
process_imageํธ์ถ.
ยท ํฐ์(255) โ โ์ด ๋ถ๋ถ์ ํฌํจํ๋ผโ = ๊ด์ฌ ์์ญ(ROI: Region of Interest)
ยท ๊ฒ์์(0) โ โ์ด ๋ถ๋ถ์ ์ ์ธํ๋ผโ = mask
mask๊ฐ ๋จ์ผ ์ฑ๋(2D) ์ด๋ฏธ์ง์ธ ์ด์ ๋ ์(3์ฑ๋) ์ ๋ณด๋ ํ์ ์๊ณ , โ์ฌ๊ธฐ์ธ๊ฐ/์๋๊ฐโ๋ง ํ๋จํ๋ฉด ๋๊ธฐ ๋๋ฌธ์ด๋ค. (๋ฉ๋ชจ๋ฆฌ ์ ์ฝ ํจ๊ณผ๋ ์๋ค!)