Làm thế nào để làm phẳng hình ảnh của nhãn trên lọ thực phẩm?


40

Tôi muốn chụp ảnh nhãn trên một lọ thức ăn và có thể biến đổi chúng sao cho nhãn phẳng, với bên phải và bên trái được thay đổi kích thước ngay cả với trung tâm của hình ảnh.

Lý tưởng nhất, tôi muốn sử dụng độ tương phản giữa nhãn và nền để tìm các cạnh và áp dụng hiệu chỉnh. Nếu không, tôi có thể yêu cầu người dùng bằng cách nào đó xác định các góc và cạnh của hình ảnh.


Tôi đang tìm kiếm các kỹ thuật và thuật toán chung để chụp ảnh bị lệch hình cầu (hình trụ trong trường hợp của tôi) và có thể làm phẳng hình ảnh. Hiện tại hình ảnh của một nhãn được bao quanh một lọ hoặc chai, sẽ có các tính năng và văn bản co lại khi nó lùi về bên phải hoặc bên trái của hình ảnh. Ngoài ra, các đường biểu thị cạnh của nhãn, sẽ chỉ song song ở giữa hình ảnh và sẽ nghiêng về phía nhau ở cực bên phải và bên trái của nhãn.

Sau khi thao tác với hình ảnh, tôi muốn để lại một hình chữ nhật gần như hoàn hảo trong đó văn bản và các tính năng có kích thước đồng đều, như thể tôi đã chụp ảnh nhãn khi nó không nằm trên bình hoặc chai.

Ngoài ra, tôi muốn nó nếu kỹ thuật có thể tự động phát hiện các cạnh của nhãn, để áp dụng hiệu chỉnh phù hợp. Nếu không, tôi sẽ phải yêu cầu người dùng của tôi chỉ ra ranh giới nhãn.

Tôi đã Googled và tìm thấy các bài viết như thế này: làm phẳng các tài liệu cong , nhưng tôi đang tìm kiếm một cái gì đó đơn giản hơn một chút, vì nhu cầu của tôi là các nhãn có đường cong đơn giản.


Nikie có những gì dường như là một giải pháp toàn diện. Tuy nhiên, mọi thứ sẽ đơn giản hơn nhiều nếu bạn biết rằng máy ảnh luôn "vuông" với bình, không có hậu cảnh khó hiểu. Sau đó, bạn tìm thấy các cạnh của bình và áp dụng phép biến đổi lượng giác đơn giản (arcsine?), Mà không cần thay đổi nhiều. Khi hình ảnh được làm phẳng, bạn có thể tự cách ly nhãn.
Daniel R Hicks

@Daniel Đó là những gì tôi đã làm ở đây . Lý tưởng nhất là người ta cũng sẽ tính đến phép chiếu không song song hoàn hảo, nhưng tôi đã không làm thế.
Szabolcs

công việc rất tốt nhưng mã hiển thị lỗi trong hệ thống của tôi. tôi đang sử dụng matlab 2017a là nó tương thích với nó. cảm ơn bạn,
Satish Kumar

Câu trả lời:


60

Một câu hỏi tương tự đã được hỏi trên Mathematica.Stackexchange . Câu trả lời của tôi ở đó đã phát triển và cuối cùng khá dài, vì vậy tôi sẽ tóm tắt thuật toán ở đây.

trừu tượng

Ý tưởng cơ bản là:

  1. Tìm nhãn.
  2. Tìm đường viền của nhãn
  3. Tìm một ánh xạ ánh xạ tọa độ hình ảnh sang tọa độ hình trụ để nó ánh xạ các pixel dọc theo đường viền trên cùng của nhãn thành ([bất cứ thứ gì] / 0), các pixel dọc theo đường viền phải sang (1 / [bất cứ thứ gì]), v.v.
  4. Chuyển đổi hình ảnh bằng cách sử dụng ánh xạ này

Thuật toán chỉ hoạt động cho hình ảnh trong đó:

  1. nhãn sáng hơn nền (điều này là cần thiết để phát hiện nhãn)
  2. nhãn là hình chữ nhật (cái này được sử dụng để đo chất lượng của ánh xạ)
  3. jar là (gần như) dọc (điều này được sử dụng để giữ cho chức năng ánh xạ đơn giản)
  4. cái bình có dạng hình trụ (cái này được sử dụng để giữ cho chức năng ánh xạ đơn giản)

