Skip to content

Instantly share code, notes, and snippets.

@rhee-elten
Created January 9, 2023 01:57
Show Gist options
  • Save rhee-elten/541852e0743d6daa4caf0a7f250d1dcf to your computer and use it in GitHub Desktop.
Save rhee-elten/541852e0743d6daa4caf0a7f250d1dcf to your computer and use it in GitHub Desktop.
import cv2
import pandas as pd
import numpy as np
"""
HoughCircles(image, method, dp, minDist[, circles[, param1[, param2[, minRadius[, maxRadius]]]]]) -> circles
. @brief Finds circles in a grayscale image using the Hough transform.
.
. The function finds circles in a grayscale image using a modification of the Hough transform.
.
. @note Usually the function detects the centers of circles well. However, it may fail to find correct
. radii. You can assist to the function by specifying the radius range ( minRadius and maxRadius ) if
. you know it. Or, you may set maxRadius to a negative number to return centers only without radius
. search, and find the correct radius using an additional procedure.
.
. @param image 8-bit, single-channel, grayscale input image.
. @param method Detection method, see #HoughModes. Currently, the only implemented method is #HOUGH_GRADIENT
. @param dp Inverse ratio of the accumulator resolution to the image resolution. For example, if
. dp=1 , the accumulator has the same resolution as the input image. If dp=2 , the accumulator has
. half as big width and height.
. @param minDist Minimum distance between the centers of the detected circles. If the parameter is
. too small, multiple neighbor circles may be falsely detected in addition to a true one. If it is
. too large, some circles may be missed.
. @param param1 First method-specific parameter. In case of #HOUGH_GRADIENT , it is the higher
. threshold of the two passed to the Canny edge detector (the lower one is twice smaller).
. @param param2 Second method-specific parameter. In case of #HOUGH_GRADIENT , it is the
. accumulator threshold for the circle centers at the detection stage. The smaller it is, the more
. false circles may be detected. Circles, corresponding to the larger accumulator values, will be
. returned first.
. @param minRadius Minimum circle radius.
. @param maxRadius Maximum circle radius. If <= 0, uses the maximum image dimension. If < 0, returns
. centers without finding the radius.
linearPolar(src, center, maxRadius, flags[, dst]) -> dst
. @brief Remaps an image to polar coordinates space.
.
. @deprecated This function produces same result as cv::warpPolar(src, dst, src.size(), center, maxRadius, flags)
.
. @param src Source image
. @param dst Destination image. It will have same size and type as src.
. @param center The transformation center;
. @param maxRadius The radius of the bounding circle to transform. It determines the inverse magnitude scale parameter too.
. @param flags A combination of interpolation methods, see #InterpolationFlags
"""
def auto_polar_to_linear(im,
dsize=None,
thumb_scale=4, # 원본: 2448x2048 ==> 612x512
blur_ksize=5,
hc_dp=1,
hc_min_dist_fac=0.2,
hc_param1=250,
hc_param2=30,
hc_minradius_fac=0.6,
hc_maxradius_fac=0.99,
cb_fac=0.15,
_debug=None):
if isinstance(_debug, dict):
_debug.clear()
### 빠른 속도록 HoughCircles 를 구하기 위해서 흑백이미지를 원본 크기의 1/4 로 줄인 썸네일 생성
thumb = cv2.cvtColor(im, cv2.COLOR_RGB2GRAY)
thumb = cv2.resize(thumb, None, fx=1.0/thumb_scale,
fy=1.0/thumb_scale)
### 노이즈 억제를 위해서 medianBlur 적용
thumb = cv2.medianBlur(thumb, blur_ksize)
### hc_min_dist_fac, hc_minradius_fac, hc_maxradius_fac 파라메터의 설정값은
### 아래에서 구하는 thumb_radius 값에 대한 상대값으로 지정되어 있음
thumb_radius = min(*thumb.shape[:2]) / 2
hc_min_dist = int(thumb_radius * hc_min_dist_fac)
hc_minradius = int(thumb_radius * hc_minradius_fac)
hc_maxradius = int(thumb_radius * hc_maxradius_fac)
### HoughCircle 계산
circles = cv2.HoughCircles(thumb, cv2.HOUGH_GRADIENT, hc_dp, hc_min_dist,
param1=hc_param1, param2=hc_param2,
minRadius=hc_minradius, maxRadius=hc_maxradius)
assert circles.shape[0] == 1, ('invalid detection:', circles)
if isinstance(_debug, dict):
### HoughCircle 결과 확인 함수
def debug_preview(thickness=5,
cb_color=(0, 222, 0),
center_color=(222, 222, 0),
circle_color=(222, 0, 0)):
canvas = cv2.cvtColor(thumb, cv2.COLOR_GRAY2RGB)
hh, ww = canvas.shape[:2]
tx_min, tx_max = int(ww * (0.5 - cb_fac)), int(ww * (0.5 + cb_fac))
ty_min, ty_max = int(hh * (0.5 - cb_fac)), int(hh * (0.5 + cb_fac))
cv2.rectangle(canvas, (tx_min, ty_min),
(tx_max, ty_max), cb_color, thickness)
for cc in np.int0(circles[0]):
center = tuple(cc[:2])
radius = cc[2]
cv2.circle(canvas, center, thickness, center_color, thickness)
cv2.circle(canvas, center, radius, circle_color, thickness)
return canvas
#_debug['debug_preview'] = debug_preview
_debug.update(locals())
### circles 결과값을 원본 이미지 크기로 변환
cands = circles[0] * thumb_scale
#print('cands:', len(cands), cands[:5])
### 탐지된 Circle 이 적절한지 확인하기 위해 중심점의 범위를 설정
### 중심점이 설정된 범위 내부가 아니면 잘못 탐지한 것으로 간주함.
### 영상 취득시, 실린더의 중심이 이미지의 가운데 근처에 위치하도록
### 잘 촬영되어 있다고 간주함.
hh, ww = im.shape[:2]
cx_min, cx_max = int(ww * (0.5 - cb_fac)), int(ww * (0.5 + cb_fac))
cy_min, cy_max = int(hh * (0.5 - cb_fac)), int(hh * (0.5 + cb_fac))
### 중심점을 벗어나지 않은 circle 을 검색
for c in cands:
cx, cy = c[:2]
if (cx_min <= cx <= cx_max and cy_min <= cy <= cy_max):
break
else:
return None, None, None
### 중심점에서 가장 가까운 경계까지의 거리가 max_radius 가 됨
### 중심점에서 max_radius 보다 멀리 떨어진 점은 최종 변환 이미지에 포함되지 않음
max_radius = min(cx, cy, abs(hh-cy), abs(ww-cx))
### linearPolar 변환 적용 (deprecated)
# warped = cv2.linearPolar(im, (cx, cy), max_radius, cv2.INTER_LINEAR)
### warpPolar 적용
""". @param flags A combination of interpolation methods, #InterpolationFlags + #WarpPolarMode.
. - Add #WARP_POLAR_LINEAR to select linear polar mapping (default)
. - Add #WARP_POLAR_LOG to select semilog polar mapping
. - Add #WARP_INVERSE_MAP for reverse mapping.
. @note
. - The function can not operate in-place.
. - To calculate magnitude and angle in degrees #cartToPolar is used internally thus angles are measured from 0 to 360 with accuracy about 0.3 degrees.
. - This function uses #remap. Due to current implementation limitations the size of an input and output images should be less than 32767x32767.
"""
if dsize is None:
#dsize = (int(max_radius), int(2 * np.pi * max_radius))
#dsize = (int(max_radius), 1080) # 360 * 1/0.3
dsize = im.shape[1], im.shape[0]
warped = cv2.warpPolar(im, dsize, (cx, cy), max_radius, cv2.WARP_POLAR_LINEAR)
return warped, (cx, cy), max_radius
def transform_points(pts, dsize, center, max_radius):
"""
pts = list of (x, y)
dsize = (height, width)
center = (cx, cy)
"""
w, h = dsize
Kmag = float(w)/max_radius
Kangle = float(h)/(2 * np.pi)
pts = np.asarray(pts, dtype=float)
I = pts - center
x, y = I.transpose()
mag, angle = cv2.cartToPolar(x, y)
mag = mag.squeeze(axis=1)
angle = angle.squeeze(axis=1)
rho = (Kmag * mag).astype(int)
phi = (Kangle * angle).astype(int)
phi = (phi + h) % h
tr_pts = np.transpose([rho, phi])
return tr_pts
def inverse_transform_points(tr_pts, dsize, center, max_radius):
w, h = dsize
Kmag = float(w)/max_radius
Kangle = float(h)/(2 * np.pi)
tr_pts = np.asarray(tr_pts, dtype=float)
rho, phi = tr_pts.transpose()
mag = rho / Kmag
angle = phi / Kangle
ux, uy = cv2.polarToCart(mag, angle)
pts = np.squeeze([ux, uy], axis=2).transpose().astype(int) + center
return pts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment