ย่อชุดจุดของรูปหลายเหลี่ยมให้เป็นชุดจุดที่สั้นลง

ฉันมีรูปหลายเหลี่ยมต่อไปนี้ซึ่งเป็นเพียงชุดของจุด 2D ดังนี้:-

poly0=[80    60
    90    60
   100    60
   110    60
   110    50
   120    50
   130    50
   140    50
   150    50
   160    50
   170    50
   180    50
   190    50
   200    50
   210    50
   210    60
   210    70
   210    80
   210    90
   220    90
   220   100
   210   100
   210   110
   200   110
   200   120
   190   120
   180   120
   180   130
   170   130
   160   130
   150   130
   140   130
   130   130
   130   120
   120   120
   110   120
   110   110
   100   110
   100   100
    90   100
    90    90
    90    80
    90    70
    80    70
    80    60];

ตอนนี้ผมสามารถพลอตมันโดยใช้

>> line(poly0(:,1), poly0(:,2),'Color','k','LineWidth',3,'LineStyle',':');

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

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

ป.ล.: มุมยอดตรงนี้อยู่ที่ 90 องศา แต่หากคุณให้คำตอบ อย่าพยายามหาประโยชน์จากข้อเท็จจริงนั้น ฉันต้องการคำตอบทั่วไปกว่านี้


person user_1_1_1    schedule 09.04.2019    source แหล่งที่มา
comment
เป็นเพียงวงเดียว: สำหรับแต่ละ 3 จุดติดต่อกัน หากอยู่บนเส้นตรง ให้เอาจุดตรงกลางออก หากคุณลบจุดออก ให้ตรวจสอบอีกครั้งด้วยจุดแรกเดิม โดยเพิ่มจุดที่ 3 ใหม่ หลังจากการวนซ้ำ คุณยังต้องตรวจสอบด้วยคะแนน end-1, end และ 1 และด้วย end, 1 และ 2   -  person Cris Luengo    schedule 10.04.2019
comment
ความเป็นไปได้ที่ซ้ำกันของ stackoverflow.com/questions/ 53839733/   -  person Durkee    schedule 10.04.2019
comment
@Durkee ฮาใช้งานได้! แต่ฉันคิดว่ามันใช้ประโยชน์จากความจริงที่ว่ามุมหลายเหลี่ยมเป็น 90 องศา โปรดดู PS ด้านบน   -  person user_1_1_1    schedule 10.04.2019
comment
@CrisLuengo ฉันเดาว่าคำตอบของคุณคือคำตอบทั่วไปที่สุดที่ได้ผล ฉันจะยอมรับถ้ามันถูกสร้างเป็นคำตอบด้วยรหัสบางส่วน   -  person user_1_1_1    schedule 10.04.2019
comment
คะแนนที่มอบให้ถูกบังคับให้เรียงลำดับติดต่อกันหรือไม่?   -  person ShadowMan    schedule 10.04.2019
comment
@ShadowMan ใช่พวกเขาติดต่อกัน   -  person user_1_1_1    schedule 10.04.2019


คำตอบ (3)


คำตอบสองข้อที่มีอยู่มีข้อบกพร่องอย่างมาก:

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

  • วิธีของ ShadowMan ดีกว่าตรงที่ระยะทางไม่จำเป็นต้องเท่ากัน และเส้นที่พาดจุดเริ่มต้น/สิ้นสุดของรูปหลายเหลี่ยมคือ ได้รับการจัดการอย่างถูกต้อง อย่างไรก็ตาม มันยังใช้การเปรียบเทียบความเท่าเทียมกันของจุดลอยตัวด้วย ซึ่งจะใช้งานไม่ได้โดยทั่วไป เฉพาะพิกัดจำนวนเต็มเท่านั้นที่จะทำงานได้อย่างถูกต้อง นอกจากนี้ ยังใช้ vecnorm (ซึ่งใช้สแควร์รูท) และการหาร ทั้งคู่มีการดำเนินการที่ค่อนข้างแพง (เมื่อเปรียบเทียบกับวิธีการที่แสดงไว้ที่นี่)

