ฉันจะหาปริมาณความตรงของเส้นตรงได้อย่างไร


37

ฉันกำลังทำงานกับเกมที่ต้องการให้ผู้เล่นวาดเส้นจากจุด A (x1, y1) ไปยังอีกจุด B (x2, y2) บนหน้าจอของอุปกรณ์ Android

ฉันต้องการค้นหาว่าการวาดที่เหมาะสมกับเส้นตรงนั้นดีแค่ไหน ตัวอย่างเช่นผลลัพธ์ 90% จะหมายถึงการวาดเส้นนั้นเหมาะกับเส้น หากผู้เล่นลากเส้นโค้งจาก A ถึง B ก็ควรได้คะแนนต่ำ

ไม่ทราบจุดสิ้นสุดล่วงหน้า ฉันจะทำสิ่งนี้ได้อย่างไร


1
คุณรู้ล่วงหน้าหรือไม่ว่าจุดสิ้นสุดสองจุดของคุณคืออะไร? หรือถูกกำหนดในขณะที่ผู้ใช้หยุดสัมผัสหน้าจอ
Vaillancourt

ขออภัยถ้าคำอธิบายของฉันไม่ชัดเจนสำหรับคุณ จุดเริ่มต้น A (x, y) คือสัมผัสแรกและจุดสิ้นสุด B (x, y) คือเมื่อเราปล่อยจากหน้าจอสัมผัสตามที่คุณพูด
3637362

เรามีคำถามที่เกี่ยวข้องในการจับคู่ตัวอักษรเล่นวาด
Anko

3
โปรดอย่าโพสต์ภาพสำหรับซอร์สโค้ดในอนาคต
Josh

1
@ user3637362 ผมเข้าใจว่าคุณจะเริ่มต้นj=1เพื่อให้คุณสามารถเปรียบเทียบtouchList[j]กับtouchList[j-1]แต่เมื่อtouch.phase == TouchPhase.Beganหรือtouch.phase == TouchPhase.Endedตำแหน่งจะไม่เพิ่มไปและต่อมาไม่รวมอยู่ในtouchList sumLengthข้อผิดพลาดนี้จะปรากฏในทุกกรณี แต่จะมีความชัดเจนมากขึ้นเมื่อบรรทัดมีบางส่วน
เคลลี่โทมัส

คำตอบ:


52

sqrt((x1-x2)² + (y1-y2)²)เป็นเส้นตรงอย่างสมบูรณ์แบบนอกจากนี้ยังจะเป็นเส้นที่สั้นที่สุดมีความยาวรวมของ เส้นที่มีลายเส้นมากกว่านั้นจะเป็นการเชื่อมต่อที่เหมาะสมน้อยกว่าและจะยาวกว่าอย่างหลีกเลี่ยงไม่ได้

เมื่อคุณใช้แต่ละจุดของเส้นทางที่ผู้ใช้ดึงและสรุประยะทางระหว่างพวกเขาคุณสามารถเปรียบเทียบความยาวทั้งหมดกับความยาวในอุดมคติ ยิ่งความยาวทั้งหมดน้อยลงหารด้วยความยาวในอุดมคติ

นี่คือการสร้างภาพข้อมูล เมื่อจุดสีดำเป็นจุดสิ้นสุดของท่าทางและจุดสีฟ้าเป็นจุดที่คุณวัดระหว่างท่าทางคุณจะคำนวณและบวกความยาวของเส้นสีเขียวและหารด้วยความยาวของเส้นสีแดง:

ป้อนคำอธิบายรูปภาพที่นี่

คะแนนหรือดัชนีความไซน์ของ 1 จะสมบูรณ์แบบทุกอย่างที่สูงกว่าจะสมบูรณ์แบบน้อยลงและต่ำกว่า 1 จะเป็นจุดบกพร่อง เมื่อคุณต้องการให้คะแนนเป็นเปอร์เซ็นต์ให้หาร 100% ด้วยหมายเลขนั้น


34
มีปัญหาเล็กน้อยเกี่ยวกับวิธีการนี้ในโพลีนที่มีความยาวเท่ากันจะไม่ 'ตรง' เท่ากัน เส้นที่โยกเยกที่มีความเบี่ยงเบนต่ำ (แต่หลายครั้ง) เกี่ยวกับเส้นตรงคือ 'straighter' มากกว่าเส้นที่มีความยาวเท่ากันซึ่งเบี่ยงเบนไปที่จุดเดียวและกลับมา
Dancrumb

