φυβλαςのβλογ
บล็อกของ phyblas



opencv-python เบื้องต้น บทที่ ๑๓: การหาเส้นเค้าโครง
เขียนเมื่อ 2020/06/28 19:05
แก้ไขล่าสุด 2024/02/22 10:24

ต่อจาก บทที่ ๑๒

บทนี้ว่าด้วยเรื่องของการหาเส้นเค้าโครงซึ่งกั้นล้อมแบ่งอาณาเขตภายในภาพ




เส้นเค้าโครงคืออะไร

เส้นเค้าโครง (contour) คือเส้นที่กั้นระหว่าง ๒ บริเวณที่มีสีแตกต่างกันในภาพ

ยกตัวอย่างเช่นมีภาพง่ายๆที่แบ่งเป็นสีขาวดำแบบนี้อยู่


a13c01.png

เส้นเค้าโครงก็คือเส้นที่แบ่งเขตเชื่อมระหว่างสีขาวกับดำในภาพนี้ เส้นนี้มีความสำคัญ ถ้าหาเส้นเค้าโครงได้ก็สามารถเอาไปใช้ทำอะไรได้มากมาย ดังที่จะเขียนถึงในหัวข้อต่อๆไป

ใน opencv มีฟังก์ชัน cv2.findContours() ไว้ใช้ค้นหาเส้นเค้าโครงทั้งหมดที่มีอยู่ในภาพให้โดยอัตโนมัติ

ค่าที่ต้องใส่ในฟังก์ชันนี้มีอยู่ ๓​ ตัว คือ

ลำดับที่ ชื่อ ค่าที่ต้องใส่ ชนิดข้อมูล
1 image อาเรย์รูปภาพ np.array
2 mode วิธีการกำหนดชั้นของเส้นเค้าโครง flag (int)
3 method วิธีการประมาณเส้นเค้าโครง flag (int)

อาเรย์ภาพในที่นี้ต้องเป็นภาพขาวดำ ถ้าเป็นภาพสีก็ให้แปลงเป็นขาวดำก่อน

ค่าในอาเรย์อาจเป็นค่าอะไรก็ได้ตั้งแต่ 0 ถึง 255 แต่ cv2.findContours() จะพิจารณาแค่ว่าเป็น 0 (สีดำ) หรือไม่ ดังนั้นจุดแบ่งที่จะถูกตรวจจับก็คือบริเวณที่เปลี่ยนจากสีดำเป็นสีอื่น

ในส่วนของวิธีการกำหนดชั้นของเส้นเค้าโครง ใส่เป็นแฟล็กดังนี้

แฟล็ก ค่า ความหมาย
cv2.RETR_EXTERNAL 0 ค้นเอาเฉพาะวงนอกสุด
cv2.RETR_LIST 2 เอาทุกวงโดยไม่มีการสร้างเป็นลำดับชั้น
cv2.RETR_CCOMP 3 เอาทุกวงโดยมีการสร้างเป็นลำดับชั้นแต่ไม่ลึกลงไปกว่าสองชั้น
cv2.RETR_TREE 4 เอาทุกวงโดยพิจารณาลำดับชั้นให้ตัวข้างในเป็นเค้าโครงลูกของตัวนอกไล่ไปเรื่อยๆ

๓ ตัวหลังต่างก็จะหาเส้นเค้าโครงทั้งหมดเท่าที่จะหาได้ แต่จะแตกต่างตรงลำดับชั้น ซึ่งเกี่ยวกับเรื่องนี้จะเขียนถึงอีกทีภายหลัง

สำหรับกรณีทั่วไปที่ต้องการหาเส้นเค้าโครงทั้งหมดที่มีโดยไม่สนลำดับชั้นก็เลือกใช้ cv2.RETR_LIST

