OpenCV 공식 문서 기준으로 ArUco 이미지는 cv2.aruco.generateImageMarker()로 생성하며, 예를 들어 DICT_6X6_250은 ID 0~249 범위를 사용합니다.
1. 설치
pip install opencv-contrib-python pillow numpy
2. 소스
aruco_print.py
import cv2
import numpy as np
from PIL import Image, ImageDraw, ImageFont
import argparse
import os
A4_WIDTH_MM = 210
A4_HEIGHT_MM = 297
def mm_to_px(mm, dpi):
return int(round(mm / 25.4 * dpi))
def generate_aruco_marker(dictionary_name, marker_id, marker_px, border_bits=1):
aruco = cv2.aruco
if not hasattr(aruco, dictionary_name):
raise ValueError(f"지원하지 않는 dictionary 이름입니다: {dictionary_name}")
dictionary_id = getattr(aruco, dictionary_name)
dictionary = aruco.getPredefinedDictionary(dictionary_id)
# OpenCV 4.7+
if hasattr(aruco, "generateImageMarker"):
marker_img = aruco.generateImageMarker(
dictionary,
marker_id,
marker_px,
borderBits=border_bits
)
# 구버전 OpenCV 대응
elif hasattr(aruco, "drawMarker"):
marker_img = np.zeros((marker_px, marker_px), dtype=np.uint8)
aruco.drawMarker(dictionary, marker_id, marker_px, marker_img, border_bits)
else:
raise RuntimeError("cv2.aruco.generateImageMarker 또는 drawMarker를 찾을 수 없습니다.")
return Image.fromarray(marker_img).convert("RGB")
def create_aruco_print_sheet(
output_path,
dictionary_name="DICT_6X6_250",
start_id=0,
count=12,
marker_size_mm=40,
margin_mm=10,
gap_mm=8,
dpi=300,
border_bits=1,
show_id=True
):
page_w_px = mm_to_px(A4_WIDTH_MM, dpi)
page_h_px = mm_to_px(A4_HEIGHT_MM, dpi)
margin_px = mm_to_px(margin_mm, dpi)
gap_px = mm_to_px(gap_mm, dpi)
marker_px = mm_to_px(marker_size_mm, dpi)
label_h_px = mm_to_px(7, dpi) if show_id else 0
cell_w_px = marker_px + gap_px
cell_h_px = marker_px + label_h_px + gap_px
usable_w_px = page_w_px - 2 * margin_px
usable_h_px = page_h_px - 2 * margin_px
cols = max(1, usable_w_px // cell_w_px)
rows = max(1, usable_h_px // cell_h_px)
max_markers_per_page = cols * rows
if count > max_markers_per_page:
print(f"[경고] 현재 설정에서는 A4 한 장에 {max_markers_per_page}개만 들어갑니다.")
print(f"[정보] 요청 개수 {count}개 중 {max_markers_per_page}개만 생성합니다.")
count = max_markers_per_page
page = Image.new("RGB", (page_w_px, page_h_px), "white")
draw = ImageDraw.Draw(page)
try:
font = ImageFont.truetype("DejaVuSans.ttf", mm_to_px(4, dpi))
except:
font = ImageFont.load_default()
for i in range(count):
marker_id = start_id + i
row = i // cols
col = i % cols
x = margin_px + col * cell_w_px
y = margin_px + row * cell_h_px
marker = generate_aruco_marker(
dictionary_name=dictionary_name,
marker_id=marker_id,
marker_px=marker_px,
border_bits=border_bits
)
page.paste(marker, (x, y))
if show_id:
text = f"{dictionary_name} / ID {marker_id} / {marker_size_mm}mm"
text_x = x
text_y = y + marker_px + mm_to_px(1.5, dpi)
draw.text((text_x, text_y), text, fill="black", font=font)
os.makedirs(os.path.dirname(output_path) or ".", exist_ok=True)
ext = os.path.splitext(output_path)[1].lower()
if ext == ".pdf":
page.save(output_path, "PDF", resolution=dpi)
elif ext in [".png", ".jpg", ".jpeg"]:
page.save(output_path)
else:
raise ValueError("출력 파일 확장자는 .pdf, .png, .jpg 중 하나를 사용하세요.")
print(f"[완료] 저장됨: {output_path}")
print(f"[설정] dictionary={dictionary_name}, start_id={start_id}, count={count}")
print(f"[설정] marker_size={marker_size_mm}mm, dpi={dpi}, A4={A4_WIDTH_MM}x{A4_HEIGHT_MM}mm")
def main():
parser = argparse.ArgumentParser(description="A4 ArUco 마커 인쇄용 PDF/이미지 생성기")
parser.add_argument("--output", default="aruco_markers.pdf", help="출력 파일명")
parser.add_argument("--dict", default="DICT_6X6_250", help="ArUco dictionary 이름")
parser.add_argument("--start-id", type=int, default=0, help="시작 마커 ID")
parser.add_argument("--count", type=int, default=12, help="생성할 마커 개수")
parser.add_argument("--size-mm", type=float, default=40, help="마커 한 변 크기(mm)")
parser.add_argument("--margin-mm", type=float, default=10, help="A4 여백(mm)")
parser.add_argument("--gap-mm", type=float, default=8, help="마커 간격(mm)")
parser.add_argument("--dpi", type=int, default=300, help="출력 DPI")
parser.add_argument("--border-bits", type=int, default=1, help="ArUco 검은 테두리 bit 수")
parser.add_argument("--no-label", action="store_true", help="ID 라벨 숨김")
args = parser.parse_args()
create_aruco_print_sheet(
output_path=args.output,
dictionary_name=args.dict,
start_id=args.start_id,
count=args.count,
marker_size_mm=args.size_mm,
margin_mm=args.margin_mm,
gap_mm=args.gap_mm,
dpi=args.dpi,
border_bits=args.border_bits,
show_id=not args.no_label
)
if __name__ == "__main__":
main()
3. 실행 예시
1) A4 PDF에 40mm 마커 12개 출력
python3 aruco_print.py --output aruco_markers.pdf --dict DICT_6X6_250 --start-id 0 --count 12 --size-mm 40
2) 50mm 마커 6개 출력
python3 aruco_print.py --output aruco_50mm.pdf --dict DICT_5X5_100 --start-id 1 --count 6 --size-mm 50
2) 60mm 마커 6개 출력
python3 aruco_print.py --output aruco_60mm.pdf --dict DICT_6X6_250 --start-id 10 --count 6 --size-mm 60
3) ID 라벨 없이 출력
python3 aruco_print.py --output aruco_clean.pdf --start-id 0 --count 8 --size-mm 50 --no-label
python3 aruco_print.py --output 4X4_50_100mm_aruco_markers.pdf --dict DICT_4X4_50 --start-id 0 --count 1 --size-mm 100
4) 자주 쓰는 Dictionary
DICT_4X4_50
DICT_4X4_100
DICT_4X4_250
DICT_5X5_100
DICT_5X5_250
DICT_6X6_250
DICT_6X6_1000
DICT_7X7_250
DICT_APRILTAG_36h11
로봇/드론 비전용이면 보통은 DICT_6X6_250 + 40~80mm부터 쓰는 게 무난합니다. 너무 작은 마커는 거리 멀어지면 인식률이 바로 떨어집니다.
5. 전체 인쇄소스
touch aruco_DICT_4X4_50_10cm_A4.py
import cv2
import cv2.aruco as aruco
import numpy as np
from PIL import Image
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import cm
from reportlab.lib.utils import ImageReader
# =========================
# 설정값
# =========================
#OUTPUT_PDF = "aruco_DICT_6X6_250_10cm_A4.pdf"
#OUTPUT_PDF = "aruco_DICT_5X5_100_10cm_A4.pdf"
OUTPUT_PDF = "aruco_DICT_4X4_50_10cm_A4.pdf"
MARKER_SIZE_CM = 10
MARKER_PIXEL_SIZE = 600
START_ID = 0
#END_ID = 249
#END_ID = 99
END_ID = 49
SHOW_ID_TEXT = True
# =========================
# ArUco Dictionary
# =========================
#aruco_dict = aruco.getPredefinedDictionary(aruco.DICT_6X6_250)
#aruco_dict = aruco.getPredefinedDictionary(aruco.DICT_5X5_100)
aruco_dict = aruco.getPredefinedDictionary(aruco.DICT_4X4_50)
def generate_marker(marker_id: int, size_px: int) -> Image.Image:
"""
OpenCV 버전에 따라 generateImageMarker 또는 drawMarker 사용
"""
# OpenCV 최신 API
if hasattr(aruco, "generateImageMarker"):
marker_img = aruco.generateImageMarker(
aruco_dict,
marker_id,
size_px
)
# OpenCV 구버전 API
elif hasattr(aruco, "drawMarker"):
marker_img = np.zeros((size_px, size_px), dtype=np.uint8)
aruco.drawMarker(
aruco_dict,
marker_id,
size_px,
marker_img,
1
)
else:
raise RuntimeError(
"현재 OpenCV의 cv2.aruco에 generateImageMarker()와 drawMarker()가 모두 없습니다.\n"
"opencv-contrib-python을 설치해야 합니다.\n"
"설치 명령:\n"
"pip uninstall opencv-python opencv-contrib-python -y\n"
"pip install opencv-contrib-python"
)
return Image.fromarray(marker_img)
def create_a4_pdf():
page_width, page_height = A4
marker_size_pt = MARKER_SIZE_CM * cm
pdf = canvas.Canvas(OUTPUT_PDF, pagesize=A4)
for marker_id in range(START_ID, END_ID + 1):
marker_pil = generate_marker(marker_id, MARKER_PIXEL_SIZE)
marker_reader = ImageReader(marker_pil)
# A4 중앙 배치
x = (page_width - marker_size_pt) / 2
y = (page_height - marker_size_pt) / 2
pdf.drawImage(
marker_reader,
x,
y,
width=marker_size_pt,
height=marker_size_pt,
preserveAspectRatio=True,
mask="auto"
)
if SHOW_ID_TEXT:
pdf.setFont("Helvetica", 16)
pdf.drawCentredString(
page_width / 2,
y - 3.6 * cm,
f"DICT_4X4_50 ID: {marker_id}"
)
pdf.showPage()
pdf.save()
print(f"PDF 생성 완료: {OUTPUT_PDF}")
if __name__ == "__main__":
print("OpenCV version:", cv2.__version__)
print("has generateImageMarker:", hasattr(aruco, "generateImageMarker"))
print("has drawMarker:", hasattr(aruco, "drawMarker"))
create_a4_pdf()