ฉันไม่สามารถ +1 @Dancrumbs แสดงความคิดเห็นได้ - นั่นเป็นข้อ จำกัด ที่สำคัญสำหรับวิธีนี้ราวกับว่าผู้ใช้วาดเส้นตรงพวกเขาจะโยกเยกเล็กน้อยดังนั้นนี่จึงเป็นกรณีที่ใช้งานทั่วไป
ต. Kiley

@DanRumb เพียงแค่คำนึงถึงระยะทางเฉลี่ยจากเส้นหรือปัจจัยใน "ระยะทางสูงสุด" จุดใด ๆ ที่มาจากเส้น จากนั้นคุณสามารถเพิ่มน้ำหนักอัลกอริธึมไปยังบรรทัดที่สั่นคลอนมากขึ้นด้วยแอมพลิจูดเบี่ยงเบนที่เล็กกว่าและห่างจากบรรทัดที่หลงทางไกลจากเส้นทางที่คาดไว้
Superdoggy

2
@Dumbrumb ฟังดูเหมือนฉันแบบนี้มันอาจจะเป็นประโยชน์สำหรับกรณีการใช้งานของ OP แน่นอนว่าเส้นที่วาดด้วยมือจะมีความเบี่ยงเบนเล็กน้อย วิธีนี้อาจใช้เพื่อลดผลกระทบจากความแตกต่างที่คาดหวังเหล่านี้

2
@ user3637362 คุณมีข้อบกพร่องในรหัสของคุณ คำอธิบายที่เป็นไปได้คือคุณลืมที่จะอธิบายระยะห่างระหว่างจุดเริ่มต้นและจุดแรกหรือจุดสิ้นสุดและจุดสุดท้าย แต่โดยไม่ต้องดูรหัสของคุณมันเป็นไปไม่ได้ที่จะบอกความผิดพลาดของคุณ
ฟิลิปป์

31

นี่อาจไม่ใช่วิธีที่ดีที่สุดในการใช้สิ่งนี้ แต่ฉันขอแนะนำRMSD (ค่าเฉลี่ยเบี่ยงเบนกำลังสอง) จะดีกว่าวิธีการระยะทางในกรณีที่ Dancrumb กล่าวถึง (ดูสองบรรทัดแรกด้านล่าง)

RMSD = sqrt(mean(deviation^2))

บันทึก:

  • ผลรวมของการเบี่ยงเบนสัมบูรณ์ (เหมือนอินทิกรัล) อาจจะดีกว่าเนื่องจากมันจะไม่เฉลี่ยข้อผิดพลาดเชิงบวกกับค่าลบ ( =sum(abs(deviation)))
  • คุณอาจจะต้องค้นหาระยะทางที่สั้นที่สุดไปยังเส้นตรงหากมีวิธีที่สร้างระยะทางสั้นกว่าการวางฉากตั้งฉาก

การวาดภาพ

(โปรดแก้ตัวภาพวาดของฉันที่มีคุณภาพต่ำ)

อย่างที่คุณเห็นคุณต้อง

  1. หาเวกเตอร์มุมฉากกับเส้นของคุณ ( dot-product เท่ากับ 0 )
    หากบรรทัดของคุณชี้ไปทางที่(1, 3) คุณต้องการ(3, -1)(รางแต่ละต้น)
  2. วัดระยะทาง hจากเส้นในอุดมคติไปยังผู้ใช้หนึ่งขนานกับเวกเตอร์นั้น
  3. คำนวณ RMSD หรือผลรวมของความแตกต่างแบบสัมบูรณ์

คำตอบของ Joel Bosveld บ่งบอกถึงกรณีที่น่าสนใจ: เส้นตรงเกือบสมบูรณ์แบบด้วยมุมที่จุดเริ่มต้นและจุดสิ้นสุด ถ้าผู้ใช้ลากเส้นอย่างอิสระโดยผู้ใช้นี่เป็นปัญหาแน่นอน อย่างไรก็ตามฉันคิดว่าวิธีนี้สามารถครอบคลุมสถานการณ์นั้นได้ หนึ่งสามารถดำเนินการได้พอดีกับRMSDหรือIntegral แบบสัมบูรณ์เป็นค่าสูงสุดลด ค่าเริ่มต้นอาจเป็นจุดเริ่มต้นและจุดสิ้นสุด เนื่องจากความยาวไม่สำคัญไม่ว่าจะเป็นสิ่งที่ไม่เกี่ยวข้องหากการปรับให้เหมาะสมย้ายจุดเพื่อให้เส้นในอุดมคติยื่นออกไปไกลกว่าหรือสั้นกว่า
gr4nt3d

