11
10

More than 3 years have passed since last update.

立科町の形で学ぶ一番細い場所の測定方法

Posted at

はじめに

長野県の地図を見ていると気になるものがありました。

立科町.PNG

この町、めっちゃ細いところがありますね。

これは長さを測ってみるしかないですね

どれくらい細いのか、調べてみました。

手順

立科町の画像を用意し、グレースケールにします。

img = cv2.imread("tateshina.png",3)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
thresh,bin_img = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
plt.imshow(bin_img,cmap='gray')

モノクロ立科.png

よくある、モノクロ立科町が出来ました。

2つ以上に分割する

この状態から任意の二点を取得して、最短距離を求めても望みのものは出ないので
二分割しましょう。

itter=1
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(3,3))
while True :
    erosion = cv2.erode(bin_img,kernel,iterations = itter)
    contours_erosion, _ = cv2.findContours(erosion, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    if len(contours_erosion) > 1:
        break
    itter += 1
    plt.imshow(erosion)
    plt.show()

kernelを用意して、少しずつerodeをかけていき、二つに分割されたところで切ります。

きれしな.png

2つのブロブの最短距離を探す

分かりにくいですが、切科町になりました。

この時の2つのブロブの最短距離を求めましょう。

def get_distance(x1, y1, x2, y2): 
    return math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)

distance_list = []
ch_list = []
for i in  (contours_erosion[0]):
    for j in (contours_erosion[1]):
        distance_list.append(get_distance(*i[0],*j[0]))
        ch_list.append([i,j])
ch_list[np.argmin(distance_list)]
x1 = ch_list[np.argmin(distance_list)][0][0][0]
y1 = ch_list[np.argmin(distance_list)][0][0][1]
x2 = ch_list[np.argmin(distance_list)][1][0][0]
y2 = ch_list[np.argmin(distance_list)][1][0][1]

fig, ax = plt.subplots(figsize=(8, 8))
fig = 0
cv2.line(erosion, (x1, y1), (x2, y2), (128, 128, 255), thickness=1)
plt.imshow(erosion,cmap="gray")

つなぎ科町.png

最短となる線分が引けました。

何がしたかったと申しますと、この線を通る任意の二点を結ぶ線分がもっとも細い箇所となるので
それらを求めたいと思います。

エッジを取得する

エッジを取得しましょう。

edge = cv2.morphologyEx(bin_img, cv2.MORPH_GRADIENT, kernel)
plt.imshow(edge,cmap="gray")

エッジな.png

サイバー立科町が取れました。これで輪郭の座標が取れますね。

分割した線に近い点を集める

任意の二点で計算してもいいですが、計算量を減らす工夫をしましょう。

今回は先ほど引いた2点の重心からの距離が近い点を集めましょう。

row_list = np.where(edge == 255)[0]
col_list = np.where(edge == 255)[1]
near_list = []
for i in range(len(col_list)):
    u = np.array([(x2 + x1)/2, (y2 + y1)/2])
    v = np.array([col_list[i], row_list[i]])
    L = np.linalg.norm(u-v)
    if L > itter*2:
        continue
    near_list.append(v)

これで、多少点は減らせたはずです。

最短の線を通る2点を探す

2つの線分が、交わっているか否かは

def intersect(p1, p2, p3, p4):
    tc1 = (p1[0] - p2[0]) * (p3[1] - p1[1]) + (p1[1] - p2[1]) * (p1[0] - p3[0])
    tc2 = (p1[0] - p2[0]) * (p4[1] - p1[1]) + (p1[1] - p2[1]) * (p1[0] - p4[0])
    td1 = (p3[0] - p4[0]) * (p1[1] - p3[1]) + (p3[1] - p4[1]) * (p3[0] - p1[0])
    td2 = (p3[0] - p4[0]) * (p2[1] - p3[1]) + (p3[1] - p4[1]) * (p3[0] - p2[0])
    return tc1*tc2<0 and td1*td2<0

shortest_list = []
from itertools import combinations
comb = list(combinations(near_list,2))
for tmp in comb:
    if (intersect([x1,y1],[x2,y2],tmp[0],tmp[1])) == 1:
        L = np.linalg.norm(np.array(tmp[0])-np.array(tmp[1]))
        shortest_list.append(L)
    else:
        shortest_list.append(50000)

もし交わっているなら長さを保存し、そうでないなら、あり得ない距離を入れています。

short = np.argmin(shortest_list)
f1x,f1y,f2x,f2y=*comb[short][0],*comb[short][1]
cv2.line(erosion,(f1x,f1y) ,(f2x,f2y), (192,0, 192), thickness=1)
res = cv2.addWeighted(gray, 0.5, erosion, 0.5, 0)
fig, ax = plt.subplots(figsize=(32, 32))
plt.imshow(res)

result.png

小さくてわかりにくいですが、最短の場所が分かりました。

これで、実際に立科町で測定する必要がなくなりました。

総括

画像処理は単純な処理が多い分、複雑なことが出来て面白いですね。
縮小や拡大の方法に不安があるので、ほかに良い方法がないかは考えてみたいと思います。

 参考

11
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
10