หากต้องการดูว่าจุดสามจุดเป็นเส้นตรงหรือไม่ คุณสามารถใช้กฎทางคณิตศาสตร์อย่างง่ายได้ สมมติว่าเรามีคะแนน p0, p1 และ p2 เวกเตอร์ตั้งแต่ p0 ถึง p1 และเวกเตอร์ตั้งแต่ p0 ถึง p2 เป็นพื้นฐานของรูปสี่เหลี่ยมด้านขนาน ซึ่งสามารถคำนวณพื้นที่ได้โดย ผลคูณไขว้ของเวกเตอร์สองตัว (ใน 2D ผลคูณไขว้เป็นที่เข้าใจกันว่าใช้ z=0 โดยเวกเตอร์ผลลัพธ์ที่มี x=0 และ y=0 มีเพียงค่า z เท่านั้นที่มีประโยชน์ ดังนั้น 2D ผลิตภัณฑ์ข้ามเราถือว่าจะสร้างค่าสเกลาร์) สามารถคำนวณได้ดังนี้:

v1 = p1 - p0;
v2 = p2 - p0;
x = v1(1)*v2(2) - v1(2)*v2(1);

x ซึ่งเป็นผลคูณไขว้จะเป็นศูนย์หากเวกเตอร์สองตัวขนานกัน ซึ่งหมายความว่าจุดสามจุดนั้นอยู่ในแนวเดียวกัน แต่การทดสอบความเท่าเทียมกับ 0 ต้องมีพิกัดความเผื่ออยู่บ้าง เนื่องจากเลขคณิตจุดลอยตัวไม่แน่นอน ฉันใช้ 1e-6 เป็นค่าเผื่อที่นี่ ใช้ค่าที่มีขนาดน้อยกว่าระยะห่างระหว่างจุดหลายจุด

เมื่อกำหนดชุดอินพุตของคะแนน p เราสามารถหาจุดมุมได้ด้วย:

p1 = p;                                  % point 1
p0 = circshift(p1,1);                    % point 0
v1 = p1 - p0;                            % vector from point 0 to 1
v2 = circshift(p1,-1) - p0;              % vector from point 0 to 2
x = v1(:,1).*v2(:,2) - v1(:,2).*v2(:,1); % cross product
idx = abs(x) > 1e-6;                     % comparison with tolerance
p = p(idx,:);                            % corner points

โปรดทราบว่าการทดสอบผลิตภัณฑ์ข้ามนี้จะล้มเหลวหากจุดสองจุดติดต่อกันมีพิกัดที่เหมือนกัน (เช่น เวกเตอร์ตัวใดตัวหนึ่งมีความยาวเป็นศูนย์) จำเป็นต้องมีการทดสอบเพิ่มเติมหากข้อมูลอาจมีจุดซ้ำกัน

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

การเปรียบเทียบทั้งสามวิธี

นี่คือรหัสที่ฉันใช้สร้างกราฟ:

% Make a polygon that will be difficult for the other two methods
p = [0,0 ; 0.5,0 ; 1,0 ; 1,1 ; 0.5,1 ; 0,1];
p = p + rand(size(p))/3;
p(end+1,:) = p(1,:);
q = [];
for ii = 1:size(p,1)-1
   t = p(ii,:) + (p(ii+1,:) - p(ii,:)) .* [0;0.1;1/3;0.45;0.5897545;pi/4;exp(1)/3];
   q = [q;t];
end
q = circshift(q,3,1);

figure
subplot(2,2,1)
plot(q(:,1),q(:,2),'bo-')
axis equal
title('input')

subplot(2,2,2)
res1 = method1(q);
plot(res1(:,1),res1(:,2),'ro-')
axis equal
title('Durkee''s method')

subplot(2,2,3)
res2 = method2(q);
plot(res2(:,1),res2(:,2),'ro-')
axis equal
title('ShadowMan''s method')

subplot(2,2,4)
res3 = method3(q);
plot(res3(:,1),res3(:,2),'go-')
axis equal
title('correct method')