1
อีกกรณีนี้ดูเหมือนจะไม่ครอบคลุม: บอกว่าทุกจุดที่วัดได้อยู่บนแกน x แต่เส้นกลับทิศทางหลายครั้ง สิ่งนี้จะส่งคืนข้อผิดพลาดของ 0
เดฟ mankoff

23

คำตอบที่มีอยู่ไม่ได้คำนึงถึงว่าจุดสิ้นสุดนั้นเป็นไปตามอำเภอใจ (มากกว่าที่กำหนด) ดังนั้นเมื่อทำการวัดความตรงของเส้นโค้งมันไม่สมเหตุสมผลที่จะใช้จุดสิ้นสุด (ตัวอย่างเช่นในการคำนวณความยาวมุมตำแหน่งที่คาดหวัง) ตัวอย่างง่ายๆจะเป็นเส้นตรงที่มีปลายทั้งสอง kincked หากเราวัดระยะทางจากเส้นโค้งและเส้นตรงระหว่างจุดสิ้นสุดสิ่งนี้จะค่อนข้างใหญ่เนื่องจากเส้นตรงที่เราวาดนั้นถูกชดเชยจากเส้นตรงระหว่างจุดสิ้นสุด

เราจะบอกได้อย่างไรว่าเส้นโค้งเป็นอย่างไร สมมติว่าเส้นโค้งเรียบพอเราอยากรู้ว่าโดยเฉลี่ยแล้วการเปลี่ยนแทนเจนต์กับเส้นโค้งนั้นเปลี่ยนไปเท่าไหร่ สำหรับบรรทัดนี่จะเป็นศูนย์ (เนื่องจากแทนเจนต์คงที่)

ถ้าเราปล่อยให้ตำแหน่งในเวลา t เป็น (x (t), y (t)), แล้วแทนเจนต์คือ (Dx (t), Dy (t)), ที่ Dx (t) คืออนุพันธ์ของ x ณ เวลา t (ไซต์นี้ดูเหมือนว่าขาดการสนับสนุน TeX) หากเส้นโค้งไม่ได้แปรตามความยาวส่วนโค้งเราจะทำให้เป็นมาตรฐานโดยการหารด้วย || (Dx (t), Dy (t)) || เรามีเวกเตอร์หน่วย (หรือมุม) ของแทนเจนต์กับส่วนโค้งในเวลา t ดังนั้นมุมคือ (t) = (Dx (t), Dy (t)) / || (Dx (t), Dy (t)) | |

เรามีความสนใจใน | | Da (t) || ^ 2 รวมอยู่ในเส้นโค้ง

เนื่องจากเราน่าจะมีจุดข้อมูลที่แยกจากกันมากกว่าเป็นโค้งเราต้องใช้ความแตกต่างแน่นอนเพื่อประมาณอนุพันธ์ ดังนั้นดา (t) (a(t+h)-a(t))/hจะกลายเป็น และเป็น (t) ((x(t+h)-x(t))/h,(y(t+h)-y(t))/h)/||((x(t+h)-x(t))/h,(y(t+h)-y(t))/h)||จะกลายเป็น จากนั้นเราจะได้ S โดยการสรุปh||Da(t)||^2สำหรับดาต้าพอยน์ทั้งหมดและอาจทำให้ปกติเป็นความยาวของส่วนโค้ง เป็นไปได้มากที่เราใช้h=1แต่จริงๆแล้วมันเป็นเพียงตัวคูณสเกลโดยพลการ

หากต้องการย้ำอีกครั้ง S จะเป็นศูนย์สำหรับหนึ่งบรรทัดและยิ่งใหญ่กว่าก็จะเบี่ยงเบนจากบรรทัดมากขึ้น 1/(1+S)แปลงที่จำเป็นต้องใช้รูปแบบการใช้งาน เนื่องจากสเกลนั้นค่อนข้างเป็นการสุ่มคุณสามารถคูณ S ด้วยจำนวนบวกบางส่วน (หรือแปลงด้วยวิธีอื่นเช่นใช้ bS ^ c แทนที่จะเป็น S) เพื่อปรับว่าเส้นโค้งบางเส้นตรงเป็นอย่างไร


2
นี่คือนิยามที่ชัดเจนที่สุดของความตรง
Marcks โทมัส

1
นี่คือคำตอบที่สมเหตุสมผลที่สุดและฉันแน่ใจว่าคนอื่น ๆ จะรู้สึกผิดหวังมาก น่าเสียดายที่รูปแบบที่นำเสนอวิธีการแก้ปัญหานั้นค่อนข้างคลุมเครือกว่ารูปแบบอื่นเล็กน้อย แต่ฉันขอแนะนำ OP ให้คงอยู่
Dan Sheppard

