Tải bản đầy đủ (.doc) (35 trang)

CHƯƠNG 6 Image Transforms doc

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (1.15 MB, 35 trang )

CHƯƠNG 6
Image Transforms
Tổng quan
Trong chương ta trước đề cập nhiều thứ khác nhau bạn có thể làm với một image. Chính yếu của các
operator được trình bày được dùng enhance, modify, hay ngược lại “process” một image thành một ảnh
tương tự nhưng mới.
Trong chương này ta sẽ quan sát image transforms, mà các methods để thay đổi một image thành một biểu
diễn thay thế của data. Chẳng hạn hầu hết example phổ biến của một transform là gì đó giống Fourier
transform, mà trong đó image được chuyển thành biểu diễn thay thế của data trong image gốc. Kết quả của
operation này được lưu trong một OpenCV “image” structure, nhưng các “pixel” riêng trong image mới
này biểu diễn các thành phần spectral của input gốc hơn là các thành phần không gian ta được dùng để nghĩ
về.
Có một số trong các transform hữu ích mà đến lặp lại trong computer vision. OpenCV cung cấp các thực
hiện hoàn chỉnh của vài trong những cái phổ biến cũng như dựng các block để giúp bạn thực hiện các
image transform riêng.
Convolution
Convolution là cơ sở của nhiều trong các transformation mà ta thảo luận trong chương này. Về khái quát,
thuật ngữ này có nghĩa ta làm mọi phần của một image. Trong ý thức này, nhiều trong các operation ta xem
xét trong chương 5 có thể cũng được hiểu như các trường hợp đặc biệt của tiến trình tổng quát hơn của
convolution. Điều một convolution cụ thể “làm” được xác định bởi dạng của Convolution kernel được
dùng. Kernel này thực sự chỉ là một fixed size array của các hệ số cùng với một anchor point trong array
đó, mà điển hình được đặt ở tâm. Size của array* được gọi support của kernel. Figure 6-1 mô tả một 3-by-3
convolution kernel với anchor đặt ở center của array. Value của convolution ở một điểm cụ thể được tính
bởi đầu tiên đặt kernel anchor ở đỉnh của một pixel trên image với phần còn lại của kernel nằm chồng các
local pixel tương ứng trong image. Cho mỗi kernel point, ta bây giờ có một value cho kernel ở điểm đó và
một value cho image ở image point tương ứng. Ta nhân những cái này cùng nhau và cộng kết quả; kết quả
này được đặt trong image kết quả ở vị trí tương ứng với vị trí của anchor trong input image. Tiến trình này
được lặp lại cho mỗi point trong image bởi quét kernel qua toàn bộ image.
Figure 6-1. Một 3-by-3 kernel cho một vi phân Sobel; lưu ý rằng anchor point ở tâm của kernel
Ta có thể, dĩ nhiên, mô tả procedure này theo dạng của một phương trình. Nếu ta định nghĩa image là I(x,
y), kernel là G(i, j) (trong đó 0 < i< Mi –1 và 0 < j < Mj –1), và điểm anchor được định vị ở (ai, aj) theo tọa


độ trong kernel, thì convolution H(x, y) được định nghĩa bởi biểu thức sau:
∑ ∑

=