ส่วนตรงวิธีการประมาณเส้นเค้าโครงนั้นก็ใส่เป็นแฟล็ก ถ้าต้องการให้หาเค้าโครงแบบละเอียดเป็นจุดต่อจุดให้ใส่ cv2.CHAIN_APPROX_NONE แต่ในกรณีทั่วไปจะใช้ cv2.CHAIN_APPROX_SIMPLE ซึ่งหมายถึงประมาณเอาเท่าที่จำเป็น

ฟังก์ชันนี้จะคืนค่ามาให้ ๒ ตัว ตัวแรกคือข้อมูลของเส้นเค้าโครงที่หามาได้ ส่วนตัวหลังคือข้อมูลเรื่องลำดับชั้นของแต่ละเส้นเค้าโครง

ยกตัวอย่างการใช้โดยสร้างภาพแบบง่ายๆที่แค่มีสี่เหลี่ยมอยู่ตรงกลางภาพ
import cv2
import numpy as np
import matplotlib.pyplot as plt

rup = np.zeros([8,8],dtype=np.uint8) # สร้างภาพสีดำล้วน
rup[2:-2,2:-2] = 170 # ใส่สีเทาตรงกลาง
# เปลี่ยนเป็น RGB เพื่อแสดงผลใน matplotlib
plt.imshow(cv2.cvtColor(rup,cv2.COLOR_GRAY2RGB),cmap='gray')
plt.show()