โดยทั่วไปฉันยังคิดว่าคำตอบนี้ดีที่สุดแน่นอน แม้ว่าปัญหาจะทำให้ฉันรำคาญใจ: จะเกิดอะไรขึ้นถ้าบรรทัดไม่ "ราบรื่นพอ" เช่นถ้าคุณมีส่วนของเส้นตรงสองส่วนที่มีมุมเป็นมุม 90 ° ฉันเข้าใจผิดหรือว่าผลลัพธ์นี้จะค่อนข้างต่ำเมื่อเทียบกับเส้นตรงที่ราบรื่นจริง ๆ หรือไม่? (ฉันคิดว่ากรณีของผู้ใช้ Dancrumb ที่มีบรรทัดสั่นคลอนเป็นปัญหาที่คล้ายกัน) ... ในพื้นที่นี้เป็นวิธีที่ดีที่สุด
gr4nt3d

3

นี่คือระบบที่ใช้กริด ค้นหาคะแนนของคุณเองสำหรับบรรทัดและคำนวณความชันของเส้น ตอนนี้ใช้การคำนวณนั้นกำหนดจุดที่ถูกต้องที่เส้นจะผ่านให้กำหนดระยะขอบของความผิดพลาดบางส่วนจากค่าที่แน่นอน

ผ่านการทดสอบจำนวนสั้น ๆ ของการทดสอบและข้อผิดพลาดตรวจสอบว่ามีจุดการจับคู่ที่ดีและไม่ดีเท่าไรและตั้งค่าเกมของคุณโดยใช้สเกลสำหรับผลลัพธ์เดียวกันจากการทดสอบของคุณ

เช่นเส้นสั้น ๆ ที่มีความลาดชันเกือบแนวนอนอาจมี 7 คะแนน หากคุณสามารถจับคู่ 6 หรือมากกว่าจาก 7 อย่างต่อเนื่องที่กำหนดให้เป็นส่วนหนึ่งของเส้นตรงนั่นจะเป็นคะแนนสูงสุด การให้คะแนนความยาวและความแม่นยำควรเป็นส่วนหนึ่งของการให้คะแนน


3

การวัดที่ง่ายและใช้งานง่ายคือพื้นที่ระหว่างเส้นตรงที่เหมาะสมที่สุดกับเส้นโค้งจริง การพิจารณาว่าสิ่งนี้ค่อนข้างตรงไปตรงมา:

  1. ใช้รูปสี่เหลี่ยมจัตุรัสที่น้อยที่สุดในทุกจุด (ซึ่งจะป้องกันปัญหาปลายหงิกงอที่ Joel Bosveld พูดถึง)
  2. สำหรับทุกจุดบนเส้นโค้งให้กำหนดระยะทางกับเส้น นี่เป็นปัญหามาตรฐานด้วย (พีชคณิตเชิงเส้น, การแปลงฐาน)
  3. รวมระยะทางทั้งหมด