=
−+−+=
1
0
1
0
),(),(),(
i
j
M
i
M
j
ji
jiGajyaixIyxH
Nhận thấy rằng số các operation, tối thiểu ở cái nhìn đầu tiên, dường như là số các pixel trong image được
nhận bởi số các pixel trong kernel.* Điều này có thể là nhiều tính toán và do đó không là thứ bạn muốn làm
với vài “for” loop và nhiều pointer de-referencing. Trong những trường hợp như thế này, tốt hơn để
OpenCV làm việc cho bạn và lấy thuận lợi của các tối ưu hiện được lập trình vào OpenCV. Cách OpenCV
làm điều này là với cvFilter2D():
void cvFilter2D(
const CvArr* src,
CvArr* dst,
const CvMat* kernel,
CvPoint anchor = cvPoint(-1,-1)

);
Ở đây ta tạo một matrix với size thích hợp, điền nó với các hệ số, và sau đó chuyển nó cùng nhau với các
source và ảnh đíchs vào cvFilter2D(). Ta có thể cũng tùy chọn chuyển vào một CvPoint để nhận diện vị trí của
center của kernel, nhưng value mặc định (bằng với cvPoint(-1,-1)) được hiểu như nhận biết ở center của
kernel. Kernel có thể có size chẵn nếu anchor point của nó được định nghĩa; ngược lại, nó nên là size lẻ.
Các src và dst images nên là cùng size. Một có thể nghĩ rằng src image nên lớn hơn dst để cho phép cho
width và length thêm của convolution kernel. Nhưng các size của src và dst có thể là giống trong OpenCV vì,
bởi mặc định, ưu tiên trong convolution OpenCV tạo các virtual pixel qua việc tái tạo qua biên của src
image sao cho các pixel biên trong dst có thể được điền vào. Việc tái tạo được làm như input(–dx, y) =
input(0, y), input(w + dx, y) = input(w – 1, y), và vân vân. Có vài thay thế với hành động mặc định này; ta
sẽ thảo luận chúng trong phần tiếp.
Ta chú ý rằng các hệ số của convolution kernel nên luôn luôn là các số floatingpoint. Điều này có nghĩa
rằng bạn nên dùng CV_32FC1 khi cấp phát matrix đó.
Convolution Boundaries
Một vấn đề mà đến tự nhiện với các convolution là cách handle các biên. Ví dụ, khi dùng convolution
kernel vừa mô tả, điều xảy ra khi point được convolved ở cạnh của image? Hầu hết các function tạo sẵn của
OpenCV thực hiện dùng của cvFilter2D() phải handle điều này theo cách này hay cách khác. Tương tự, khi
làm các convolution riêng, bạn sẽ cần biết cách giải quyết điều này hiệu quả. Giải pháp đến theo dạng của
cvCopyMakeBorder() function, mà chép một image cho trước vào một image lớn hơn một tí và sau đó tự
động đêm boundary theo cách này hay cách khác:
void cvCopyMakeBorder(
const CvArr* src,
CvArr* dst,
CvPoint offset,
int bordertype,
CvScalar value = cvScalarAll(0)
);
Offset argument nói với cvCopyMakeBorder() nới để đặt copy của image gốc bên trong image đích. Điều
hình, nếu kernel là N-by-N (cho N lẻ) thì bạn sẽ muốn một boundary mà là rộng (N – 1)/2 trên tất cả các
cạnh hay, tương đương, một image mà rộng hơn và cao hơn N – 1 so với gốc. Trong trường hợp này bạn

nên đặt offset thành cvPoint((N-1)/2,(N-1)/2) sao cho biên sẽ là chẵn trên tất cả các cạnh.* Kiểu border có thể là
một trong IPL_BORDER_CONSTANT hay IPL_BORDER_REPLICATE (xem Figure 6-2).
Trong trường hợp đầu tiên, value argument sẽ được hiểu như value mà với tất cả pixels trong boundary nên
được đặt. Trong trường hợp thứ hai, row hay column ở cạnh của gốc được tái tạo thành cạnh của image lớn
hơn. Lưu ý rằng border của pattern image kiểm tra là gì đó huyền ảo (kiểm tra image trên phải trong Figure
6-2); trong image mẫu kiểm tra, có một border tối rộng một pixel ngoại trừ nơi các circle pattern đến gần
border ở đó nó chuyển thành trắng. Có hai border types khác nhau được định nghĩa,
IPL_BORDER_REFLECT và PL_BORDER_WRAP, mà không thực hiện ở thời điểm này trong OpenCV
nhưng có thể được hỗ trợ trong tương lai.
Figure 6-2. Mở rộng image border. Cộ trái cho thấy IPL_BORDER_CONSTANT nới một zero value được dùng để
điền cho các border. Cột phải cho thấy IPL_BORDER_REPLICATE nơi border pixels được tái tạo theo các hướng
ngang và dọc
Ta đề cập trước đây rằng, khi bạn làm các lời gọi đến các OpenCV libraery functions mà thực hiện
convolution, các library function này gọi cvCopyMakeBorder() để lấy việc của chúng làm. Trong hầu hết các
trường hợp border type được gọi là IPL_BORDER_REPLICATE, nhưng đôi khi bạn sẽ không muốn nó được
làm theo cách đó. Đâu là một dịp khác nơi bạn có thể muốn dùng cvCopyMakeBorder(). Bạn có thể tạo một
image lớn hơn một tí với border bạn muốn, gọi bất cứ routine trên image đó, và sau đó cắt lại phần bạn
quan tâm ban đầu. Cách này, định biên tự động của OpenCV sẽ không ảnh hưởng các pixel bạn quan tâm.
Gradients và Sobel Derivatives
Một trong các convolution hầu hết cơ bản và quan trọng là tính các đạo hàm (hay xấp xỉ chúng). Có nhiều
cách để làm điều này, nhưng chỉ vài được phù hợp tốt cho trường hợp được cho.
Nhìn chung, hầu hết operator phổ biến dùng để biểu diễn vi phân là Sobel derivative [Sobel68] operator
(xem Figures 6-3 và 6-4). Các Sobel operator tồn tại cho bất kỳ bậc đạo hàm cũng như for các vi phân riêng
phần hỗn hợp (chẳng hạn
yx∂∂∂ /
2
).
Figure 6-3. Hiệu ứng của Sobel operator khi dùng với xấp xỉ một vi phân đầu tiên theo x-dimension
cvSobel(
const CvArr* src,

CvArr* dst,
int xorder,
int yorder,
int aperture_size = 3
);
Ở đâu, src và dst là image input và output của bạn, và xorder và yorder là các thứ tự của vi phân. Điển hình bạn
sẽ dùng 0, 1, hay hầu hết ở 2; một 0 value nhận biết không vi phân theo hướng đó.* aperture_size parameter
nên là lẻ và là width (và height) của square filter. Hiện tại, aperture_sizes 1, 3, 5, và 7 được hỗ trợ. Nếu src là
8-bit thì dst phải có độ sâu IPL_DEPTH_16S để tránh tràn. Sobel derivatives có đặc tính tốt mà chúng có thể
được định nghĩa cho các kernel ở bất kỳ size, và những kernel này có thể được xây dựng nhanh chóng và
lặp lại. Các kernel lớn hơn cho một xấp xỉ tốt hơn cho vi phân vì các kernel nhỏ hơn rất nhậy với noise.
Figure 6-4. The effect of the Sobel operator khi used to approximate a đầu tiên derivative in the y-dimension
Để hiểu điều này chính xác hơn, ta phải thấy rõ rằng một Sobel derivative không thực sự một vi phân. Điều
này là vì Sobel operator được định nghĩa trên một không gian rời rạc. Điều Sobel operator thực sự biểu
diễn là một điều chỉnh thành một đa thức. Mà là, Sobel derivative của bậc hai theo x-direction không thực
sự là vi phân thứ hai; nó là điều chỉnh tại chỗ thành một hàm parabolic. Điều này giải thích một nguyên
nhân có thể muốn dùng một kernel lớn hơn: mà kernel lớn hơn sẽ tính điều chỉnh trên số lớn hơn các pixel.
Scharr Filter
Thật ra, có nhiều cách để thích hợp một vi phân trong trường hợp của một discrete grid. Phía dưới của xấp
xỉ dùng cho Sobel operator mà nó ít chính xác cho các kernel nhỏ. Cho các kernel lớn, nơi nhiều điểm được
dùng trong xấp xỉ, vấn đề này là ít ý nghĩa. Sự không chính xác này không cho thấy trực tiếp cho các X và
Y filters được dùng trong cvSobel(), vì chúng chính xác được sắp với các trục x- và y Sự khó khăn đến khi
bạn muốn làm các đo lường image mà là các xấp xỉ của các đạo hàm hướng (chẳng hạn hướng của image
gradient bởi dùng arctangent của các đáp ứng y/x filter).
Để đặt điều này trong ngữ cảnh, một example cụ thể nơi bạn có thể muốn các đo lường image của kiểu này
sẽ ở tiến trình thu thập thông tin hình dáng từ một object bởi tập hợp một histogram của các góc gradient
quanh object. Chẳng hạn một histogram là cơ sở mà trên nhiều bộ phân loại hình dáng chung được huấn
luyện và hoạt động. Trong trường hợp này, các đo lường không chính xác của gradient angle sẽ giảm hiện
suất nhận diện của bộ phân loại.
Cho một 3-by-3 Sobel filter, các không chính xác là rõ ràng hơn gradient angle từ ngang hay dọc. OpenCV

để ý sự không chính xác này cho các bộ lọc Sobel nhỏ (nhưng nhanh) 3-by-3 bởi một dùng một ít mờ của
giá trị aperture_size đặc biệt CV_SCHARR trong cvSobel() function. Scharr filter không chỉ nhanh mà còn
chính xác hơn Sobel filter, do đó nó sẽ luôn luôn được dùng nếu bạn muốn làm các đo lường image dùng
một 3-by-3 filter. Các hệ số filter cho Scharr filter được thấy trong Figure 6-5 [Scharr00].
Figure 6-5. 3-by-3 Scharr filter dùng flag CV_SHARR
Laplace
OpenCV Laplacian function (đầu tiên được dùng trong vision bởi Marr [Marr82]) thực hiện một tương tự
rời rạc của Laplacian operator:*
2
22
)(
y
f
x
f
fLaplace
e


+


=
Vì Laplacian operator có thể được định nghĩa theo các thuật ngữ của đạo hàm bậc hai, bạn có thể giả thiết
rằng thực hiện rời rạc làm gì đó như đạo hàm Sobel bậc hai. Thực ra nó làm, và thật ra OpenCV thực hiện
Laplacian operator dùng các Sobel operator trực tiếp trong tính toán của nó.
void cvLaplace(
const CvArr* src,
CvArr* dst,
int apertureSize = 3

);
cvLaplace() function lấy các ảnh nguồn và đích như thường cũng như một aperture size. Nguồn có thể là
một trong một 8-bit (unsigned) image hay a 32-bit (floating-point) image. Đích phải là một 16-bit (signed)
image hay 32-bit (floating-point) image. Aperture này chính xác là giống aperture xuất hiện trong các đạo
hàm Sobel và, về hiệu quả, cho size của vùng mà trên đó các pixel được lấy mẫu trong tính toán của đạo
hàm bậc hai.
Laplace operator có thể được dùng trong muôn vàn hoàn cảnh. Một ứng dụng chung là dò các “blob.” Nhờ
lại dạng của Laplacian operator mà là tổng các đạo hàm bậc hai theo trục x và trục y. Điều này có nghĩa
rằng một điểm đơn hay bất kỳ blob nhỏ (nhỏ hơn aperture) mà được bao quanh bởi các giá trị cao hơn sẽ
hướng đến cực đại function này. Ngược lại, một điểm hay blob nhỏ mà được bao quanh bởi các giá trị thấp
hơn sẽ hướng đến tối đa phần âm của function này.
Với điều này trong đầu, Laplace operator có thể cũng được dùng như một kiểu của edge detector. Để thấy
cách điều này được làm, lưu ý đạo hàm đầu tiên của một function, mà sẽ (dĩ nhiên) là lớn bất kể function là
thay đổi nhanh. Quan trọng đều, nó sẽ phát triển nhanh như ta tiếp cận một sự không liên tục giống cạnh và
co rút nhanh như ta chuyển qua sự không liên tục. Do đó đạo hàm sẽ ở cực đại địa phương đâu đó bên trong
dải này. Do đó ta có thể quan sát các số 0 của đạo hàm bậc hai cho các vị trí của cực đại địa phương như
thế. Hiểu không? Các cạnh trong ảnh gốc sẽ là những 0 của Laplacian. Không may, cả các cạnh quan trọng
và ít s nghĩa sẽ là những số 0 của Laplacian, nhưng điều này không là một vấn đề vì ta có thể đơn giản lọc
ra các pixel mà cũng có các giá trị lớn của đạo hàm đầu tiên (Sobel). Figure 6-6 cho thấy một ví dụ dùng
Laplacian trên một image cùng nhau với các chi tiết của đạo hàm đầu tiên và bậc hai và các zero crossing
của chúng.
Canny
Method vừa được mô tả để tìm các cạnh được tinh chỉnh xa hơn bởi J. Canny năm 1986 thành cái mà bây
giờ phổ biến được gọi là Canny edge detector [Canny86]. Một trong các khác biệt giữa Canny algorithm và
thuật toán dựa Laplace đơn giản hơnvới phần trước là, trong thuật toán Canny, các đạo hàm bậc một được
tính theo x và y và sau đó kết hợp thành bốn đạo hàm hướng. Các điểm nơi những đạo hàm hướng này là
cực trị địa phương sau đó là các ứng viên cho tập hợp thành các cạnh.
Figure 6-6. Laplace transform (trên phải) của racecar image: zooming in trên bánh (vòng tròn trắng) và lưu ý chr
hướng x, ta thấy mô tả (qualitative) của sự sáng cũng như đạo hàm bậc một và bậc hai (thấp hơn ba cell); những số 0
trong đạo hàm bậc hai tương ứng vứi các cạnh, và 0 tương ứng đạo hàm bậc một lớn là một cạnh mạnh

Tuy nhiên, hầu hết hướng mới ý nghĩa với thuật toán Canny là nó cố tập hợp các pixel ứng viên cạnh riêng
rẽ thành các contour.* những contours này được hình thành bởi áp dụng một hysteresis threshold cho các
pixel. Điều này có nghĩa rằng có hai thresholds, một trên hơn và một thấp hơn. Nếu một pixel có gradient
lớn hơn upper threshold, thì nó được chấp nhận như một edge pixel; nếu một pixel bên dưới lower
threshold, nó bị loại. Nếu gradient của pixel ở giữa các threshold, thì nó được chấp nhậnchỉ nếu nó được
nối với một pixel mà trên threshold cao. Canny khuyến cáo một ratio của high:low threshold giữa 2:1 và
3:1. Các hình 6-7 và 6-8 cho thấy các kết quả của áp dụng cvCanny() với một mẫu kiểm tra và một ảnh dùng
các high:low hysteresis threshold ratios 5:1 và 3:2, tương ứng.
void cvCanny(
const CvArr* img,
CvArr* edges,
double lowThresh,
double highThresh,
int apertureSize = 3
);
Figure 6-7. Các kết quả của Canny edge detection cho hai ảnh khác nhau khi các high và low thresholds được đặt
thành 50 và 10, tương ứng
cvCanny() function mong một input image, mà phải là grayscale, và một output image, mà phải cũng là
grayscale (nhưng mà sẽ thực sự là một Boolemột image). Hai arguments tiếp là các low và high thresholds,
và argument cuối là một aperture khác. Như thường, đây là aperture được dùng bởi các Sobel derivative
operators mà được gọi bên trong của thực hiện cvCanny().
Hough Transforms
Hough transform* là một method để tìm các đường thẳng, đường tròn, hay các dạng đơn giản trong một
image. Hough transform ban đầu là một biến đổi thẳng, mà là cách tương đối nhanh để tìm một ảnh nhị
phân cho các đường thẳng. Transform này có thể được tổng quát xa hơn cho các trường hợp hơn chỉ cho
các đường đơn giản.
Hough Line Transform
Lý thuyết cơ bản của Hough line transform là bất kỳ điểm trong ảnh nhị phân có thể là phần của vài tập các
đường có thể. Nếu ta tham số hóa mỗi đường bởi, ví dụ, một slope a và một intercept b, thì điểm a trong
một ảnh gốc được biến đổi thành một quỹ tích các điểm trong mặt phẳng (a, b) tương ứng với tất cả các

đường đi qua điểm đó (xem Figure 6-9). Nếu ta chuyển mọi nonzero pixel trong input image thành một tập
các điểm trong output image và tộng trên tất cả các đóng góp, thì các đường mà xuất hiện trong input
(chẳng hạn (x, y) plane) image sẽ xuất hiện như cực trị địa phương trong output (chẳng hạn (a, b) plane)
image. Vì ta đang cộng các hợp thành từ mỗi điểm, (a, b) plane nhìn chung được gọi là accumulator plane.
Figure 6-8. các kết quả của Canny edge detection cho hai image khác nhau khi các high và low thresholds được đặt
thành 150 và 100, tương ứng
Điều có thể xảy ra với bạn rằng dạng slope-intercept không thực sự là cách tốt nhất để biểu diễn tất cả các
đường đi qua một điểm (vì mật độ khác nhau đáng kể của các đường như một function của slope, và sự thật
liên qua rằng interval của các slope có thể đi từ –∞ đến +∞). Nó là cho nguyên nhân này mà sự tham số hóa
thực của transform image dùng trong tính toán số là gì đó khác. Sự tham số hóa ưa thích hơn biểu diễn mỗi
đường như một điểm trong tọa độ cực (ρ, θ), với đường ngầm định bằng đầu là đường đi qua điểm nhận
biết nhưng vuông góc với radial từ gốc đến điểm đó (xem Figure 6-10). Phương trình cho một đường thẳng
là :
)sin()cos(
θθ
yxp +=
Figure 6-9. Hough line transform tìm thấy nhiều đường trong mỗi image; vài trong các đường được thấy như mong
đợi, nhưng những cái khác có thể không
Figure 6-10. Một point (x0, y0) trong image plane (panel a) ngụ ý nhiều đường mỗi cái được tham số bởi một ρ và θ
(panel b) khác nhau; những đường này mỗi cái được ngụ ý các điểm trong (ρ, θ) plane, mà được lấy cùng nhau hình
thành đường cong hình dáng đặc tính (panel c)
Thuật toán Hough transform OpenCV không thực hiện tính toán này rõ ràng với user. Thay vào đó, nó đơn
giản trả về cực trị địa phương trong (ρ, θ) plane. Tuy nhiên, bạn sẽ cần hiểu tiến trình này để hiểu các
argument cho OpenCV Hough line transform function.
OpenCV hỗ trợ hai kiểu khác nhau của Hough line transform: standard Hough transform (SHT) [Duda72]
và progressive probabilistic Hough transform (PPHT).* SHT là thuật toán ta vừa xem xét. PPHT là một
biến thể của thuật toán này mà, cùng với các thứ khác, tính một mở rộng cho các đường cụ thể thêm vào đó
với hướng (như được thấy trong Figure 6-11). Nó là “có khả năng” vì, hơn việc lũy kế mọi điểm có khả
năng trong mặt phẳng lũy kế, nó lũy kế chỉ một phần của chúng. Ý tưởng là nếu đỉnh sẽ là đủ cao đại khái,
sau khi đánh nó chỉ một phần của thời gian sẽ để để tìm thấy nó; Kết quả của ước đoán này có thể là giảm

thực chất trong thời gian tính. Cả hai thuật toán này được truy cập với cùng OpenCV function, qua các
phương tiện của vài các argument phụ thuộc vào method mà đang được dùng.
CvSeq* cvHoughLines2(
CvArr* image,
void* line_storage,
int method,
double rho,
double theta,
int threshold,
double param1 = 0,
double param2 = 0
);
Argument đầu tiên là input image. Nó phải là một 8-bit image, nhưng input được đối xử như thông tin
binary (chẳng hạn tất cả các nonzero pixel được xem như bằng nhau). Argument thứ hai là một pointer đến
một nơi đặt các kết quả có thể được lưu, mà có thể là một trong memory storage (xem CvMemoryStorage
trong chương 8) hay một N-by-1 matrix array phẳng (số các hàng, N, sẽ phục vị để giới hạm số cực đại các
đường trả về). Argument tiếp theo, method, có thể là CV_HOUGH_STANDARD,
CV_HOUGH_PROBABILISTIC, hay CV_HOUGH_MULTI_SCALE cho (tương ứng) SHT, PPHT, hay một
biến thể multiscale của SHT. Hai argument tiếp theo, rho và theta, đặt phân giải mong muốn cho các
đường (chẳng hạn phân giải của mặt phẳng lỹ kế). Các đơn vị của rho là pixel và đơn vị của theta là
radians; Do đó, mặt phẳng lũy kế có thể được nghĩ như histogram hai chiều với các cell của các pixel rho
chiều với các theta radian. Giá trị threshold là giá trị trong mặt phẳng lũy kế mà phải đạt được cho
routine để report một đường thẳng.
Argument cuối là một tí mẹo trong thực tế; không tiêu chuẩn hóa, do đó bạn sẽ mong tỉ lệ nó bằng image
size cho SHT. Nhớ rằng argument này, về hiệu quả, nhận diện số các điểm (the edge image) mà phải hỗ trợ
đường cho đường này được trả về.
Figure 6-11. Canny edge detector (param1=50, param2=150) được chạy đầu tiên, với các kết quả được thấy trong
gray, và progressive probabilistic Hough transform (param1=50, param2=10) được chạy tiếp theo, với các kết quả
được chồng theo màu trắng; bạn có thể thấy rằng các đường mạnh nhìn chung được nhấc bởi Hough transform
Các tham số param1 và param2 không được dùng bởi SHT. Cho PPHT, param1 đặt chiều dài tối thiểu của

một đoạn thẳng mà sẽ được trả về, và param2 đặt sự chia tách giữa các đoạn collinear đòi hỏi cho thuật toán
không hợp chúng thành một đoạn đơn dài hơn. Cho multiscale HT, hai parameters được dùng để nhận diện
các phân giải cao hơn mà các parameter cho các đường thẳng nên được tính. Multiscale HT đầu tiên tính
các vị trí các đường với độ chính xác được cho bởi các tham số rho và theta và sau đó tiếp tục tinh chỉnh các
kết quả này bởi một factor của param1 và param2, tương ứng (chẳng hạn phân giải cuối cùng trong rho là rho
được dẫn xuất bởi param1 và phân giải cuối trong theta được chia bởi param2).
Cái function trả về phụ thuộc vào cách nó được gọi. Nếu giá trị line_storage là một matrix array, thì giá trị
thực trả về sẽ là NULL. Trong trường hợp này, matrix nên là type CV_32FC2 nếu SHT hay multi-scale HT
đang được dùng và nên là CV_32SC4 nếu PPHT đang được dùng. Trong hai trường hợp đầu tiên, các giá trị
ρ- và θ-cho mỗi đường thẳng sẽ được đặt trong hai channels của array. Trong trường hợp của PPHT, bốn
channels sẽ giữ các giá trị x- và y- của các điểm bắt đầu và kết thúc của các đoạn trả về. Trong tất cả những
trường hợp này, số các hàng trong array sẽ được cập nhật bởi cvHoughLines2() để phản ảnh đúng số các
đường trả về.
Nếu giá trị line_storage là một một pointer đến một memory store,* thì giá trị trả về sẽ là một pointer đến
một CvSeq sequence structure. Trong trường hợp đó, bạn có thể lấy từng đường thẳng hay đoạn thẳng từ
chuỗi này bằng một lệnh như
float* line = (float*) cvGetSeqElem( lines , i);
trong đó cá đường là giá trị trả về từ cvHoughLines2() và i là index của đường thẳng mong muốn. Trong
trường hợp này, đường thẳng sẽ là một pointer đến data cho đường thẳng đó, với line[0] và line[1] là các giá trị
floating-point ρ và θ (cho SHT và MSHT) hay các CvPoint structure cho các điểm cuối của các đoạn (cho
PPHT).
Hough Circle Transform
Hough circle transform [Kimme75] (xem Figure 6-12) làm việc theo cách đại khái tương tự với các Hough
line transform veaf được mô tả. Nguyên nhân nó chỉ “đại khái” là—nếu một cái cố làm điều tương tự chính
xác —mặt phẳng lũy kế sẽ phải phải được thay bằng volume lỹ kế với ba chiều: một cho x, một cho y, và
một cái khác cho circle radius r. Điều này có nghĩa các yêu cầu bộ nhớ lớn hơn nhiều và tốc độ chậm hơn
nhiều. Thực hiện của circle transform trong OpenCV tránh vấn đề này bởi dùng một phương pháp thông
minh hơn gọi là Hough gradient method.
Hough gradient method làm việc như sau. đầu tiên image được chuyển qua một edge detection phase (trong
trường hợp này, cvCanny()). Tiếp theo, cho mọi nonzero point trong edge image, local gradient được xem

xét (gradient được tính bởi tính toán đầu tiên các đạo hàm bậc một Sobel x- và y-qua cvSobel()). Dùng
gradient này, mọi điểm dọc đường này được nhận biết bởi slope này—từ một khoảng cách tối thiểu cụ thể
đến một tối đa cụ thể—được tăng theo accumulator. Ở cùng thời điểm, vị trí của mỗi cái trong những
nonzero pixels này trong edge image được lưu ý. Các tâm ứng viên sau đó được chọn từ những điểm này
theo lũy kế này (hai chiều) mà là cả trên vài threshold cho trước và lớn hơn tất cả trong các láng giềng tức
thời của chúng. Những tâm ứng viên này được lưu theo thứ tự giảm dần của các giá trị lũy kế của chúng,
sao cho các tâm với hầu hết pixel hỗ trợ xuất hiện đầu tiên. Tiếp theo, cho mỗi tâm, tất cả các nonzero pixel
(nhớ rằng danh sách này được dựng trước) được xem xét. Những pixels này được lưu theo khoảng cách đến
tâm. Làm việc với các khoảng cách nhỏ nhất đến bán kính lớn nhất, một bán kính đơn được chọn mà được
hỗ trợ tốt nhất bởi các nonzero pixel. Một tâm được giữ nếu nó có đủ hỗ trợ từ các nonzero pixel theo edge
image và nếu nó đủ khoảng cách từ bất kỳ tâm được chọn trước đây.
Thực hiện này cho phép thuật toán chạy nhanh hơn nhiều và, có lẽ quan trọng hơn, giúp giải vấn đề của
bùng nổ dân số thưa thời ngược lại của một lũy kế ba chiều, mà sẽ dẫn đến nhiều nhiễu và render các kết
quả không ổn định. Mặt khác, thuật toán này có vài khuyết điểm mà bạn nên biết.
Figure 6-12. Hough circle transform tìm thấy vài circles trong mẫu kiểm tra và (correctly) tìm thấy không trong
photograph
Đầu tiên, dùng các đạo hàm Sobel để tính local gradient—và sự chiếm giữ kèm theo mà điều này có thể
được xem tương đương với một tiếp tuyến địa phương—không một xác nhận ổn định số. Nó có thể là đúng
“hầu hết mọi lúc,” nhưng bạn nên mong điều này tạo vài noise trong output.
Thứ hai, tập toàn bộ các nonzero pixel trong edge image được quan tâm cho mọi tâm ứng cử; do đó, nếu
bạn làm ngưỡng lũy kế quá thấp, thuật toán sẽ mất nhiều thời gian để chạy.
Thứ ba, vì một đường tròn được chọn cho mỗi tâm, nếu có các đường tròn đồng tâm thì bạn sẽ lấy chỉ một
trong chúng.
Cuối cùng, vì các tâm được xem theo thứ tự tăng dần của các giá trị lũy kế kết hợp với chúng và vì các tâm
mới không được giữ nếu chúng qua gần với các tâm được chấp nhận trước, có một khuynh hướng dẫn đến
giữ các đường tròn lớn hơn khi nhiều đường tròn đồng tâm hay gần đồng tâm. (nó chỉ là một “thiên hướng”
vì nhiễu đến từ các đạo hàm Sobel; trong ảnh làm trơn ở phân giải vô cùng, nó sẽ là một chắc chắn.)
Với tất cả điều này trong đầu, hãy tiến vào OpenCV routine mà làm tất cả điều này cho ta:
CvSeq* cvHoughCircles(
CvArr* image,

void* circle_storage,
int method,
double dp,
double min_dist,
double param1 = 100,
double param2 = 300,
int min_radius = 0,
int max_radius = 0
);
Hough circle transform function cvHoughCircles() có các argument tương tự với line transform. Input image
lần nữa là một 8-bit image. Một khác biệt ý nghĩa giữa cvHoughCircles() và cvHoughLines2() là cái sau đòi hỏi
một ảnh nhị phân.
cvHoughCircles() function sẽ bên trong (tự động) gọi cvSobel()* cho bạn, do đó bạn có thể cung cấp một
grayscale image tổng quát hơn.
circle_storage có thể là một trong một array hay memory storage, phụ thuộc vào cách bạn thích các kết quả
trả về. Nếu an array được dùng, nó nên là cột đơn type CV_32FC3; ba channels sẽ được dùng để encode vị
trí của đường tròn và bán kính của nó. Nếu memory storage được dùng, thì các đường tròn sẽ được làm
thành một chuỗi OpenCV và một pointer đến chuỗi đó sẽ được trả về bởi cvHoughCircles(). (Cho trước một
array pointer value cho circle_storage, giá trị trả về của cvHoughCircles() là NULL.) Method argument phải
luôn luôn được đặt thành CV_HOUGH_GRADIENT. Parameter dp là phân giải của ảnh lũy kế được dùng.
Parameter này cho phép ta tạo một bộ lũy kế của một phân giải thấp hơn input image. (thật ý nghĩa để làm
điều này vì không có lý do để mong các đường tròn tòn tạo trong image để rơi tự nhiên vào cùng số các
nhóm như width hay height của image tự nó.) Nếu dp được đặt thành 1 thì các phân giải sẽ là giống; nếu đặt
thành một số lớn hơn (chẳng hạn 2), thì phân giải lũy kế sẽ nhỏ hơn bởi factor đó (trong trường hợp này,
một nửa). Giá trị của dp không thể nhỏ hơn 1. Parameter min_dist là khoảng cách tối thiểu mà phải tồn tại giữa
hai đường tròn để thuật toán lưu ý chúng để phân biệt các đường tròn.
Cho trường hợp (hiện tại được đòi hỏi) của method đang được đặt thành CV_HOUGH_GRADIENT, hai
argument tiếp theo, param1 và param2, là edge (Canny) threshold và accumulator threshold, tương ứng. Bạn
có thể nhớ lại Canny edge detector thực sự lấy hai threshold khác nhau chính nó. Khi cvCanny() được gọi
bên trong, threshold đầu tiên (cao hơn) được đặt thành value của param1 chuyển vào cvHoughCircles(), và

threshold thứ hai (thấp hơn) được đặt chính xác một nửa giá trị đó. Parameter param2 là cái được dùng để
threshold bộ lũy kế và là chính xác tương tự với threshold argument của cvHoughLines().
Hai parameters cuối là các bán kính cực tiểu và cực đại của các đường tròn có thể được thấy. Điều này có
nghĩa rằng những cái này là radii của các đường tròn mà bộ lũy kế có một biểu diễn. Example 6-1 cho thấy
một program ví dụ dùng cvHoughCircles().
Example 6-1. Dùng cvHoughCircles để return một chuỗi các đường tròn tìm thấy trong một grayscale image
#include <cv.h>
#include <highgui.h>
#include <math.h>
int main(int argc, char** argv) {
IplImage* image = cvLoadImage(
argv[1],
CV_LOAD_IMAGE_GRAYSCALE
);
CvMemStorage* storage = cvCreateMemStorage(0);
cvSmooth(image, image, CV_GAUSSIAN, 5, 5 );
CvSeq* results = cvHoughCircles(
image,
storage,
CV_HOUGH_GRADIENT,
2,
image->width/10
);
for( int i = 0; i < results->total; i++ ) {
float* p = (float*) cvGetSeqElem( results, i );
CvPoint pt = cvPoint( cvRound( p[0] ), cvRound( p[1] ) );
cvCircle(
image,
pt,
cvRound( p[2] ),

CV_RGB(0xff,0xff,0xff)
);
}
cvNamedWindow( “cvHoughCircles”, 1 );
cvShowImage( “cvHoughCircles”, image);
cvWaitKey(0);
}
Điều đáng phản ảnh tạm thời về sự thật rằng, không vần đề gì với các mẹo ta thực hiện, không có lấy quanh
yêu cầu rằng các đường tròn được mô tả bởi ba bậc tự do (x, y, và r), ngược với chỉ hai bậc tự do (ρ và θ)
cho các đường thẳng. Kết quả sẽ bất biến mà bất kỳ thuật toán tìm đường tròn đòi hỏi nhiều memory và
thời gian tính toán hơn các thuật toán tìm đường thẳng ta xem xét trước đây. Với điều này trong ý nghĩ, nó
là ví dụ tốt để bao tham số bán kính chặt như các trường hơpj cho phép để giữ những chi phí này dưới điều
khiển.* Hough transform được mở rộng cho các hình tùy ý bởi Ballard năm 1981 [Ballard81] về cơ bản bởi
quan tâm các đối tượng như tập các gradient edge.
Remap
Phía bên dưới, nhiều trong các biến đổi theo sau có một phần tử giải thích nào đó. Về cụ thể, chúng sẽ lấy
các pixel từ một nơi trong image và chiếu chúng vào một nơi khác. Trong trường hợp này, sẽ luôn luôn có
vài chiếu trơn, mà sẽ làm điều ta cần, nhưng nó sẽ không luôn luôn là đáp ứng pixel một một. Ta đôi khi
muốn hoàn thành nội suy này một cách lập trình; mà là, ta thích áp dụng vài thuật toán đã biết mà sẽ xác
định phép chiếu. Trong các trường hợp khác, tuy nhiên, ta thích tự làm phép chiếu này. Trước khi đi sâu
vào vài method mà sẽ tính (và áp dụng) những phép chiếu cho ta, hãy tốn ít thời gian để quan sát đáp ứng
function để áp dụng các phép chiếu mà những method khác này dựa vào. OpenCV function ta muốn được
gọi là cvRemap():
void cvRemap(
const CvArr* src,
CvArr* dst,
const CvArr* mapx,
const CvArr* mapy,
int flags = CV_INTER_LINEAR | CV_WARP_FILL_OUTLIERS,
CvScalar fillval = cvScalarAll(0)

);
Hai arguments đầu tiên của cvRemap() là các ảnh nguồn và ảnh đích, tương ứng. Hiển nhiên, những cái này
nên là cùng size và số các channel, nhưng chúng có thể có bất kỳ data type. Điều quan trọng phải lưu ý
rằng hai cái không thể là cùng ảnh.* Hai argument tiếp, mapx và mapy, nhận diện nơi bất kỳ pixel cụ thể
được định vị lại. Những cái này nên là cùng size như ảnh nguồn và ảnh đích, nhưng chúng là single-
channel và thường của data type float (IPL_DEPTH_32F). Các phép chiếu không nguyên là OK, và
cvRemap() sẽ là các phép tính nội suy cho bạn tự động. Một việc dùng phổ biến của cvRemap() là chỉnh sửa
(làm đúng các méo) các ảnh được calibrated và stereo images. Ta sẽ thấy các function trong các Chương 11
và 12 mà chuyển đổi các méo camera được tính toán và sắp xếp thành các tham số mapx và mapy. Argument
tiếp theo chứa flags mà bảo cvRemap() độ chính xác mà nội suy được làm. Bất kỳ một trong các giá trị này kê
trong Table 6-1 sẽ làm việc.
Table 6-1. cvWarpAffine() additional flags values
flags values Meaning
CV_INTER_NN Nearest neighbor
CV_INTER_LINEAR Bilinear (default)
CV_INTER_AREA Pixel area resampling
CV_INTER_CUBIC Bicubic interpolation
Nội suy là một vấn đề quan trọng ở đây. Các pixel trong ảnh nguồn nằm trên một integer grid; ví dụ, ta có
thể refer đến một pixel ở vị trí (20, 17). Khi những vị trí nguyên này được chiếu vào ảnh mới, có thể có các
gap—một trong vì các vị trí pixel nguồn nguyên được chiếu thành các vị trí float trong ảnh đích và phải
được làm tròn thành vị trí pixel nguyên gần nhất hay vì có vài vị trí mà không các pixel mà tất cả được
chiếu (nghĩ về gấp đôi kích thước ảnh bởi kéo dãn nó; sau đó mọi pixel đích sẽ được để trống). Những vấn
đề này nhìn chung được xem như các vấn đề forward projection. Để làm việc các vấn đề làm tròn như thế
và các gap đích, ta thực sự giải quết vấn đề theo hướng khác: ta bước qua mỗi pixel của ảnh đích và hỏi,
“Pixel nào trong source được cần điền vào pixel đích này?” những source pixels này sẽ hầu hết luôn luôn là
các vị trí pixel phân đoạn do đó ta phải nội suy các source pixels để dẫn xuất giá trị đúng cho giá trị đích.
Method mặc định là nội suy bilinear, nhưng bạn có thể chọn các method khác (như được thấy trong Table
6-1).
Bạn có thể cũng thêm (dùng OR operator) flag CV_WARP_FILL_OUTLIERS, mà hiệu quả là điền các pixel
trong ảnh đích mà không là đích của bất kỳ pixel trong input image với giá trị nhận diện bởi argument cuối

cùng fillval. Theo cách này, nếu bạn chiếu tất cả của image của bạn thành một vòng tròn theo tâm sau đó
bên ngoài của vòng tròn đó sẽ tự động được tô bằng màu đen (hay bất kỳ màu khác mà bạn tưởng tượng).
Stretch, Shrink, Warp, và Rotate
Trong phần này ta chuyển đến thao tác hình học của ảnh.* Các thao tác như thế gồm kéo dãn theo nhiều
cách, mà bao gồm cả resizing đồng dạng hay không đồng dạng (cái sau được biết là warping). Có nhiều
những nguyên nhân để thực hiện những tác vụ này: ví dụ, warping và quay một image sao cho nó có thể
được xếp chồng trên tường trong một cảnh tồn tại, hay làm lớn nhân tạo một tập các ảnh huấn luyện dùng
để nhận dạng đối tượng.† Các function mà có thể stretch, shrink, warp, và/hoặc rotate một image được gọi
là các geometric transform (cho một bộc lộ sớm, thấy [Semple79]). Cho các vùng phẳng, có hai hương vị
của các geometric transform: các transform mà dùng một 2-by-3 matrix, mà được gọi affine transforms; và
các transform dựa trên a 3-by-3 matrix, mà được gọi perspective transforms hay homographies. Bạn có thể
nghĩ biến đổi sau như một method để tính cách mà trong mặt phẳng trong ba chiều được nhận thức bởi một
người quan sát cụ thể, người không thể quan sát thẳng trên mặt phẳng đó. Một biến đổi affine là bất kỳ biến
đổi mà có thể được nhằm theo dạng của một tích matrix theo sau bởi một tổng vector. Trong OpenCV kiểu
chuẩn của biểu diễn một biến đổi như thế là một 2-by-3 matrix. Ta định nghĩa:







1110
0100
aa
aa
A









1
0
b
b
B

[ ]
ABT ≡








y
x
X













1
'
y
x
X
Điều được thấy dễ dàng hiệu quả của biến đổi affine A · X + B chính xác tương đương với mở rộng vector
X thành vector X´ và đơn giản nhân trái X´ bởi T. Các biến đổi affine có thể được thấy như sau. Bất kỳ
hình bình hành ABCD trong một mặt phẳng có thể được chiếu vào bất kỳ hình bình hành khác A'B'C'D' bởi
vài biến đổi affine. Nếu các vùng của những hình bình hành này là khác không, thì biến đổi affine ngầm
định được định nghĩa duy nhất bởi (ba cạnh của) hai hình bình hành. Nếu bạn thích, bạn có thể nghĩ một
biến đổi affine như vẽ ảnh của bạn thành một trang cao su lớn và sau đó biến dạng trang giấy bởi ấn hay
kéo* trên các cạnh để làm các kiểu khác nhau của các hình bình hành.
Khi ta có nhiều ảnh mà ta biết là các view khác nhau một ít của cùng đối tượng, ta có thể muốn tính các
biến đổi thực mà liên quan các view khác nhau. Trong trường hợp này, các biến đổi affine thường được
dùng để mô hình các view vì, có ít parameter hơn, chúng dễ để giải quyết. Phía dưới là sự thật rằng các
méo mó phối cảnh có thể được mô hình bởi một homography,† do đó các biến đổi affine sản ra một biểu
diễn mà không thể thích hợp tất cả các quan hệ có thể giữa các view. Mặt khác, cho các thay đổi nhỏ trong
viewpoint méo kết quả là affine, do đó trong vài trường hợp một biến đổi affine có thể là đủ.
Các biến đổi Affine có thể chuyển các hình chữ nhật thành các hình bình hành. Chúng có thể nén hình dáng
nhưng phải giữ các cạnh song song; chúng có thể quay nó và/hoặc tỉ lệ nó. Các biến đổi phối cảnh cung cấp
nhiều linh hoạt hơn; một biến đổi phối cảnh có thể chuyển hình chữ nhật thành hình thanh. Dĩ nhiên, do các
hình bình hành cũng là hình thang, các biến đổi affine là tập con của các biến đổi phối cảnh. Figure 6-13
cho thấy các ví dụ của các biến đổi affine và phối cảnh khác nhau.
Affine Transform

Có hai trường hợp mà đến khi làm việc với cac biến đổi affine. Trong trường hợp đầu tiên, ta có một image
(hay một vùng quan tâm) ta thích biến đổi; trong trường hợp thứ hai, ta có một danh sách các điểm mà ta
muốn tính kết quả của một biến đổi.
Dense affine transformations
Trong trường hợp đầu tiên, các định dạng input và output hiển nhiên là ảnh, và yêu cầu ngầm định là việc
warping giả sử các pixel là một dense representation của ảnh nằm dưới. Điều này có nghĩa rằng image
warping phải handle cần thiết các nội suy sao cho các output image là trơn và trông tự nhiên. Hàm biến đổi
affine được cấp bởi OpenCV cho các biến đổi dense là cvWarpAffine().
Figure 6-13. Các biến đổi Affine và phối cảnh
void cvWarpAffine(
const CvArr* src,
CvArr* dst,
const CvMat* map_matrix,
int flags = CV_INTER_LINEAR | CV_WARP_FILL_OUTLIERS,
CvScalar fillval = cvScalarAll(0)
);
Ở đây src và dst refer đến một array hay image, mà có thể là một trong một hay ba channels và của bất kỳ
type (cung cấp chúng là cùng type và size).* map_matrix là 2-by-3 matrix ta giới thiệu trước mà định lượng
biến đổi mong muốn. next-tolast argument, flags, điều khiển method nội suy cũng như một trong hay cả
option thêm sau (như bình thường, kết hợp với Boolean OR).
CV_WARP_FILL_OUTLIERS
Thường, ảnh được biến đổi src không vừa vặn vào ảnh dst —có các pixels “được chiếu” từ source
file mà không thực sự tồn tại. Nếu flag này được đặt, thì những giá trị mất này được điền bằng
fillval (được mô tả trước đây).
CV_WARP_INVERSE_MAP
Flag này để thuận tiện nhằm cho phép warping ngược từ dst đến src thay vì từ src đến dst.
cVWarpAffine performance
Điều đáng biết mà cvWarpAffine() liên quan khó khăn kết hợp thực sự. Một thay thế là dùng
cvGetQuadrangleSubPix(). Function này có ít option hơn but vài thuận lợi. Về cụ thể, nó có ít overhead và có
thể handle trường hợp đặc biệt khi ảnh nguồn là 8-bit và ảnh đích là 32-bit floating-point image. Nó cũng

sẽ handle các multichannel image.
void cvGetQuadrangleSubPix(
const CvArr* src,
CvArr* dst,
const CvMat* map_matrix
);
Điều cvGetQuadrangleSubPix() làm là tính tất cả các điểm trong dst bởi chiếu chúng (bằng nội suy) từ các
điểm trong src mà được tính bởi áp dụng biến đổi affine bởi nhận với 2-by-3 map_matrix. (Chuyển đổi các vị
trí trong dst thành tọa độ homogeneous để phép nhân được làm tự động.)
Một tính chất của cvGetQuadrangleSubPix() là có một phép chiếu phụ áp dụng bởi function. Về cụ thể, các
điểm kết quả trong dst được tính tùy theo công thức:
),(),(
1
''
11
''
100
'
01
'
00
byaxabyaxasrcyxdst ++++=
Trong đó








11110
00100
baa
baa
M















=






2
)1)((
2

)1)((
''
''
dstheight
y
dstwidth
x
y
x
Nhận xét thấy phép chiếu từ (x, y) thành (x˝, y˝) có hiệu ứng mà—ngay cả nếu phép chiếu M là phép chiếu
đồng nhất—các điểm trong ảnh đích ở tâm sẽ được lấy từ ảnh gốc ở gốc. Nếu cvGetQuadrangleSubPix() cần
các điểm từ bên ngoài ảnh, nó dùng sự tái tạo để dựng lại các giá trị này.
Computing the affine map matrix
OpenCV cung cấp hai functions giúp bạn tạo map_matrix. Cái đầu tiên được dùng khi bạn hiện có hai ảnh
mà bạn biết được liên quan bởi một biến đổi affine hay bạn thích xấp xỉ theo cách đó:
CvMat* cvGetAffineTransform(
const CvPoint2D32f* pts_src,
const CvPoint2D32f* pts_dst,
CvMat* map_matrix
);
Ở đây src và dst là các array chứa ba điểm hai chiều (x, y), và map_matrix là biến đổi affine tính từ các điểm
này. pts_src và pts_dst trong cvGetAffineTransform() chỉ là các array của ba điểm định nghĩa hai hình
bình hành. Cách đơn giản nhất để định nghĩa một biến đổi affine do đó là đặt pts_src thành ba góc trong ảnh
nguồn—ví dụ, trên trái và dưới trái cùng với trên phải của ảnh nguồn. Phép chiếu từ ảnh nguồn đến ảnh
đích sau đó được định nghĩa bởi chỉ định pts_dst, các vị trí mà những ba điểm này sẽ được chiếu trong that
ảnh đích. Một khi việc chiếu những ba góc độc lập này (mà, về hiệu quả, chỉ định một hình bình hành “biểu
diễn”) được thiết lập, tất cả các điểm khác có thể được wrap phù hợp.
Example 6-2 cho thấy ít code mà dùng những function này. Trong example ta lấy các tham số matrix
cvWarpAffine() bởi đầu tiên tính hai array ba thành phần của các điểm (các góc của hình bình hành biểu diễn
của ta) và sau đó chuyển thành matrix biến đổi thực dùng cvGetAffineTransform(). Ta sau đó làm một affine

warp theo sau bởi quay của image. Cho array của các điểm biểu diễn trong ảnh nguồn, gọi là srcTri[], ta lấy
ba điểm: (0,0), (0,height-1), và (width-1,0). Ta sau đó chỉ định các vị trí mà những điểm này sẽ được chiếu
theo array tương ứng srcTri[].
Example 6-2. An affine transformation
// Usage: warp_affine <image>
//
#include <cv.h>
#include <highgui.h>
int main(int argc, char** argv)
{
CvPoint2D32f srcTri[3], dstTri[3];
CvMat* rot_mat = cvCreateMat(2,3,CV_32FC1);
CvMat* warp_mat = cvCreateMat(2,3,CV_32FC1);
IplImage *src, *dst;
if( argc == 2 && (( {
dst = cvCloneImage( src );
dst->origin = src->origin;
cvZero( dst );
// Compute warp matrix
//
srcTri[0].x = 0; //src Top left
srcTri[0].y = 0;
srcTri[1].x = src->width - 1; //src Top right
srcTri[1].y = 0;
srcTri[2].x = 0; //src Bottom leftoffset
srcTri[2].y = src->height - 1;
dstTri[0].x = src->width*0.0; //dst Top left
dstTri[0].y = src->height*0.33;
dstTri[1].x = src->width*0.85; //dst Top right
dstTri[1].y = src->height*0.25;

dstTri[2].x = src->width*0.15; //dst Bottom leftoffset
dstTri[2].y = src->height*0.7;
cvGetAffineTransform( srcTri, dstTri, warp_mat );
cvWarpAffine( src, dst, warp_mat );
cvCopy( dst, src );
// Compute rotation matrix
//
CvPoint2D32f center = cvPoint2D32f(
src->width/2,
src->height/2
);
double angle = -50.0;
double scale = 0.6;
cv2DRotationMatrix( center, angle, scale, rot_mat );
// Do the transformation
//
cvWarpAffine( src, dst, rot_mat );
cvNamedWindow( “Affine_Transform”, 1 );
cvShowImage( “Affine_Transform”, dst );
cvWaitKey();
}
cvReleaseImage( &dst );
cvReleaseMat( &rot_mat );
cvReleaseMat( &warp_mat );
return 0;
}
}
Cách thứ hai để tính map_matrix là dùng cv2DRotationMatrix(), mà tính map matrix cho một phép quay quanh
vài điểm tùy ý, kết hợp với một rescaling tùy chọn. Đây chỉ là một kiểu có thể của biến đổi affine, nhưng
nó biểu diễn một tập con quan trọng mà có một biểu diễn thay thế (mà nhiều trực giác hơn) mà dễ hơn để

làm việc trong đầu bạn:
CvMat* cv2DRotationMatrix(
CvPoint2D32f center,
double angle,
double scale,
CvMat* map_matrix
);
Argument đầu tiên, center, là điểm tâm của phép quay. Hai argument tiếp cho biên độ của phép quay và
rescaling toàn bộ. Argument cuối là output map_matrix, mà (như luôn luôn) là một 2-by-3 matrix của các số
floating-point).
Nếu ta định nghĩa
)cos(anglescale ⋅=
α

)sin(anglescale ⋅=
β
thì function này tính map_matrix
là:






⋅−+⋅−
⋅−⋅−
yx
yx
centercenter
centercenter

)1(
)1(
αβαβ
βαβα
Bạn có thể kết hợp những methods này để setting map_matrix để lấy, ví dụ, một image mà được quay,
scaled, và warped.
Sparse affine transformations
Ta đã giải thích cvWarpAffine() là cách đúng để handle các phép chiếu dày đặc. Cho các phép chiếu thưa
(chẳng hạn chiếu các dánh sách các điểm cụ thể), tốt nhất là dùng cvTransform():
void cvTransform(
const CvArr* src,
CvArr* dst,
const CvMat* transmat,
const CvMat* shiftvec = NULL
);
Nhìn chung, src là một N-by-1 array với Ds channels, trong đó N là số các điểm được biến đổi và Ds là chiều
của các điểm nguồn này. Output array dst phải cùng size nhưng có thể có số khác các channel, Dd. Matrix
biến đổi transmat là một Ds-by-Dd matrix mà sau đó được áp dụng với mọi phần tử của src, mà sau đó các
kết quả được đặt vào dst. Optional vector shiftvec, nếu non-NULL, phải là một Ds-by-1 array, mà được
thêm vào mỗi kết quả trước khi kết quả được đặt vào dst.
Trong trường hợp của một biến đổi affine, có hai cách để dùng cvTransform() mà phụ thuộc vào cách ta
thích biểu diễn biến đổi của ta. Trong method đầu tiên, ta phân ly biến đổi thành phần 2-by-2 part (mà làm
rotation, scaling, và warping) và phần 2-by-1 (mà làm biến đổi). Ở đây input của ta là một N-by-1 array với
hai channels, transmat là biến đổi homogeneous tại chỗ, và shiftvec chứa bất kỳ thay thế cần thiết.
Method thứ hai là dùng biểu diễn 2-by-3 bình thường của biến đổi affine. Trong trường hợp này input array
src là một three-channel array bên trong mà ta phải đặt tất cả các third-channel entry thành 1 (chẳng hạn
các điểm phải được cung cấp theo tọa độ homogeneous). Dĩ nhiên, output array sẽ vẫn là một two-channel
array.
Perspective Transform
Để có lợi sự linh hoạt lớn hơn cấp bởi cac biến đổi phối cảnh (homographies), ta cần một function mới mà

sẽ cho phép ta nhanh chóng đến lớp rộng hơn các biến đổi. Đầu tiên ta chú ý rằng, ngay cả qua chiếu phối
cảnh được chỉ định hoàn toàn bởi một matrix đơn, việc chiếu không thực sự là biến đổi tuyến tính. Đây là
vì biến đổi này đòi hỏi chia bởi chiều cuối cùng (thường Z; xem chương 11) và do đó mất một chiều trong
tiến trình.
Như với các biến đổi affine, các image operation (các biến đổi dày đặc) được handle bởi các function khác
nhau hơn các biến đổi trên tập điểm (biến đổi thưa).
Dense perspective transform
Biến đổi phối cảnh dày đặc dùng một OpenCV function mà tương tự với cái cung cấp cho các biến đổi
affine dày đặc. Đặc biệt, cvWarpPerspective() có tất cả cùng argument như cvWarpAffine() nhưng
với phân biệt nhỏ nhưng quan trọng mà map matrix phải bây giờ là 3-by-3.
void cvWarpPerspective(
const CvArr* src,
CvArr* dst,
const CvMat* map_matrix,
int flags = CV_INTER_LINEAR + CV_WARP_FILL_OUTLIERS,
CvScalar fillval = cvScalarAll(0)
);
Các flags là giống ở trường hợp affine.
Computing the perspective map matrix
Như với biến đổi affine, để điền map_matrix trong mã trước đây ta có một function tiện ích mà có thể tính
matrix biến đổi từ một danh sách của các tương ứng điểm:
CvMat* cvGetPerspectiveTransform(
const CvPoint2D32f* pts_src,
const CvPoint2D32f* pts_dst,
CvMat* map_matrix
);
pts_src và pts_dst bây giờ là các array của bốn điểm (không phải ba) points, do đó ta có thể điều khiển
độc lập cách các góc (điển hình) một hình chữ nhật trong pts_src được chiếu vào (nói chung) vài hình
thoi trong pts_dst. Biến đổi của ta hoàn toàn được định nghĩa bởi các đích cụ thể của bốn điểm nguồn.
Như đề cập trước, cho các biến đổi phối cảnh ta phải cấp phát một 3-by-3 array cho map_matrix; xem

Example 6-3 cho mã mẫu. Khác hơn 3-by-3 matrix và việc dịch từ ba thành bốn điểm điều khiển, biến đổi
phối cảnh ngược lại là chính xác tương tự với biến đổi affine ta đã giới thiệu.
Example 6-3. Code for perspective transformation
// Usage: warp <image>
//
#include <cv.h>
#include <highgui.h>
int main(int argc, char** argv) {
CvPoint2D32f srcQuad[4], dstQuad[4];
CvMat* warp_matrix = cvCreateMat(3,3,CV_32FC1);
IplImage *src, *dst;
if( argc == 2 && (( dst = cvCloneImage(src);
dst->origin = src->origin;
cvZero(dst);
srcQuad[0].x = 0; //src Top left
srcQuad[0].y = 0;
srcQuad[1].x = src->width - 1; //src Top right
srcQuad[1].y = 0;
srcQuad[2].x = 0; //src Bottom left
srcQuad[2].y = src->height - 1;
srcQuad[3].x = src->width – 1; //src Bot right
srcQuad[3].y = src->height - 1;
dstQuad[0].x = src->width*0.05; //dst Top left
dstQuad[0].y = src->height*0.33;
dstQuad[1].x = src->width*0.9; //dst Top right
dstQuad[1].y = src->height*0.25;
dstQuad[2].x = src->width*0.2; //dst Bottom left
dstQuad[2].y = src->height*0.7;
dstQuad[3].x = src->width*0.8; //dst Bot right
dstQuad[3].y = src->height*0.9;

cvGetPerspectiveTransform(
srcQuad,
dstQuad,
warp_matrix
);
cvWarpPerspective( src, dst, warp_matrix );
cvNamedWindow( “Perspective_Warp”, 1 );
cvShowImage( “Perspective_Warp”, dst );
cvWaitKey();
}
cvReleaseImage(&dst);
cvReleaseMat(&warp_matrix);
return 0;
}
}
Sparse perspective transformations
Có một function đặc biệt, cvPerspectiveTransform(), mà thực hiện các biến đổi phối cảnh trên danh sách các
điểm; ta không thể dùng cvTransform(), mà bị giới hạn với các tác vụ tuyến tính. Như thế, nó không thể
handle các biến đổi phối cảnh vì chúng đòi hỏi chia tọa độ thứ ba của biểu diễn homogeneous (x = f *X/Z, y
= f *Y/Z). Function đặc biệt cvPerspectiveTransform() nhận chăm sóc điều này cho ta.
void cvPerspectiveTransform(
const CvArr* src,
CvArr* dst,
const CvMat* mat
);
Như bình thường, các tham số src và dst là (tương ứng) array của các điểm nguồn được biến đổi và array của
các điểm đích; những arrays này nên là kiểu three-channel, floating-point. Matrix mat có thể là một trong
matrix 3-by-3 hay 4-by-4. Nếu nó là 3-by-3 thì phép chiếu là từ hai chiều thành hai; nếu matrix là 4-by-4,
thì phép chiếu từ bốn chiều thành ba. Trong hoàn cảnh hiện tại ta đang biến đổi một tập các điểm trong một
image thành một tập khác các điểm trong một image, mà nghe như phép chiếu từ hai chiều thành hai chiều.

Nhưng điều này không chính xác đúng, vì biến đổi phối cảnh thực sự là chiếu các điểm vào một mặt phẳng
hai chiều nhúng trong không gian ba chiều lui xuống thành một không gian phụ hai chiều (khác). Nghĩ về
điều này chỉ như với một camera làm (ta sẽ trở lại chủ đề này chi tiết hơn khi thảo luận các camera trong
các chương sau). Camera lấy các điểm theo ba chiều và chiếu chúng thành hai chiều của camera imager.
Điều này thực sự là cái được ý nghĩa khi các điểm nguồn được lấy để ở trong “tọa độ homogeneous”. Ta
đang thêm một chiều phụ cho các điểm này bởi giới thiệu chiều Z và sau đó setting tất cả các giá trị Z thành
1. Biến đổi chiếu sau đó chiếu ngược ra không gian đó vào không gian hai chiều của output của ta. Điều
này là cách dài dòng hơn của giải thích tại sao, khi chiếu các điểm trong một ảnh vào một cái khác, bạn sẽ
cần một 3-by-3 matrix.
Output của code trong Example 6-3 được thấy trong Figure 6-14 cho các biến đổi affine và phối cảnh. So
sánh điều này với các sơ đồ của Figure 6-13 thấy cách điều này làm việc với các ảnh thực. Trong Figure 6-
14, ta biến đổi toàn bộ ảnh. Đây là không cần thiết; ta có thể dùng src_pts để định nghĩa vùng nhỏ hơn (hay
lớn hơn!) trong ảnh nguồn được biến đổi. Ta có thể cũng dùng các ROI trong ảnh source hay đích để giới
hạn biến đổi.
CartToPolar và PolarToCart
Các function cvCartToPolar() và cvPolarToCart() được thực hiện bởi các routine phức tạp hơn
nhiều chẳng hạn cvLogPolar() (mô tả sau) nhưng cũng hữu ích theo quyền riêng của chúng. Những
function này chiếu các số lui và tới giữa một không gian Cartesian (x, y) và không gian cực hay radial (r, θ)
(chẳng hạn từ tọa độ Cartesian thành polar và ngược lại). Các định dạng function là như sau:
void cvCartToPolar(
const CvArr* x,
const CvArr* y,
CvArr* magnitude,
CvArr* angle = NULL,
int angle_in_degrees = 0
);
void cvPolarToCart(
const CvArr* magnitude,
const CvArr* angle,
CvArr* x,

CvArr* y,
int angle_in_degrees = 0
);
Figure 6-14. Perspective và affine mapping of một image
Trong mỗi những function này, hai array hai ảnh hai chiều đầu tiên là input và hai thứ hai là các output.
Nếu một output pointer được đặt thành NULL thì nó sẽ không được tính. Các yêu cầu trên những arrays này
là chúng là float hay doubles và thỏa mãn (size, số kênh, và type). Parameter cuối chỉ định có hay không ta
đang làm việc với các góc theo độ (0, 360) hay theo radians (0, 2π).
Cho một ví dụ nơi bạn có thể dùng function này, giả sử bạn đã lấy các đạo hàm x- và y-của một image, một
trong by dùng cvSobel() hay bởi dùng các convolution function qua cvDFT () hay cvFilter2D(). Nếu bạn lưu
các đạo hàm x- trong một image dx_img và đạo hàm y-trong dy_img, bạn có thể bây giờ tạo một histogram
nhận diện cạnh-góc. Mà là, bạn có thể thu thập tất cả các góc cung cấp biên độ hay sức mạnh của edge
pixel trên một threshold nào đó. Để tính điều này, ta tạo hai ảnh đích cùng kiểu (integer hay float) như các
ảnh đạo hàm và gọi chúng là img_mag và img_angle. Nếu bạn muốn kết quả được cho theo độ, thì bạn có thể
dùng function cvCartToPolar( dx_img, dy_img, img_mag, img_angle, 1 ). Ta sau đó điền histogram từ img_angle
dài bằng “pixel” tương ứng trong img_mag ở trên threshold đó.
LogPolar
Cho các ảnh hai chiều, biến đổi log-polar [Schwartz80] là một thay đổi từ tọa độ Cartesian thành polar:
θ
i
reyx ↔),(
trong đó
22
yxr +=

))/arctan(exp()exp( xyii ⋅=
θ
. Để phân các tọa độ cực
thành một không gian (ρ, θ) mà liên quan với vài điểm tâm (xc, yc), ta lấy log sao cho
))()(log(

22
cc
yyxx −+−=
ρ

))/()arctan((
cc
xxyy −−=
θ
Cho các mục đích ảnh—
khi ta cần “làm vừa” điều thú vị vào image memory sẵn có—ta điển hình áp dụng một scaling factor m cho
ρ. Figure 6-15 cho thấy một đối tượng vuông bên trái và encoding của nó trong không gian log-polar.
Figure 6-15. Biến đổi log-polar chiếu (x, y) thành (log(r),θ); ở đây, một hình vuông được hiển thị trong hệ tọa độ log-
polar
Câu hỏi tiếp theo là, dĩ nhiên, “Sao chán thế?” Biến đổi log-polar lấy cảm hứng từ hệ thống nhìn con người.
Mắt bạn nhỏ nhưng tâm dày đặc các tế bào cảm quang trong tâm của nó (fovea), và mật độ các receptors
rơi xuống nhanh chóng (lũy thừa) từ đó. Cố bắt đầu ở một đốm trên tường và giữ ngón tay ở chiều dài cánh
tay trong đường thấy của bạn. Sau đó, giữ bắt đầu ở đốm và di chuyển chậm ngón tay ra khỏi; lưu ý cách
chi tiết giảm nhanh khi ảnh của ngón tay ra khỏi fovea. Structure này cũng có các thuộc tính toán học hay
nào đó (nằm ngoài phạm vi sách này) mà liên quan bảo trì các góc của các giao điểm đường.
Quan trọng hơn cho ta là biến đổi log-polar có thể được dùng để tạo các biểu diễn bất biến hai chiều của
các view đối tượng bởi dịch tâm của ảnh biến đổi của mass thành một điểm cố định trong mặt phẳng log-
polar; xem Figure 6-16. Bên trái là ba hình mà ta muốn nhận biết như “hình vuông”. Vấn đề là, chúng trông
rất khác. Một cái lớn hơn nhiều những cái khác và một cái khác bị quay. Biến đổi log-polar xuất hiện bên
phải trong Figure 6-16. Nhận thấy rằng các khác biệt size trong mặt phẳng (x, y) được chuyển thành các
dịch chuyển với trục log(r) của mặt phẳng log-polar plane và mà các khác biệt quay được chuyển thành
dịch chuyển dọc trục θ- trong mặt phẳng log-polar. Nếu ta lấy tâm biến đổi của mỗi hình vuông được biến
đổi trong mặt phẳng log-polar và thì định tâm lại điểm đó đến vị trí cố định nào đó, thì tất cả các hình
vuông sẽ được thấy đồng nhất trong mặt phẳng logpolar. Điều này sản ra một kiểu bất biến cho quay và tỉ
lệ hai chiều.*

Figure 6-16. Biến đổi log-polar của các hình vuông bị quay và tỉ lệ: size chuyển thành dịch chuyển trên trục log(r) và
quay chuyển thành trục θ-
OpenCV function cho biến đổi log-polar là cvLogPolar():
void cvLogPolar(
const CvArr* src,
CvArr* dst,
CvPoint2D32f center,
double m,
int flags = CV_INTER_LINEAR | CV_WARP_FILL_OUTLIERS
);
src và dst là ảnh màu hay xám một hay ba chiều. Parameter center là điểm tâm (xc, yc) của biến đổi log-polar;
m là hệ số tỉ lệ, mà nên được đặt sao cho các khả năng ưu thế mong muốn vùng ảnh sẵn có. flags parameter
cho phép các phương pháp nội suy khác nhau. Phương pháp nội suy là cùng tập các nội suy chuẩn sẵn trong
OpenCV (Table 6-1). Các phương pháp nội suy có thể được kết hợp bằng một trong hay cả hai của các flag
CV_WARP_FILL_OUTLIERS (để điền các điểm mà ngược lại sẽ không được định nghĩa) hay
CV_WARP_INVERSE_MAP (để tính chiếu ngược từ tọa độ log-polar thành Cartesian).
Mã mẫu log-polar được cho trong Example 6-4, mà giới thiệu biến đổi log-polar tới và lui (ngược). Các kết
quả trên ảnh photographic được thấy trong Figure 6-17.
Figure 6-17. Ví dụ Log-polar trên con nai sừng tấm với biến đổi tâm ở vòng tròn trắng bên trái;
output bên phải
Example 6-4. Log-polar transform example
// logPolar.cpp : định nghĩa the entry point for the console application.
//
#include <cv.h>
#include <highgui.h>
int main(int argc, char** argv) {
IplImage* src;
double M;
if( argc == 3 && ((src=cvLoadImage(argv[1],1)) != 0 )) {
M = atof(argv[2]);

IplImage* dst = cvCreateImage( cvGetSize(src), 8, 3 );
IplImage* src2 = cvCreateImage( cvGetSize(src), 8, 3 );
cvLogPolar(
src,
dst,
cvPoint2D32f(src->width/4,src->height/2),
M,
CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS
);
cvLogPolar(
dst,
src2,
cvPoint2D32f(src->width/4, src->height/2),
M,
CV_INTER_LINEAR | CV_WARP_INVERSE_MAP
);
cvNamedWindow( “log-polar”, 1 );
cvShowImage( “log-polar”, dst );
cvNamedWindow( “inverse log-polar”, 1 );
cvShowImage( “inverse log-polar”, src2 );
cvWaitKey();
}
return 0;
}
Discrete Fourier Transform (DFT )
Cho bất kỳ tập giá trị mà được index bởi một tham số rời rạc (integer), có khả năng định nghĩa một discrete
Fourier transform (DFT )* mà một cách tương tự với Fourier transform của một function liên tục. Cho N
số phức
10
, ,

−N
xx
, DFT một chiều được định nghĩa bởi công thức sau (trong đó
1−=i
):


=
−=−=
1
0
1, ,0),
2
exp(
N
n
nk
Nkkn
N
i
xf
π
Một biến đổi tương tự có thể được định nghĩa cho một array hai chiều của các số (dĩ nhiên tương tự chiều
cao hơn cũng tồn tại):
∑ ∑

=

=
−−=

1
0
1
0
)
2
exp()
2
exp(
x
x
y
y
yxyx
N
n
N
n
yy
y
xx
x
nnkk
nk
N
i
nk
N
i
yxf

ππ
Nhìn chung, cái có thể mong đợi rằng tính toán của N phần khác nhau fk sẽ đòi hỏi các tác vụ O(N 2). Thật
ra, có một số thuật toán fast Fourier transform (FFT) có khả năng tiinhs những giá trị này trong O(N log N)
thời gian. OpenCV function cvDFT() thực hiện một thuật toán FFT như thế. Function cvDFT() có thể tính
FFTs cho các array một và hai chiều của các input. Trong trường hợp sau, biến đổi hai chiều có thể được
tính hay, nếu mong đợi, chỉ các biến đổi một chiều cho mỗi hàng riêng có thể được tính (tác vụ này nhanh
hơn gọi cvDFT () nhiều lần).
void cvDFT (
const CvArr* src,
CvArr* dst,
int flags,
int nonzero_rows = 0
);
Các aray input và output phải là kiểu floating-point và có thể là array single- hay double-channel. Trong
trường hợp single-channel, các entry được giả thiết là các số thực và output sẽ được nhóm theo định dạng
tiết kiện không gian đặc biệt (thừa kế từ cùng thư viện IPL cũ hơn như IplImage structure). Nếu source và
channel là các matrix hay images hai channel, thì hai channels sẽ được hiểu như các thành phần thực và ảo
của input data. Trong trường hợp này, sẽ không có gói đặc biệt trong các kết quả, và vài không gian sẽ bị
phí với nhiều số 0 trong cả input và output arrays.*
Việc gói đặc biệt các giá trị kết quả mà được dùng với single-channel output là như sau. Cho một one-
dimensional array:
Re Y0 Re Y1 Im Y1 Re Y2 Im Y2 … Re Y(N/2–1) Im Y(N/2–1) Re Y(N/2)
Cho một two-dimensional array:
Re Y00 Re Y01 Im Y01 Re Y02 Im Y02 … Re Y0(Nx/2–1) Im
Y0(Nx/2–1)
Re Y0(Nx/2)
Re Y10 Re Y11 Im Y11 Re Y12 Im Y12 … Re Y1(Nx/2–1) Im
Y1(Nx/2–1)
Re Y1(Nx/2)
Re Y20 Re Y21 Im Y21 Re Y22 Im Y22 … Re Y2(Nx/2–1) Im

Y2(Nx/2–1)
Re Y2(Nx/2)
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Re Y(Ny/2–1)0 Re Y(Ny–3)1 Im Y(Ny–

3)1
Re Y(Ny–
3)2 Im
Y(Ny–3)2


Re Y(Ny–3)(Nx/2–1) Im Y(Ny–3)
(Nx/2–1)
Re Y(Ny–3)
(Nx/2)
Im Y(Ny/2–1)0 Re Y(Ny–2)1 Im Y(Ny–
2)1
Re Y(Ny–
2)2 Im
Y(Ny–2)2 … Re Y(Ny–2)(Nx/2–1) Im Y(Ny–2)
(Nx/2–1)
Re Y(Ny–2)
(Nx/2)
Re Y(Ny/2)0 Re Y(Ny–1)1 Im Y(Ny–
1)1
Re Y(Ny–
1)2
Im Y(Ny–
1)2


Re Y(Ny–1)(Nx/2–1) Im Y(Ny–1)
(Nx/2–1)
Re Y(Ny–1)
(Nx/2)

Đáng mất một ít thời gian để quan sát gần các inex trong những array này. Vấn đề ở đây mà các giá trị nào
đó được đảm bảo là 0 (chính xác hơn, các giá trị nào đó của fk được đảm bảo là thực). Nên được lưu ý rằng
hàng cuối kê trong table này sẽ có mặt chỉ nếu Ny là chẵn và cột cuối sẽ có mặt chỉ nếu Nx là chắn. (Trong
trường hợp của 2D array được xem như các Ny 1D array hơn một biến đổi 2D đầy đủ, tất cả các hàng kết
quả sẽ tương tự với hàng đơn được kê cho output của 1D array).
Argument thứ ba, gọi là flags, nhận biết chính xác tác vụ mà được làm. Biến đổi ta bắt đầu với điều này
được biết là forward transform và được chọn bởi flag CV_DXT_FORWARD. Biến đổi ngược * được định
nghĩa chính xác theo cùng cách ngoại trừ cho thay đổi dấu trong lũy thừa và một scale factor. Để thực hiện
biến đổi ngược không có scale factor, dùng flag CV_DXT_INVERSE. Flag cho scale factor là
CV_DXT_SCALE, và các kết quả này trong tất cả output được scaled bởi một factor 1/N (hay 1/Nx Ny cho
một biến đổi 2D). Việc tỉ lệ này cần thiết nếu ứng dụng liên tiếp của biến đổi thuận và biến đổi ngược mang
chúng quay lại nơi ta bắt đầu. Vì một cái thường muốn kết hợp CV_DXT_INVERSE với CV_DXT_SCALE,
có vài ghi chú thuận tiện cho kiểu này của tác vụ. Thêm vào đó chỉ kết hợp hai tác vụ bằng OR, bạn có thể
dùng CV_DXT_INV_SCALE (hay CV_DXT_INVERSE_SCALE nếu bạn không vào điều tóm tắt đó). Flag cuối
bạn có thể muốn có trong tay là CV_DXT_ROWS, mà cho phép bạn bảo cvDFT () đối xử một two-
dimensional array như một tập các array một chiều mà mỗi cái sẽ được biến đổi riêng như chúng là các Ny

Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Tải bản đầy đủ ngay
×