OpenCV:Example:IronPlateCamber
철판 솟음 (Camber; 휘어짐) 측정
Code
import cv2
import glob
import time
import numpy as np
from math import sqrt
from typing import Tuple
from datetime import datetime
# 접점
def get_crosspt(ST1, ST2, ED1, ED2):
x11, y11 = ST1[0], ST1[1]
x12, y12 = ST2[0], ST2[1]
x21, y21 = ED1[0], ED1[1]
x22, y22 = ED2[0], ED2[1]
if x12 == x11 or x22 == x21:
# print('delta x=0')
if x12 == x11:
cx = x12
m2 = (y22 - y21) / (x22 - x21)
cy = m2 * (cx - x21) + y21
return np.array([round(cx), round(cy)])
if x22 == x21:
cx = x22
m1 = (y12 - y11) / (x12 - x11)
cy = m1 * (cx - x11) + y11
return np.array([round(cx), round(cy)])
m1 = (y12 - y11) / (x12 - x11)
m2 = (y22 - y21) / (x22 - x21)
if m1 == m2:
print('parallel')
return None
# print(x11,y11, x12, y12, x21, y21, x22, y22, m1, m2)
cx = (x11 * m1 - y11 - x21 * m2 + y21) / (m1 - m2)
cy = m1 * (cx - x11) + y11
return np.array([round(cx), round(cy)])
# 2개의 점으로 기울기,상수 구하기
def cal_ab(PT1,PT2):
x1,y1 = PT1[0],PT1[1]
x2,y2 = PT2[0],PT2[1]
# y = ax + b
a = (y2 - y1)/(x2 - x1)
b = (x2*y1 - x1*y2)/(x2 - x1)
return a,b
Point = Tuple[float, float]
# 2개점 사이의 길이 구하는 함수
def norm(p1: Point, p2: Point) -> float:
x1, y1 = p1
x2, y2 = p2
dx = x2 - x1
dy = y2 - y1
return sqrt((dx ** 2) + (dy ** 2))
# 2개의 점을 지나는 직선과 1개의 점 사이의 거리
def cal_dist(Lpt,Rpt,Countorpt):
x1,y1 = Lpt[0],Lpt[1]
x2,y2 = Rpt[0],Rpt[1]
a,b = Countorpt[0][0],Countorpt[0][1]
area = abs((x1-a) * (y2-b) - (y1-b) * (x2 - a))
AB = ((x1-x2)**2 + (y1-y2)**2) **0.5
distance = area/AB
return distance
# 파일로드설정
# data_list = glob.glob('data/*/*.mp4')
# d_num = 4 # 4 short 5 long
# Vid = cv2.VideoCapture(data_list[d_num])
Vid = cv2.VideoCapture('curve_origin2.mp4')
w = round(Vid.get(cv2.CAP_PROP_FRAME_WIDTH))
h = round(Vid.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = Vid.get(cv2.CAP_PROP_FPS)
speed = 2 # play speed
# 동영상 저장
fourcc = cv2.VideoWriter_fourcc(*'mp4v') # mp4v
# file_name = data_list[d_num].split('/')[-1]
# surfix = '.curve'
# out = cv2.VideoWriter(f'video_result/curve_result2.mp4', fourcc, fps, (w, h))
# color
red = (0, 0, 255)
orange = (0, 128, 255)
yellow = (0, 255, 255)
green = (0, 255, 0)
skyblue = (255, 255, 0)
blue = (255, 0, 0)
magenta = (255, 0, 255)
black = (0, 0, 0)
white = (255, 255, 255)
# 변수 초기값
pivot_crossL = None
pivot_crossR = None
# 동영상 재생 시작
while Vid.isOpened():
begin = datetime.now()
ret, frame = Vid.read()
# 전처리(전 기능 공통)
img_gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
ret, dst = cv2.threshold(img_gray, 130, 255, cv2.THRESH_BINARY) # rect 170 contour 125
# 출력프레임 설정
show_frame = frame
# 최대넓이윤곽추출 contours / type np.ndarray / shape (N,1,2)
contours, hierarchy = cv2.findContours(dst, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
c_list = []
for index in range(len(contours)):
c_list.append(cv2.contourArea(contours[index]))
max_index = c_list.index(max(c_list))
largest_contour_size = c_list[max_index]
largest_contour = contours[max_index]
# 출력세팅
Ltxt_x = 20
Rtxt_x = 750
txt_y = 1300
y_mr = 250
Fface = cv2.FONT_HERSHEY_SIMPLEX
Fsize = 2.5
Lthick = 10
Ltype = cv2.LINE_AA
# 기준선 세로
topPt = [650, 100]
botPt = [445, 1600]
cv2.line(show_frame, topPt, botPt, white, 5)
# 기준선 가로
Lpt = [220, 1520]
Rpt = [640, 1550]
cv2.line(show_frame, Lpt, Rpt, white, 5)
# 특정 크기 이상일때 실행
if max(c_list) > 12000:
cv2.drawContours(show_frame, contours, max_index, skyblue, 3)
contours_min = np.argmin(largest_contour, axis = 0)
y_Min = largest_contour[contours_min[0][1]][0][1]
# 모든 contour의 각 점에 대하여 반복문 돌림
# 좌우 합의 평균을 기준으로 x값을 비교하여 2구간으로 나눔. (좌,우 구분)
# 1.가로기준선의 방정식에 맞는 해가 있으면 그 점은 접점임.
# 2.없을 경우 기준선의 위쪽,아래쪽에 각각 거리가 가장 짧은 2점을 잡고 교점을 구함.
w, b = cal_ab(Lpt, Rpt)
dtLplus_index = []
dtLmins_index = []
dtRplus_index = []
dtRmins_index = []
dtLplus_list = []
dtLmins_list = []
dtRplus_list = []
dtRmins_list = []
crossL = None
crossR = None
for i, pt in enumerate(largest_contour):
if pt[0][0] < (Lpt[0]+Rpt[0])/2:
if pt[0][1] == round(w * pt[0][0] + b):
# print('한번에 접점 찾음')
crossL = pt.reshape(2,)
# break
elif pt[0][1] < round(w * pt[0][0] + b): # opencv상에서 y축 반대개념
# print('양의 최소값 찾는중')
dtLplus_index.append(i)
dtLplus_list.append(cal_dist(Lpt, Rpt, pt))
elif pt[0][1] > round(w * pt[0][0] + b):
# print('음의 최소값 찾는중')
dtLmins_index.append(i)
dtLmins_list.append(cal_dist(Lpt, Rpt, pt))
# print('--------------------- 여기서 왼쪽점 끝나고 오른쪽으로 넘어가야함 -----------')
if pt[0][0] >= (Lpt[0]+Rpt[0])/2:
if pt[0][1] == round(w * pt[0][0] + b):
# print('한번에 접점 찾음')
crossR = pt.reshape(2,)
# continue
elif pt[0][1] < round(w * pt[0][0] + b): # opencv상에서 y축 반대개념
# print('양의 최소값 찾는중')
dtRplus_index.append(i)
dtRplus_list.append(cal_dist(Lpt, Rpt, pt))
elif pt[0][1] > round(w * pt[0][0] + b):
# print('음의 최소값 찾는중')
dtRmins_index.append(i)
dtRmins_list.append(cal_dist(Lpt, Rpt, pt))
# print('---------- 1frame 끝 ----------------')
if crossL is None and len(dtLplus_list) != 0 and len(dtLmins_list) !=0:
plusmin = largest_contour[dtLplus_index[dtLplus_list.index(min(dtLplus_list))]].reshape(2,)
minsmin = largest_contour[dtLmins_index[dtLmins_list.index(min(dtLmins_list))]].reshape(2,)
crossL = get_crosspt(Lpt,Rpt,plusmin,minsmin)
if crossR is None and len(dtRplus_list) != 0 and len(dtRmins_list) !=0:
plusmin = largest_contour[dtRplus_index[dtRplus_list.index(min(dtRplus_list))]].reshape(2,)
minsmin = largest_contour[dtRmins_index[dtRmins_list.index(min(dtRmins_list))]].reshape(2,)
crossR = get_crosspt(Lpt,Rpt,plusmin,minsmin)
if crossL is None or crossR is None:
dtL = '0'
dtR = '0'
else:
# 초기값 할당
if pivot_crossL is None:
pivot_crossL = crossL
pivot_crossR = crossR
# 이전프레임의 2개 교점과 현재프레임의 2개 교점의 길이
dtL = round(norm(crossL, pivot_crossL), 1)
dtR = round(norm(crossR, pivot_crossR), 1)
# -= 부호판별식
if crossL[1] > pivot_crossL[1]: dtL = -dtL
if crossR[1] < pivot_crossR[1]: dtR = -dtR
dtL, dtR = str(dtL), str(dtR)
# 이전프레임 교점 2개
cv2.line(show_frame, pivot_crossL, pivot_crossL, red, 10)
cv2.line(show_frame, pivot_crossR, pivot_crossR, red, 10)
# 현재프레임 교점 2개
cv2.line(show_frame, crossL, crossL, blue, 10)
cv2.line(show_frame, crossR, crossR, blue, 10)
pivot_crossL = crossL
pivot_crossR = crossR
# 왼쪽 글자
cv2.putText(show_frame, f'{dtL}', (Ltxt_x, txt_y + y_mr), Fface, Fsize, black, Lthick + 10, Ltype)
cv2.putText(show_frame, f'{dtL}', (Ltxt_x, txt_y + y_mr), Fface, Fsize, white, Lthick, Ltype)
# 오른쪽 글자
cv2.putText(show_frame, f'{dtR}', (Rtxt_x, txt_y + y_mr), Fface, Fsize, black, Lthick + 10, Ltype)
cv2.putText(show_frame, f'{dtR}', (Rtxt_x, txt_y + y_mr), Fface, Fsize, white, Lthick, Ltype)
else:
# 왼쪽 글자
cv2.putText(show_frame, '0', (Ltxt_x, txt_y + y_mr), Fface, Fsize, black, Lthick + 10, Ltype)
cv2.putText(show_frame, '0', (Ltxt_x, txt_y + y_mr), Fface, Fsize, white, Lthick, Ltype)
# 오른쪽 글자
cv2.putText(show_frame, '0', (Rtxt_x, txt_y + y_mr), Fface, Fsize, black, Lthick + 10, Ltype)
cv2.putText(show_frame, '0', (Rtxt_x, txt_y + y_mr), Fface, Fsize, white, Lthick, Ltype)
frametime = (datetime.now() - begin).total_seconds()
print(f"Duration {frametime:.4f}s")
if ret:
cv2.imshow('curve',show_frame)
# cv2.imshow('dst',dst2_gray)
# print(max(c_list))
# out.write(show_frame)
key = cv2.waitKeyEx(10)
if key == ord('q'):
break
elif key == ord('d'):
speed *= 2
if speed == 16:
speed = 16
print('max speed')
elif key == ord('a'):
speed /= 2
if speed == 0.25:
speed = 0.25
print('min speed')
# time.sleep(1./fps/speed)
else:
break
Vid.release()
cv2.destroyAllWindows()