แล้วลองใช้ฟังก์ชัน cv2.findContours() เพื่อหาเค้าโครงดู
contour,hierarchy = cv2.findContours(rup,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
print(contour) 
print(len(contour)) # จำนวนเส้นเค้าโครง
print(contour[0].shape) # อาเรย์แสดงข้อมูลของเส้นเค้าโครงตัวแรก

ผลลัพธ์ที่ได้ จะได้ข้อมูลของเส้นเค้าโครงมาดังนี้
[array([[[2, 2]],

       [[2, 5]],

       [[5, 5]],

       [[5, 2]]], dtype=int32)]
1
(4, 1, 2)

ซึ่งจะได้เป็นลิสต์ของอาเรย์สามมิติซึ่งแสดงตำแหน่งจุดของเส้นเค้าโครง ในที่นี้มีเส้นเค้าโครงเดียวเลยได้มาอาเรย์เดียว

มิติแรกของอาเรย์จะมีค่าเท่ากับจำนวนจุด ในที่นี้เป็นสี่เหลี่ยมจึงมี ๔ จุด ส่วนมิติที่ ๒ จะเป็น ๑ เสมอ และมิติที่ ๓ จะเป็น ๒ ค่าซึ่งแสดงถึง

ส่วนค่าคืนกลับตัวหลังคือลำดับชั้น (hierarchy) ของเส้นเค้าโครง ตอนนี้จะขอละไว้ก่อนเพราะมีรายละเอียดมาก จะเขียนถึงในหัวข้อถัดไป

ลองใส่สีที่ตำแหน่งแต่ละจุดของเส้นเค้าโครงเพื่อแสดงให้เห็นดูชัดว่าจุดมองของเค้าโครงคือตรงไหน
rup = cv2.cvtColor(rup,cv2.COLOR_GRAY2RGB)
for i in range(4):
    x,y = contour[0][i,0]
    rup[y,x] = (0,255,0)
plt.imshow(rup)
plt.show()


จะเห็นว่าจุดอยู่ที่ทั้ง ๔ มุมของของสี่เหลี่ยม

คราวนี้ลองหาเส้นเค้าโครงของภาพในตัวอย่างด้านบนสุดนั่น วาดจุดลงบนเส้น
rup = cv2.imread('a13c01.png',0)
contour,_ = cv2.findContours(rup,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
print(len(contour[0])) # ได้ 362
plt.imshow(cv2.cvtColor(rup,cv2.COLOR_GRAY2RGB))
x = contour[0][:,0,0]
y = contour[0][:,0,1]
plt.scatter(x,y,s=10,c='m')
plt.show()


เนื่องจากเต็มไปด้วยส่วนโค้งมาก จะได้เค้าโครงที่มีจุดอยู่มากมาย ในที่นี้มี ๓๖๒ จุด โดยล้อมอยู่รอบๆเค้าโครง เว้นแค่ตรงที่เป็นเส้นตรงยาวๆ




ว่าด้วยเรื่องการประมาณเส้นเค้าโครงโดยเว้นส่วนเส้นตรง

ดังที่ได้กล่าวถึงไปในตัวอย่างที่แล้วว่าวิธีการประมาณเส้นเค้าโครงมี cv2.CHAIN_APPROX_NONE กับ cv2.CHAIN_APPROX_SIMPLE

ในที่นี้จะแสดงตัวอย่างเพื่อให้เห็นความแตกต่างชัดเจน โดยลองสร้างอาเรย์ภาพสี่เหลี่ยมง่ายๆแบบตัวอย่างที่แล้วขึ้นแล้วใช้ cv2.findContours() โดยใช้ทั้ง ๒ วิธีนี้เทียบกัน
rup = np.zeros([20,20],dtype=np.uint8)
rup[4:-6,4:-6] = 120

contour,hierarchy = cv2.findContours(rup,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
print(contour[0].shape) # ได้ (4, 1, 2)

contour,hierarchy = cv2.findContours(rup,cv2.RETR_LIST,cv2.CHAIN_APPROX_NONE)
print(contour[0].shape) # ได้ (36, 1, 2)

plt.imshow(cv2.cvtColor(rup,cv2.COLOR_GRAY2RGB),cmap='gray')
plt.show()


ผลที่ได้จะเห็นว่า cv2.CHAIN_APPROX_SIMPLE จะมีแค่ ๔ จุด ก็คือที่มุมของสี่เหลี่ยม ในขณะที่ cv2.CHAIN_APPROX_NONE จะได้จุดทั้งหมดที่อยู่รอบเส้นเค้าโครง จึงเป็น ๓๖​ จุด

ในกรณีส่วนใหญ่มักไม่ได้จำเป็นต้องใช้จุดทั้งหมด เพราะถ้าเจอเส้นตรงยาวก็สามารถละได้ ดังนั้นจึงเลือก cv2.CHAIN_APPROX_SIMPLE ก็พอ




การวาดเส้นเค้าโครงลงบนภาพ

ใน opencv มีฟังก์ชัน cv2.drawContours() ซึ่งใช้สำหรับนำเอาข้อมูลเส้นเค้าโครงที่ได้มาจาก cv2.findContours() มาวาดเป็นเส้นใส่ลงบนภาพ

ค่าที่ต้องใส่ในฟังก์ชันนี้
ลำดับที่ ชื่อ ค่าที่ต้องใส่ ชนิดข้อมูล
1 image อาเรย์รูปภาพ np.array
2 contours ลิสต์ของข้อมูลเส้นเค้าโครง list ของ np.array
3 contourIdx ลำดับของเส้นเค้าโครงที่จะใช้ int
4 color สี tuple ของ int
5 thickness ความหนาของเส้นที่จะวาด
(สามารถละได้ ถ้าละจะหนาเป็น 1 พิกเซล)
int

ขอใช้ภาพนี้เป็นตัวอย่าง


a13c06.png

นำภาพมาหาเส้นเค้าโครงแล้วนำมาวาดเส้นเค้าโครงทุกเส้นด้วยสีต่างกัน
rup = cv2.imread('a13c06.png',0) # อ่านภาพเป็นขาวดำ
# หาเส้นเค้าโครง
contour,_ = cv2.findContours(rup,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
n_contour = len(contour) # จำนวนเส้นเค้าโครง
print(n_contour) # ได้ 9
rup = cv2.cvtColor(rup,cv2.COLOR_GRAY2BGR) # แปลงเป็นภาพสี
for i in range(n_contour):
    # สร้างสีต่างๆขึ้นจากฟังก์ชัน get_cmap
    r,g,b,a = plt.get_cmap('rainbow')(i/(n_contour-1))
    si = (b*255,g*255,r*255)
    rup = cv2.drawContours(rup,contour,i,si,2)
cv2.imwrite('a13c07.png',rup) # เขียนลงไฟล์

a13c07.png

ในที่นี้ใช้ฟังก์ชัน plt.get_cmap() ของ matplotlib เพื่อสร้างสี รายละเอียดวิธีใช้อ่านได้ใน numpy & matplotlib บทที่ ๒๔

ในตัวอย่างนี้ได้เส้นเค้าโครงออกมาทั้งหมด ๙ เส้น เนื่องจากด้านในมีส่วนสีดำเล็กๆน้อยแทรกอยู่ ทุกซอกหลืบเหล่านี้ก็นับด้วยเช่นกัน




การหาเค้าโครงของภาพสี

ถ้ามีภาพสีที่ดูแล้วมีขอบเขตที่แน่นอนก็สามารถนำมาหาเค้าโครงแบ่งภาพได้เช่นกัน โดยแปลงเป็นขาวดำ แล้วใช้ cv2.threshold() เพื่อทำให้เหลือแต่ 0 กับ 255 นำภาพนี้มาหาเค้าโครง จากนั้นเมื่อได้เค้าโครงแล้วก็นำไปใช้วาดลงบนภาพเก่าที่เป็นภาพสีได้

ตัวอย่าง


miku13c01.jpg
miku = cv2.imread('miku13c01.jpg') # อ่านภาพมาเป็นภาพสี
miku_gr = cv2.cvtColor(miku,cv2.COLOR_BGR2GRAY) # แปลงเป็นขาวดำ
_,miku_thr = cv2.threshold(miku_gr,10,255,0)
# หาเส้นเค้าโครง
contour,_ = cv2.findContours(miku_thr,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
# วาดเส้นเค้าโครงลงบนภาพเดิม
for i in range(len(contour)):
    miku = cv2.drawContours(miku,contour,i,(0,200,0),2)
cv2.imwrite('miku13c02.jpg',miku)

miku13c02.jpg

ผลที่ได้ก็จะได้เส้นเค้าโครงออกมา แต่ก็จะเห็นว่าประกอบด้วยจุดเล็กน้อยเส้นย่อยซอกหลืบด้านใน ซึ่งบางครั้งเราอาจไม่ได้ต้องการด้วย เพื่อที่จะเอามันออกไป วิธีหนึ่งที่นิยมใช้ก็คือแปลงสัณฐาน cv2.morphologyEx() ด้วยโหมด MORPH_CLOSE วิธีนี้จะช่วยลบจุดดำเล็กๆด้านในไปได้

ลองแปลงสัณฐานแล้วหาเส้นเค้าโครงใหม่เหมือนเดิม
miku = cv2.imread('miku13c01.jpg')
miku_gr = cv2.cvtColor(miku,cv2.COLOR_BGR2GRAY)
_,miku_thr = cv2.threshold(miku_gr,10,255,0)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5))
miku_thr = cv2.morphologyEx(miku_thr,cv2.MORPH_CLOSE,kernel)
contour,_ = cv2.findContours(miku_thr,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
for i in range(len(contour)):
    miku = cv2.drawContours(miku,contour,i,(0,200,0),2)
cv2.imwrite('miku13c03.jpg',miku)

miku13c03.jpg

คราวนี้เส้นเค้าโครงตามซอกเล็กซอกน้อยหายไป ดูแล้วเรียบกว่าเดิม

จากนั้นอาจลองนำเค้าโครงมาวาดด้วย matplotlib โดยใช้ plt.Polygon()
ax = plt.axes(aspect=1,xlim=[0,600],ylim=[450,0])
for cnt in contour:
    ax.add_patch(plt.Polygon(cnt[:,0],fc='c',ec='r'))
plt.show()





ลำดับชั้นของเส้นเค้าโครง

ดังที่ได้กล่าวไปข้างต้นแล้วว่าฟังก์ชัน cv2.findContours() นอกจากจะคืนข้อมูลตำแหน่งจุดของเส้นเค้าโครงมาแล้วก็ยังให้ข้อมูลลำดับชั้นของแต่ละเค้าโครงมาด้วย โดยจะให้มาเป็นค่าคืนกลับตัวที่ ๒ ของฟังก์ชัน

ข้อมูลลำดับชั้นจะเป็นตัวเลข ๔​ ตัว ซึ่งเป็นดัชนีของเส้นเค้าโครงตัวอื่น ซึ่งบอกถึง [ตัวถัดไป, ตัวก่อนหน้า, ตัวลูกตัวแรก, ตัวพ่อแม่]

เลขลำดับนั้นจะไล่ตั้งแต่ 0 ไป ถ้าส่วนประกอบไหนขาดไปจะเป็น -1

ลองสร้างรูปง่ายๆที่มีสี่เหลี่ยมซ้อนกันหลายชั้นขึ้นมา
rup = np.zeros([500,600],dtype=np.uint8)
rup[50:-50,50:-50] = 200
rup[100:-100,100:250] = 0
rup[100:-100,-250:-100] = 0
rup[150:200,150:200] = 240
rup[-250:-150,150:200] = 60
rup[150:-150,-200:-150] = 120
cv2.imwrite('a13c08.png',rup)

a13c08.png

ในภาพนี้ประกอบด้วยกรอบสี่เหลี่ยม ๖ อัน ถ้าหาเค้าโครงก็จะได้มา ๖ ตัว

ลองหาเค้าโครงดูโดยเลือกวิธีการกำหนดชั้นของเส้นเค้าโครงเป็น cv2.RETR_TREE แล้วดูข้อมูลลำดับชั้น
contour,hierar = cv2.findContours(rup,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
print(hierar)

[[[-1 -1  1 -1]
  [ 3 -1  2  0]
  [-1 -1 -1  1]
  [-1  1  4  0]
  [ 5 -1 -1  3]
  [-1  4 -1  3]]]

จะได้อาเรย์ขนาด 6×4 เป็นข้อมูลลำดับชั้นของเส้นเค้าโครงทั้ง ๖ ตัว

แถวแรกคือตัวนอกสุด [-1 -1 1 -1] จะเห็นว่าไม่มีทั้งตัวลำดับก่อนหน้าและหลังและไม่มีตัวพ่อแม่ด้วยเพราะเป็นตัวนอกสุดแล้ว จึงเป็น -1 ส่วนตัวลูกตัวแรกคือหมายเลข 1 นั่นหมายถึงตัวถัดไป

ตัวที่แถวถัดมา [ 3 -1 2 0] แสดงให้เห็นว่ามีตัวลูกอยู่ คือตัวหมายเลข 2 และตัวพ่อแม่คือตัวหมายเลข 0 คือตัวนอกสุด

ที่เหลือก็ไล่ไปเรื่อยๆก็จะเห็นภาพและเข้าใจความหมาย เห็นความสัมพันธ์ของแต่ละตัวได้

หากเปลี่ยนเป็น cv2.RETR_LIST ผลจะกลายเป็นแบบนี้
[[[ 1 -1 -1 -1]
  [ 2  0 -1 -1]
  [ 3  1 -1 -1]
  [ 4  2 -1 -1]
  [ 5  3 -1 -1]
  [-1  4 -1 -1]]]

จะเห็นว่าเลข ๒​ ตัวหลังเป็น -1 หมด เพราะไม่มีการสร้างความสัมพันธ์พ่อแม่ลูกขึ้น แต่ละตัวถือว่าแยกจากกันไม่ได้ซ้อนกัน ถือว่าอยู่ชั้นที่ ๑ หมด

ถ้าใช้เป็น cv2.RETR_CCOMP
[[[ 1 -1 -1 -1]
  [ 2  0 -1 -1]
  [ 3  1 -1 -1]
  [-1  2  4 -1]
  [ 5 -1 -1  3]
  [-1  4 -1  3]]]

ในที่นี้ตัวหมายเลข 3 คือตัวเค้าโครงนอกสุด ส่วนตัวหมายเลข 4 และ 5 คือช่องสีดำที่ซ้อนอยู่ในนั้น ส่วนอีก ๓​ ตัวที่เหลือแม้จะอยู่ด้านในลงไปอีกแต่จะถือว่าไม่มีการสร้างลำดับชั้น

ถ้าเป็น cv2.RETR_EXTERNAL จะได้
[[[-1 -1 -1 -1]]]

คือจะได้มาเฉพาะตัวที่อยู่นอกสุดเท่านั้น ซึ่งมีตัวเดียว เหมาะจะใช้เวลาที่สนใจเฉพาะโครงด้านนอกสุดจริงๆ



อ่านบทถัดไป >> บทที่ ๑๔



-----------------------------------------

囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧

ดูสถิติของหน้านี้

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> opencv
-- คอมพิวเตอร์ >> เขียนโปรแกรม >> python >> numpy
-- คอมพิวเตอร์ >> เขียนโปรแกรม >> python >> matplotlib

ไม่อนุญาตให้นำเนื้อหาของบทความไปลงที่อื่นโดยไม่ได้ขออนุญาตโดยเด็ดขาด หากต้องการนำบางส่วนไปลงสามารถทำได้โดยต้องไม่ใช่การก๊อปแปะแต่ให้เปลี่ยนคำพูดเป็นของตัวเอง หรือไม่ก็เขียนในลักษณะการยกข้อความอ้างอิง และไม่ว่ากรณีไหนก็ตาม ต้องให้เครดิตพร้อมใส่ลิงก์ของทุกบทความที่มีการใช้เนื้อหาเสมอ

สารบัญ

รวมคำแปลวลีเด็ดจากญี่ปุ่น
มอดูลต่างๆ
-- numpy
-- matplotlib

-- pandas
-- manim
-- opencv
-- pyqt
-- pytorch
การเรียนรู้ของเครื่อง
-- โครงข่าย
     ประสาทเทียม
ภาษา javascript
ภาษา mongol
ภาษาศาสตร์
maya
ความน่าจะเป็น
บันทึกในญี่ปุ่น
บันทึกในจีน
-- บันทึกในปักกิ่ง
-- บันทึกในฮ่องกง
-- บันทึกในมาเก๊า
บันทึกในไต้หวัน
บันทึกในยุโรปเหนือ
บันทึกในประเทศอื่นๆ
qiita
บทความอื่นๆ

บทความแบ่งตามหมวด



ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ

  ค้นหาบทความ

  บทความแนะนำ

ตัวอักษรกรีกและเปรียบเทียบการใช้งานในภาษากรีกโบราณและกรีกสมัยใหม่
ที่มาของอักษรไทยและความเกี่ยวพันกับอักษรอื่นๆในตระกูลอักษรพราหมี
การสร้างแบบจำลองสามมิติเป็นไฟล์ .obj วิธีการอย่างง่ายที่ไม่ว่าใครก็ลองทำได้ทันที
รวมรายชื่อนักร้องเพลงกวางตุ้ง
ภาษาจีนแบ่งเป็นสำเนียงอะไรบ้าง มีความแตกต่างกันมากแค่ไหน
ทำความเข้าใจระบอบประชาธิปไตยจากประวัติศาสตร์ความเป็นมา
เรียนรู้วิธีการใช้ regular expression (regex)
การใช้ unix shell เบื้องต้น ใน linux และ mac
g ในภาษาญี่ปุ่นออกเสียง "ก" หรือ "ง" กันแน่
ทำความรู้จักกับปัญญาประดิษฐ์และการเรียนรู้ของเครื่อง
ค้นพบระบบดาวเคราะห์ ๘ ดวง เบื้องหลังความสำเร็จคือปัญญาประดิษฐ์ (AI)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ

บทความแต่ละเดือน

2024年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2023年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2022年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2021年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2020年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

ค้นบทความเก่ากว่านั้น

ไทย

日本語

中文