비슷한 질문이 Mathematica.Stackexchange에 질문을 받았다 . 저의 대답은 진화했고 결국 꽤 길었습니다. 그래서 여기에 알고리즘을 요약하겠습니다.
요약
기본 아이디어는 다음과 같습니다.
- 라벨을 찾으십시오.
- 라벨의 테두리를 찾습니다
- 이미지 좌표를 원통 좌표에 매핑하여 레이블의 위쪽 테두리를 따라 픽셀을 ([anything] / 0)으로, 오른쪽 테두리를 따라 픽셀을 (1 / [anything])으로 매핑하는 매핑을 찾습니다.
- 이 매핑을 사용하여 이미지 변환
이 알고리즘은 다음 이미지에서만 작동합니다.
- 라벨이 배경보다 밝습니다 (라벨 감지에 필요함)
- 레이블이 직사각형입니다 (매핑 품질을 측정하는 데 사용됨)
- 항아리는 (거의) 수직입니다 (매핑 기능을 단순하게 유지하는 데 사용됩니다)
- 항아리는 원통형입니다 (매핑 기능을 단순하게 유지하는 데 사용됩니다)
그러나 알고리즘은 모듈 식입니다. 최소한 원칙적으로 어두운 배경이 필요없는 자체 레이블 감지를 작성하거나 타원형 또는 팔각형 레이블에 대처할 수있는 자체 품질 측정 기능을 작성할 수 있습니다.
결과
이러한 이미지는 완전히 자동으로 처리됩니다. 즉, 알고리즘은 소스 이미지를 가져와 몇 초 동안 작동 한 다음 매핑 (왼쪽)과 왜곡되지 않은 이미지 (오른쪽)를 표시합니다.
다음 이미지는 수정 된 버전의 알고리즘으로 처리되었으며, 사용자가 라벨의 곡률을 정면 샷의 이미지에서 추정 할 수 없기 때문에 (라벨이 아닌) 항아리의 왼쪽과 오른쪽 경계를 선택했습니다. 완전 자동 알고리즘은 약간 왜곡 된 이미지를 반환합니다.
이행:
1. 라벨 찾기
어두운 배경 앞에서 레이블이 밝으므로 이진화를 사용하여 쉽게 찾을 수 있습니다.
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}}]];
이것은이 4 개의 이미지 중 하나에서 모든 흰색 픽셀을 찾고 인덱스를 좌표로 변환하는 작은 도우미 함수입니다 (지수를 Position
반환하고 인덱스는 1 기반 {y, x} 튜플입니다. 여기서 y = 1은 맨 위에 있음) 그러나 모든 이미지 처리 함수는 0 기반 {x, y}-튜플 인 좌표를 필요로합니다. 여기서 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 자유도를 갖습니다. ArcSin으로 직접 작업하는 최적화를 얻을 수 없기 때문에 Taylor 시리즈를 사용하여 아크 사인을 근사화했습니다. 그만큼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 / [anything])에 매핑되고 위쪽 테두리의 픽셀이 ([anything] / 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 이미지 좌표에서 매핑을 얻습니다. Mathematica의 ContourPlot
기능을 사용하여이 매핑을 시각화 할 수 있습니다 .
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. 이미지 변환
마지막으로 Mathematica의 ImageForwardTransform
기능을 사용 하여이 매핑에 따라 이미지를 왜곡합니다.
ImageForwardTransformation[src, mapping[#] /. solution &, {400, 300}, DataRange -> Full, PlotRange -> {{0, 1}, {0, 1}}]
위와 같이 결과가 나타납니다.
수동 지원 버전
위의 알고리즘은 완전 자동입니다. 조정할 필요가 없습니다. 위 또는 아래에서 사진을 찍는 한 합리적으로 잘 작동합니다. 그러나 정면 샷 인 경우 항아리의 반경은 레이블의 모양으로 추정 할 수 없습니다. 이 경우 사용자가 항아리의 왼쪽 / 오른쪽 테두리를 수동으로 입력하고 매핑에서 해당 자유도를 명시 적으로 설정하면 훨씬 더 나은 결과를 얻을 수 있습니다.
이 코드를 사용하면 왼쪽 / 오른쪽 테두리를 선택할 수 있습니다.
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]