Tuy nhiên, thuật toán là mô-đun. Ít nhất về nguyên tắc, bạn có thể viết phát hiện nhãn của riêng bạn mà không yêu cầu nền tối hoặc bạn có thể viết hàm đo lường chất lượng của riêng bạn có thể đối phó với nhãn hình elip hoặc hình bát giác.

Các kết quả

Những hình ảnh này được xử lý hoàn toàn tự động, tức là thuật toán lấy hình ảnh nguồn, hoạt động trong vài giây, sau đó hiển thị ánh xạ (trái) và hình ảnh không bị biến dạng (phải):

nhập mô tả hình ảnh ở đây

nhập mô tả hình ảnh ở đây

nhập mô tả hình ảnh ở đây

nhập mô tả hình ảnh ở đây

nhập mô tả hình ảnh ở đây

nhập mô tả hình ảnh ở đây

nhập mô tả hình ảnh ở đây

Các hình ảnh tiếp theo được xử lý bằng một phiên bản sửa đổi của thuật toán, là người dùng chọn viền trái và phải của bình (không phải nhãn), vì độ cong của nhãn không thể ước tính được từ ảnh trong ảnh chụp phía trước (tức là thuật toán hoàn toàn tự động sẽ trả về hình ảnh hơi bị méo):

nhập mô tả hình ảnh ở đây

nhập mô tả hình ảnh ở đây

Thực hiện:

1. Tìm nhãn

Nhãn sáng trước một nền tối, vì vậy tôi có thể dễ dàng tìm thấy nó bằng cách sử dụng nhị phân:

src = Import["http://i.stack.imgur.com/rfNu7.png"];
binary = FillingTransform[DeleteBorderComponents[Binarize[src]]]

hình ảnh nhị phân

Tôi chỉ cần chọn thành phần được kết nối lớn nhất và cho rằng đó là nhãn:

labelMask = Image[SortBy[ComponentMeasurements[binary, {"Area", "Mask"}][[All, 2]], First][[-1, 2]]]

thành phần lớn nhất

2. Tìm đường viền của nhãn

Bước tiếp theo: tìm đường viền trên / dưới / trái / phải bằng cách sử dụng mặt nạ chập đạo hàm đơn giản:

topBorder = DeleteSmallComponents[ImageConvolve[labelMask, {{1}, {-1}}]];
bottomBorder = DeleteSmallComponents[ImageConvolve[labelMask, {{-1}, {1}}]];
leftBorder = DeleteSmallComponents[ImageConvolve[labelMask, {{1, -1}}]];
rightBorder = DeleteSmallComponents[ImageConvolve[labelMask, {{-1, 1}}]];

nhập mô tả hình ảnh ở đây

Đây là một hàm trợ giúp nhỏ tìm thấy tất cả các pixel trắng trong một trong bốn hình ảnh này và chuyển đổi các chỉ số thành tọa độ ( Positiontrả về các chỉ mục và các chỉ mục là {y, x} -tuples, trong đó y = 1 nằm ở đầu hình ảnh. Nhưng tất cả các hàm xử lý hình ảnh đều mong đợi tọa độ, dựa trên 0 x, y} -tuples, trong đó y = 0 là đáy của hình ảnh):

{w, h} = ImageDimensions[topBorder];
maskToPoints = Function[mask, {#[[2]]-1, h - #[[1]]+1} & /@ Position[ImageData[mask], 1.]];

3. Tìm ánh xạ từ hình ảnh đến tọa độ hình trụ

Bây giờ tôi có bốn danh sách tọa độ riêng biệt của các đường viền trên, dưới, trái, phải của nhãn. Tôi xác định ánh xạ từ tọa độ hình ảnh đến tọa độ hình trụ:

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]}]]
   }