% Durkee's method: https://stackoverflow.com/a/55603145/7328782
function P = method1(P)
a = logical([1 diff(P(:,1),2)' 1]);
b = logical([1 diff(P(:,2),2)' 1]);
idx = or(a,b);
P = P(idx,:);
end

% ShadowMan's method: https://stackoverflow.com/a/55603040/7328782
function corners = method2(poly0)
poly0Z = circshift(poly0,1);
poly0I = circshift(poly0,-1);
unitVectIn =(poly0 - poly0I)./vecnorm((poly0 - poly0I),2,2);
unitVectOut =(poly0Z - poly0)./vecnorm((poly0Z - poly0),2,2);
cornerIndices = sum(unitVectIn == unitVectOut,2)==0;
corners = poly0(cornerIndices,:);
end
% vecnorm is new to R2017b, I'm still running R2017a.
function p = vecnorm(p,n,d)
% n is always 2
p = sqrt(sum(p.^2,d));
end

function p = method3(p1)
p0 = circshift(p1,1);
v1 = p1 - p0;
v2 = circshift(p1,-1) - p0;
x = v1(:,1).*v2(:,2) - v1(:,2).*v2(:,1);
idx = abs(x) > 1e-6;
p = p1(idx,:);
end
person Cris Luengo    schedule 10.04.2019
comment
คริสขอบคุณสำหรับข้อเสนอแนะ ฉันพยายามใช้ความอดทนในสคริปต์ของฉันโดยใช้จุดกระวนกระวายใจของคุณ (abs(unitVectIn-unitVectOut)<1e-6 มันไม่ค่อยได้ผลนักเพราะมันทำให้ฉันมีเวกเตอร์ sum((abs(unitVectIn-unitVectOut)<1e-6) == 2 จะเหมาะสมหรือไม่ เพราะตอนนี้แทนที่จะเปรียบเทียบการลอยตัว ฉันแค่เปรียบเทียบจำนวนเต็มที่เป็นผลลัพธ์จากบูลีนตัวแรก ฉันต้องการเรียนรู้เพิ่มเติมเกี่ยวกับหลักการเบื้องหลังคำวิพากษ์วิจารณ์ของคุณ หากคุณทราบแหล่งข้อมูลที่ดี - person ShadowMan; 10.04.2019
comment
@ShadowMan: ใช่นั่นคือสิ่งที่ฉันหมายถึง มันเกี่ยวกับการแทนที่ a==b ด้วย abs(a-b)<tol นี่คืองานที่ทุกคนมักอ้างอิงถึงเสมอเมื่อพูดถึงปัญหาเกี่ยวกับการเป็นตัวแทนจุดลอยตัว. - person Cris Luengo; 10.04.2019
comment
มีเหตุผลใดที่คุณเลือกคำนวณผลคูณไขว้ด้วยตนเองแทนที่จะใช้ cross() - person ShadowMan; 11.04.2019
comment
@ShadowMan: cross ต้องใช้เวกเตอร์ 3 มิติ ฉันจะต้องเพิ่มคอลัมน์ศูนย์ (สำหรับมิติที่ 3) จากนั้นจะคำนวณค่า x และ y ที่ฉันรู้อยู่แล้วว่าจะเป็นศูนย์ มันจึงจะมีประสิทธิภาพน้อยลง ฉันไม่รู้ว่า MATLAB มีฟังก์ชันในการคำนวณผลคูณไขว้ 2D หรือไม่ นอกจากนี้ ฉันแก้ไขโค้ดจากโค้ด C++ ที่ฉันมีอยู่แล้ว ฉันใช้ความพยายามที่จะแทนที่ลูปโดยใช้ circshift ที่ฉันเห็นในคำตอบของคุณ แต่ไม่ได้ดูว่ามีอะไรที่จะทำให้ง่ายขึ้นอีกหรือไม่ ดังนั้นการทิ้ง cross product ด้วยตนเองนี้จึงเป็นทางออกที่ง่าย :) - person Cris Luengo; 11.04.2019
comment
ขอบคุณสำหรับเคล็ดลับที่นี่ การแก้ไขการเปรียบเทียบโฟลตในสคริปต์ของฉันนำไปสู่ผลลัพธ์ที่ดีแม้ในข้อมูลที่ 'กระวนกระวายใจ' ฉันจะคอยติดตามปัญหานี้ด้วยรหัสของฉันต่อจากนี้ไป :D - person ShadowMan; 11.04.2019
comment
@ShadowMan: ถ้าคุณทำอย่างนั้น แสดงว่าคุณนำหน้าโค้งอยู่แล้ว! :) -- BTW: นี่คือเป้าหมายหลอกมาตรฐานของเราสำหรับข้อผิดพลาดในการเปรียบเทียบจุดลอยตัว ดูคำถามที่เชื่อมโยง คุณจะเห็นว่าข้อผิดพลาดนี้เกิดขึ้นบ่อยแค่ไหน นี่เป็นเพียงเป้าหมายในแท็ก MATLAB ฉันแน่ใจว่ามีเป้าหมายที่ซ้ำกันที่คล้ายกันสำหรับภาษาอื่น - person Cris Luengo; 11.04.2019

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

พิจารณารูปสามเหลี่ยมที่ระบุโดยจุดต่างๆ

P = [0 0; 1 0; 2 0; 1.5 1; 1 2; .5 1; 0 0];

นี่คืออาร์เรย์ 7x2 ถ้าเรากำหนดเวกเตอร์อนุพันธ์ 2 ตัวตามที่กำหนดโดยคำถามที่ฉันพูดถึงในความคิดเห็น

a = logical([1 diff(P(:,1),2)' 1]);
b = logical([1 diff(P(:,2),2)' 1]);

จากนั้น เราอาจรวมทั้งสองเข้าด้วยกันเพื่อรับตัวแปรการจัดทำดัชนีใหม่

idx = or(a,b);

ในที่สุดเราอาจใช้สิ่งนี้เพื่อสร้างพล็อตของเรา

line(P(idx,1), P(idx,2),'Color','k','LineWidth',3,'LineStyle',':');

หากคุณกำลังวาดเส้น ฉันคิดว่าคุณต้องตั้งค่าตัวแปรสุดท้ายให้เป็นเท็จ

idx(end) = false;
person Durkee    schedule 10.04.2019
comment
วิธีนี้จะแก้ปัญหาได้เร็วกว่าการคำนวณของฉันมาก รวมถึงประหยัดเวลาในการเขียนโค้ด :D - person ShadowMan; 10.04.2019
comment
หากจุดแรกและจุดสุดท้ายเป็นเส้นตรง คุณจะลบคะแนนไม่เพียงพอ ฉันคิดว่าคำตอบอื่นทำอย่างนั้นกับวงเวียน - person Cris Luengo; 10.04.2019
comment
จริงอยู่ที่สถานการณ์อาจไม่สำคัญ หรือสามารถแก้ไขได้ด้วยคำสั่ง if - person Durkee; 10.04.2019
comment
ฉันเพิ่งรู้ว่าวิธีการนี้มีข้อบกพร่องอีกอย่างหนึ่ง คุณถือว่าจุดยอดมีระยะห่างเท่ากัน หากจุดบนเส้นตรงมีระยะห่างไม่เท่ากัน จุดเหล่านั้นก็จะยังคงอยู่ในเอาท์พุต - person Cris Luengo; 10.04.2019

วิธี 'เวกเตอร์' สามารถทำได้ค่อนข้างสวยงาม ฉันลองใช้วิธี for loop แล้วคุณก็ทำแบบเดียวกันได้ แต่คุณขอเวกเตอร์ ดังนั้นนี่คือวิธีของฉันในการทำสิ่งนั้น

การเปลี่ยนแปลงเดียวที่ฉันทำกับข้อมูลของคุณคือการลบการจำลองใด ๆ ก่อนที่จะเริ่มสคริปต์นี้ นอกจากนี้จุดที่ให้ไว้ควรเรียงตามลำดับตามเข็มนาฬิกาหรือทวนเข็มนาฬิกา

    poly0Z = circshift(poly0,1);
    poly0I = circshift(poly0,-1); 
    unitVectIn =(poly0 - poly0I)./vecnorm((poly0 - poly0I),2,2);
    unitVectOut =(poly0Z - poly0)./vecnorm((poly0Z - poly0),2,2)  ;
    cornerIndices = sum(unitVectIn == unitVectOut,2)==0
    corners = poly0(cornerIndices,:)

    line(poly0(:,1), poly0(:,2),'Color','k','LineWidth',2,'LineStyle',':');
    hold on
    scatter(corners(:,1), corners(:,2),'filled')

พื้นฐานของวิธีนี้คือไปที่แต่ละจุด คำนวณเวกเตอร์หน่วยที่เข้ามา และเวกเตอร์หน่วยที่ออกไป จุดที่เวกเตอร์หน่วยเข้าไม่ตรงกับเวกเตอร์หน่วยคือมุม

person ShadowMan    schedule 10.04.2019
comment
วิธีนี้ได้ผลดี ยกเว้นการเปรียบเทียบความเท่าเทียมกัน คุณควรเผื่อความคลาดเคลื่อนเมื่อเปรียบเทียบเวกเตอร์ที่ทำให้เป็นมาตรฐาน (abs(unitVectIn-unitVectOut)<1e-6 หรือคล้ายกัน) - person Cris Luengo; 10.04.2019