คำถามที่คล้ายกันถูกถามเกี่ยวกับ Mathematica.Stackexchange คำตอบของฉันมีการพัฒนาและได้ค่อนข้างนานในที่สุดดังนั้นฉันจะสรุปขั้นตอนวิธีที่นี่
บทคัดย่อ
แนวคิดพื้นฐานคือ:
- ค้นหาฉลาก
- ค้นหาชายแดนของฉลาก
- ค้นหาการแมปที่แมปพิกัดรูปภาพกับพิกัดรูปทรงกระบอกเพื่อที่จะแมปพิกเซลตามขอบด้านบนของป้ายกำกับไปที่ ([อะไร] / 0), พิกเซลตามขอบด้านขวาของ (1 / [อะไรก็ได้)) และอื่น ๆ
- แปลงภาพโดยใช้การทำแผนที่นี้
อัลกอริทึมใช้งานได้กับภาพที่:
- ฉลากสว่างกว่าพื้นหลัง (จำเป็นสำหรับการตรวจจับฉลาก)
- ป้ายชื่อเป็นรูปสี่เหลี่ยมผืนผ้า (ใช้เพื่อวัดคุณภาพของการแมป)
- jar เป็นแนวตั้ง (เกือบ) (ใช้เพื่อให้ฟังก์ชันการทำแผนที่ง่ายขึ้น)
- jar เป็นรูปทรงกระบอก (ใช้เพื่อให้ฟังก์ชันการทำแผนที่ง่าย ๆ )
อย่างไรก็ตามอัลกอริทึมเป็นแบบแยกส่วน อย่างน้อยในหลักการคุณสามารถเขียนการตรวจจับฉลากของคุณเองที่ไม่ต้องการพื้นหลังสีเข้มหรือคุณสามารถเขียนฟังก์ชั่นการวัดคุณภาพของคุณเองที่สามารถรับมือกับฉลากรูปไข่หรือแปดเหลี่ยม
ผล
ภาพเหล่านี้ได้รับการประมวลผลโดยอัตโนมัติอย่างสมบูรณ์เช่นอัลกอริธึมนำภาพต้นฉบับมาใช้งานได้สองสามวินาทีจากนั้นจะแสดงการแมป (ซ้าย) และภาพที่ไม่บิดเบี้ยว (ขวา):
ภาพถัดไปได้รับการประมวลผลด้วยอัลกอริทึมรุ่นที่แก้ไขแล้วคือผู้ใช้เลือกเส้นขอบซ้ายและขวาของ jar (ไม่ใช่ฉลาก) เนื่องจากความโค้งของฉลากไม่สามารถประเมินได้จากภาพในช็อตหน้า (เช่น อัลกอริทึมอัตโนมัติจะคืนค่ารูปภาพที่บิดเบี้ยวเล็กน้อย):
การดำเนินงาน:
1. ค้นหาฉลาก
ฉลากสว่างด้านหน้าของพื้นหลังสีดำดังนั้นฉันสามารถค้นหาได้อย่างง่ายดายโดยใช้ binarization:
src = Import["http://i.stack.imgur.com/rfNu7.png"];
binary = FillingTransform[DeleteBorderComponents[Binarize[src]]]
ฉันเลือกส่วนประกอบที่เชื่อมต่อที่ใหญ่ที่สุดและสมมติว่าเป็นฉลาก:
labelMask = Image[SortBy[ComponentMeasurements[binary, {"Area", "Mask"}][[All, 2]], First][[-1, 2]]]
2. ค้นหาขอบของฉลาก
ขั้นตอนถัดไป: ค้นหาขอบด้านบน / ล่าง / ซ้าย / ขวาโดยใช้มาสก์คอนเฟอเรนซ์แบบง่าย:
topBorder = DeleteSmallComponents[ImageConvolve[labelMask, {{1}, {-1}}]];
bottomBorder = DeleteSmallComponents[ImageConvolve[labelMask, {{-1}, {1}}]];
leftBorder = DeleteSmallComponents[ImageConvolve[labelMask, {{1, -1}}]];
rightBorder = DeleteSmallComponents[ImageConvolve[labelMask, {{-1, 1}}]];
นี่คือฟังก์ชั่นตัวช่วยเล็ก ๆ ที่ค้นหาพิกเซลสีขาวทั้งหมดในหนึ่งในสี่ภาพเหล่านี้และแปลงดัชนีเป็นพิกัด ( Position
ดัชนีกลับคืนและดัชนีเป็น 1 {{y, x} -tuples ที่อิง 1 โดยที่ y = 1 อยู่ด้านบนของ รูปภาพ แต่ฟังก์ชั่นการประมวลผลภาพทั้งหมดคาดหวังว่าพิกัดซึ่งเป็น 0-x {y, y} -tuples โดยที่ y = 0 คือด้านล่างของภาพ):
{w, h} = ImageDimensions[topBorder];
maskToPoints = Function[mask, {#[[2]]-1, h - #[[1]]+1} & /@ Position[ImageData[mask], 1.]];
3. ค้นหาการแมปจากภาพไปยังพิกัดกระบอกสูบ
ตอนนี้ฉันมีสี่รายการแยกกันของพิกัดด้านบน, ล่าง, ซ้าย, ขอบด้านขวาของฉลาก ฉันกำหนดการแมปจากพิกัดรูปภาพไปยังพิกัดทรงกระบอก:
arcSinSeries = Normal[Series[ArcSin[\[Alpha]], {\[Alpha], 0, 10}]]
Clear[mapping];
mapping[{x_, y_}] :=
{
c1 + c2*(arcSinSeries /. \[Alpha] -> (x - cx)/r) + c3*y + c4*x*y,
top + y*height + tilt1*Sqrt[Clip[r^2 - (x - cx)^2, {0.01, \[Infinity]}]] + tilt2*y*Sqrt[Clip[r^2 - (x - cx)^2, {0.01, \[Infinity]}]]
}
นี่คือการทำแผนที่ทรงกระบอกที่แมป X / Y- พิกัดในภาพต้นฉบับไปยังพิกัดทรงกระบอก การทำแผนที่มีอิสระ 10 องศาสำหรับความสูง / รัศมี / ศูนย์กลาง / เปอร์สเปคทีฟ / เอียง ฉันใช้เทย์เลอร์ซีรีส์เพื่อประมาณค่าอาร์คไซน์เพราะฉันไม่สามารถปรับให้เหมาะสมกับอาร์คซินโดยตรง Clip
การโทรคือความพยายามในการป้องกันหมายเลขที่ซับซ้อนระหว่างการปรับให้เหมาะสม มีการแลกเปลี่ยนที่นี่: ในมือข้างหนึ่งฟังก์ชั่นควรจะใกล้เคียงกับการทำแผนที่ทรงกระบอกที่แน่นอนที่สุดเท่าที่จะทำได้เพื่อให้การบิดเบือนที่ต่ำที่สุด ในทางตรงกันข้ามถ้ามันมีความซับซ้อนมันก็ยากที่จะหาค่าที่เหมาะสมที่สุดสำหรับองศาอิสระโดยอัตโนมัติ (สิ่งที่ดีเกี่ยวกับการประมวลผลภาพด้วย Mathematica คือคุณสามารถเล่นกับแบบจำลองทางคณิตศาสตร์เช่นนี้ได้อย่างง่ายดายแนะนำคำเพิ่มเติมสำหรับการบิดเบือนที่แตกต่างกันและใช้ฟังก์ชั่นการเพิ่มประสิทธิภาพเดียวกันเพื่อให้ได้ผลลัพธ์สุดท้ายฉันไม่เคยทำอะไรเลย เช่นนั้นโดยใช้ OpenCV หรือ Matlab แต่ฉันไม่เคยลองใช้กล่องเครื่องมือสัญลักษณ์สำหรับ Matlab อาจจะทำให้มีประโยชน์มากกว่านี้)
ต่อไปฉันจะกำหนด "ฟังก์ชันข้อผิดพลาด" ที่วัดคุณภาพของภาพ -> การทำแผนที่พิกัดกระบอกสูบ เป็นเพียงผลรวมของข้อผิดพลาดกำลังสองสำหรับพิกเซลขอบ:
errorFunction =
Flatten[{
(mapping[#][[1]])^2 & /@ maskToPoints[leftBorder],
(mapping[#][[1]] - 1)^2 & /@ maskToPoints[rightBorder],
(mapping[#][[2]] - 1)^2 & /@ maskToPoints[topBorder],
(mapping[#][[2]])^2 & /@ maskToPoints[bottomBorder]
}];
ฟังก์ชั่นข้อผิดพลาดนี้วัด "คุณภาพ" ของการจับคู่: ต่ำสุดถ้าจุดบนเส้นขอบด้านซ้ายถูกแมปไปที่ (0 / [อะไรก็ได้), พิกเซลที่ขอบด้านบนจะถูกแมปไปที่ ([อะไร] / 0) เป็นต้น .
ตอนนี้ฉันสามารถบอก Mathematica เพื่อหาค่าสัมประสิทธิ์ที่ลดฟังก์ชั่นข้อผิดพลาดนี้ให้น้อยที่สุด ฉันสามารถสร้าง "การศึกษาที่คาดเดา" เกี่ยวกับสัมประสิทธิ์บางอย่าง (เช่นรัศมีและกึ่งกลางขวดในภาพ) ฉันใช้สิ่งเหล่านี้เป็นจุดเริ่มต้นของการเพิ่มประสิทธิภาพ:
leftMean = Mean[maskToPoints[leftBorder]][[1]];
rightMean = Mean[maskToPoints[rightBorder]][[1]];
topMean = Mean[maskToPoints[topBorder]][[2]];
bottomMean = Mean[maskToPoints[bottomBorder]][[2]];
solution =
FindMinimum[
Total[errorFunction],
{{c1, 0}, {c2, rightMean - leftMean}, {c3, 0}, {c4, 0},
{cx, (leftMean + rightMean)/2},
{top, topMean},
{r, rightMean - leftMean},
{height, bottomMean - topMean},
{tilt1, 0}, {tilt2, 0}}][[2]]
FindMinimum
ค้นหาค่าสำหรับอิสระ 10 องศาของฟังก์ชั่นการทำแผนที่ของฉันที่ลดฟังก์ชั่นข้อผิดพลาดให้น้อยที่สุด รวมการจับคู่ทั่วไปกับโซลูชันนี้เข้าด้วยกันและฉันได้รับการแมปจากพิกัดรูปภาพ X / Y ซึ่งเหมาะกับพื้นที่ป้ายกำกับ ฉันสามารถเห็นภาพการทำแผนที่นี้โดยใช้ContourPlot
ฟังก์ชันของ Mathematica :
Show[src,
ContourPlot[mapping[{x, y}][[1]] /. solution, {x, 0, w}, {y, 0, h},
ContourShading -> None, ContourStyle -> Red,
Contours -> Range[0, 1, 0.1],
RegionFunction -> Function[{x, y}, 0 <= (mapping[{x, y}][[2]] /. solution) <= 1]],
ContourPlot[mapping[{x, y}][[2]] /. solution, {x, 0, w}, {y, 0, h},
ContourShading -> None, ContourStyle -> Red,
Contours -> Range[0, 1, 0.2],
RegionFunction -> Function[{x, y}, 0 <= (mapping[{x, y}][[1]] /. solution) <= 1]]]
4. แปลงภาพ
ในที่สุดฉันใช้ImageForwardTransform
ฟังก์ชันMathematica เพื่อบิดเบือนภาพตามแผนที่นี้:
ImageForwardTransformation[src, mapping[#] /. solution &, {400, 300}, DataRange -> Full, PlotRange -> {{0, 1}, {0, 1}}]
ที่ให้ผลลัพธ์ตามที่แสดงด้านบน
รุ่นที่ช่วยเหลือด้วยตนเอง
อัลกอริทึมด้านบนเป็นแบบอัตโนมัติ ไม่จำเป็นต้องทำการปรับเปลี่ยน มันทำงานได้ดีพอสมควรตราบใดที่ภาพถูกถ่ายจากด้านบนหรือด้านล่าง แต่ถ้าเป็นภาพด้านหน้ารัศมีของขวดจะไม่สามารถประมาณได้จากรูปร่างของฉลาก ในกรณีเหล่านี้ฉันได้รับผลลัพธ์ที่ดีกว่ามากถ้าฉันให้ผู้ใช้ป้อนขอบซ้าย / ขวาของ jar ด้วยตนเองและตั้งค่าองศาอิสระที่สอดคล้องกันในแผนที่อย่างชัดเจน
รหัสนี้ช่วยให้ผู้ใช้เลือกขอบซ้าย / ขวา:
LocatorPane[Dynamic[{{xLeft, y1}, {xRight, y2}}],
Dynamic[Show[src,
Graphics[{Red, Line[{{xLeft, 0}, {xLeft, h}}],
Line[{{xRight, 0}, {xRight, h}}]}]]]]
นี่คือรหัสการเพิ่มประสิทธิภาพทางเลือกซึ่งจะให้ค่ากึ่งกลางและรัศมีอย่างชัดเจน
manualAdjustments = {cx -> (xLeft + xRight)/2, r -> (xRight - xLeft)/2};
solution =
FindMinimum[
Total[minimize /. manualAdjustments],
{{c1, 0}, {c2, rightMean - leftMean}, {c3, 0}, {c4, 0},
{top, topMean},
{height, bottomMean - topMean},
{tilt1, 0}, {tilt2, 0}}][[2]]
solution = Join[solution, manualAdjustments]