Đây là ánh xạ hình trụ, ánh xạ tọa độ X / Y trong ảnh nguồn thành tọa độ hình trụ. Bản đồ có 10 độ tự do cho chiều cao / bán kính / trung tâm / phối cảnh / độ nghiêng. Tôi đã sử dụng loạt Taylor để ước tính sin hình cung, vì tôi không thể trực tiếp tối ưu hóa với ArcSin. CácClipcác cuộc gọi là nỗ lực đặc biệt của tôi để ngăn chặn các số phức trong quá trình tối ưu hóa. Có một sự đánh đổi ở đây: Một mặt, chức năng phải càng gần với ánh xạ hình trụ chính xác càng tốt, để tạo ra độ méo thấp nhất có thể. Mặt khác, nếu nó phức tạp, việc tìm các giá trị tối ưu cho mức độ tự do sẽ khó hơn nhiều. (Điều thú vị khi thực hiện xử lý hình ảnh với Mathematica là bạn có thể chơi xung quanh với các mô hình toán học như thế này rất dễ dàng, giới thiệu các thuật ngữ bổ sung cho các biến dạng khác nhau và sử dụng các hàm tối ưu hóa giống nhau để có kết quả cuối cùng. Tôi chưa bao giờ có thể làm gì cả. như thế khi sử dụng OpenCV hoặc Matlab. Nhưng tôi chưa bao giờ thử hộp công cụ tượng trưng cho Matlab, có lẽ điều đó làm cho nó hữu ích hơn.)

Tiếp theo tôi xác định "hàm lỗi" đo chất lượng của hình ảnh -> ánh xạ tọa độ hình trụ. Đây chỉ là tổng số lỗi bình phương cho các pixel viền:

errorFunction =
  Flatten[{
    (mapping[#][[1]])^2 & /@ maskToPoints[leftBorder],
    (mapping[#][[1]] - 1)^2 & /@ maskToPoints[rightBorder],
    (mapping[#][[2]] - 1)^2 & /@ maskToPoints[topBorder],
    (mapping[#][[2]])^2 & /@ maskToPoints[bottomBorder]
    }];

Hàm lỗi này đo lường "chất lượng" của ánh xạ: Thấp nhất nếu các điểm ở viền bên trái được ánh xạ tới (0 / [bất cứ thứ gì]), các pixel ở viền trên cùng được ánh xạ tới ([bất cứ thứ gì] / 0), v.v. .

Bây giờ tôi có thể nói với Mathicala để tìm các hệ số giảm thiểu hàm lỗi này. Tôi có thể đưa ra "những phỏng đoán có giáo dục" về một số hệ số (ví dụ bán kính và tâm của bình trong ảnh). Tôi sử dụng chúng làm điểm bắt đầu của tối ưu hóa:

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]]

FindMinimumtìm các giá trị cho 10 bậc tự do của chức năng ánh xạ của tôi để giảm thiểu chức năng lỗi. Kết hợp ánh xạ chung và giải pháp này và tôi nhận được ánh xạ từ tọa độ hình ảnh X / Y, phù hợp với vùng nhãn. Tôi có thể hình dung ánh xạ này bằng ContourPlothàm Mathicala:

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]]]

nhập mô tả hình ảnh ở đây

4. Chuyển đổi hình ảnh

Cuối cùng, tôi sử dụng ImageForwardTransformchức năng của Mathicala để làm biến dạng hình ảnh theo ánh xạ này:

ImageForwardTransformation[src, mapping[#] /. solution &, {400, 300}, DataRange -> Full, PlotRange -> {{0, 1}, {0, 1}}]

Điều đó cho kết quả như hình trên.

Phiên bản hỗ trợ thủ công

Thuật toán trên là hoàn toàn tự động. Không cần điều chỉnh. Nó hoạt động hợp lý tốt miễn là hình ảnh được chụp từ trên hoặc dưới. Nhưng nếu đó là ảnh chụp phía trước, bán kính của bình không thể ước tính được từ hình dạng của nhãn. Trong những trường hợp này, tôi nhận được kết quả tốt hơn nhiều nếu tôi cho phép người dùng nhập các đường viền trái / phải của bình theo cách thủ công và đặt mức độ tự do tương ứng trong ánh xạ một cách rõ ràng.

Mã này cho phép người dùng chọn viền trái / phải:

LocatorPane[Dynamic[{{xLeft, y1}, {xRight, y2}}], 
 Dynamic[Show[src, 
   Graphics[{Red, Line[{{xLeft, 0}, {xLeft, h}}], 
     Line[{{xRight, 0}, {xRight, h}}]}]]]]

Trình định vị

Đây là mã tối ưu hóa thay thế, trong đó trung tâm & bán kính được cung cấp rõ ràng.

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]

11
Tháo kính râm ... mẹ của thần ...
Spacey

Bạn có tình cờ có một tham chiếu đến ánh xạ hình trụ không? Và có lẽ phương trình cho ánh xạ nghịch đảo? @ niki-estner
Ita
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.