คุณพอจะทราบได้ไหมว่าฉันขอให้คุณเขียนข้อความ (JS, C #) หรือหลอกรหัสเนื่องจากคำตอบส่วนใหญ่อธิบายไว้ในทฤษฎีฉันไม่รู้ว่าจะเริ่มอย่างไร
user3637362

1
@ user3637362: StackOverflow มีคำตอบในทางปฏิบัติ: stackoverflow.com/questions/6195335/... stackoverflow.com/questions/849211/...
MSalters

2

แนวคิดคือการรักษาจุดทั้งหมดที่ผู้ใช้สัมผัสจากนั้นประเมินและรวมระยะห่างระหว่างแต่ละจุดเหล่านั้นกับบรรทัดที่เกิดขึ้นเมื่อผู้ใช้ปล่อยหน้าจอ

นี่คือสิ่งที่จะช่วยให้คุณเริ่มต้นในรหัสหลอก:

bool mIsRecording = false;
point[] mTouchedPoints = new point[];

function onTouch
  mIsRecording = true

functon update
  if mIsRecording
    mTouchedPoints.append(currentlyTouchedLocation)

function onRelease
  mIsRecording = false

  cumulativeDistance = 0

  line = makeLine( mTouchedPoints.first, mTouchedPoints.last )

  for each point in mTouchedPoints:
    cumulativeDistance = distanceOfPointToLine(point, line)

  mTouchedPoints = new point[]

อะไรคือสิ่งที่cumulativeDistanceทำให้คุณมีความคิดที่เหมาะสม ระยะทาง 0 หมายถึงผู้ใช้อยู่บนเส้นตลอดเวลา ตอนนี้คุณต้องทำการทดสอบเพื่อดูว่ามันทำงานอย่างไรในบริบทของคุณ และคุณอาจต้องการขยายค่าที่ส่งคืนdistanceOfPointToLineโดยการยกกำลังสองเพื่อปรับระยะห่างให้ห่างจากเส้น

ฉันไม่คุ้นเคยกับความสามัคคี แต่รหัสในupdateที่นี้อาจไปในonDragฟังก์ชั่น

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


5
เมื่อคุณเพิ่มระยะห่างระหว่างเส้นในอุดมคติและจุดสำหรับทุกจุดที่วัดคุณจะต้องคำนึงถึงจำนวนการวัดของคุณมิฉะนั้นเมื่อผู้ใช้ดึงช้าลงหรือใช้อุปกรณ์ที่มีอัตราการสแกนเร็วขึ้นพวกเขาจะลงทะเบียนมากขึ้น คะแนนซึ่งหมายความว่าพวกเขาจะได้รับคะแนนแย่ลง
Philipp

@Phippipp ใช่คุณทำ! ฉันต้องยอมรับวิธีที่คุณทำมันดูดีกว่าของฉัน: P
Vaillancourt

ฉันคิดว่าวิธีนี้ได้รับการปรับปรุงโดยใช้ระยะทางเฉลี่ยมากกว่าระยะทางสะสม
Dancrumb

@DanRumb จริง ๆ มันขึ้นอยู่กับความต้องการ แต่ใช่ว่าจะเป็นวิธีที่จะทำ
Vaillancourt

2

วิธีหนึ่งที่คุณสามารถใช้คือการแบ่งบรรทัดออกเป็นเซ็กเมนต์และทำผลิตภัณฑ์เวกเตอร์ดอทระหว่างเวกเตอร์แต่ละอันที่แทนส่วนและเวกเตอร์ที่แสดงเส้นตรงระหว่างจุดแรกและจุดสุดท้าย นี่คือข้อดีของการให้คุณค้นหาเซ็กเมนต์ "แหลมคม" ได้อย่างง่ายดาย

แก้ไข:

นอกจากนี้ฉันจะพิจารณาใช้ความยาวของส่วนเพิ่มเติมจากผลิตภัณฑ์ดอท เวกเตอร์ที่สั้นมาก แต่ orthogonal ควรนับน้อยกว่าเวกเตอร์ที่มีความเบี่ยงเบนน้อยกว่า


1

วิธีที่ง่ายที่สุดและเร็วที่สุดอาจเป็นเพียงการค้นหาว่าเส้นหนาแค่ไหนที่จะครอบคลุมจุดทั้งหมดของเส้นที่วาดโดยผู้ใช้

ยิ่งเส้นหนาขึ้นเท่าไรผู้ใช้ก็ยิ่งวาดเส้นได้ยาก


0

อย่างใดหมายถึงคำตอบ MSalters นี่คือข้อมูลที่เฉพาะเจาะจงมากขึ้น

ใช้วิธีกำลังสองน้อยที่สุดเพื่อให้พอดีกับเส้นสำหรับคะแนนของคุณ คุณกำลังมองหาฟังก์ชั่น y = f (x) ซึ่งเหมาะสมที่สุด เมื่อคุณมีแล้วคุณสามารถใช้ค่า y จริงเพื่อหาผลรวมของความแตกต่าง:

s = ยอดรวม ((yf (x)) ^ 2)

ยิ่งผลรวมมีขนาดเล็กเท่าใด

วิธีรับการประมาณที่ดีที่สุดได้อธิบายไว้ที่นี่: http://math.mit.edu/~gs/linearalgebra/ila0403.pdf

แค่อ่านจาก "การติดตั้งเป็นเส้นตรง" โปรดทราบว่าใช้ t แทน x และ b แทน y C และ D จะถูกกำหนดเป็นการประมาณจากนั้นคุณมี f (x) = C + Dx

หมายเหตุเพิ่มเติม: แน่นอนคุณต้องคำนึงถึงความยาวของบรรทัดด้วย ทุกบรรทัดประกอบด้วย 2 คะแนนจะสมบูรณ์แบบ ฉันไม่ทราบบริบทที่แน่นอน แต่ฉันเดาว่าฉันจะใช้ผลรวมของกำลังสองหารด้วยจำนวนคะแนนเป็นคะแนน นอกจากนี้ฉันจะเพิ่มข้อกำหนดของความยาวน้อยที่สุดจำนวนจุดน้อย (อาจจะประมาณ 75% ของความยาวสูงสุด)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.