Các lớp và mẫu trong thư viện MQL5
Mặc dù việc xuất và nhập các lớp và mẫu nói chung bị cấm, nhà phát triển có thể vượt qua những hạn chế này bằng cách chuyển mô tả của các giao diện cơ sở trừu tượng vào tệp tiêu đề của thư viện và truyền các con trỏ. Hãy minh họa khái niệm này bằng một ví dụ về thư viện thực hiện biến đổi Hough của một hình ảnh.
Biến đổi Hough là một thuật toán để trích xuất các đặc trưng của hình ảnh bằng cách so sánh nó với một mô hình chính thức (công thức) được mô tả bởi một tập hợp các tham số.
Biến đổi Hough đơn giản nhất là việc chọn các đường thẳng trên hình ảnh bằng cách chuyển đổi chúng sang tọa độ cực. Với quá trình xử lý này, các chuỗi pixel "được điền", sắp xếp ít nhiều thành một hàng, tạo thành các đỉnh trong không gian tọa độ cực tại giao điểm của một góc cụ thể ("theta") của độ nghiêng của đường thẳng và độ dịch chuyển của nó ("ro") so với tâm tọa độ.
Biến đổi Hough cho các đường thẳng
Mỗi trong ba chấm màu trên hình ảnh ban đầu (bên trái) để lại một dấu vết trong không gian tọa độ cực (bên phải) vì một số lượng vô hạn các đường thẳng có thể được vẽ qua một điểm ở các góc khác nhau và vuông góc với tâm. Mỗi đoạn dấu vết được "đánh dấu" chỉ một lần, ngoại trừ dấu đỏ: tại điểm này, cả ba dấu vết giao nhau và cho phản hồi tối đa (3). Thật vậy, như chúng ta có thể thấy trong hình ảnh ban đầu, có một đường thẳng đi qua cả ba điểm. Do đó, hai tham số của đường thẳng được tiết lộ bởi giá trị tối đa trong tọa độ cực.
Chúng ta có thể sử dụng biến đổi Hough này trên biểu đồ giá để làm nổi bật các đường hỗ trợ và kháng cự thay thế. Nếu các đường như vậy thường được vẽ tại các cực trị riêng lẻ và thực tế thực hiện phân tích các giá trị ngoại lai, thì các đường biến đổi Hough có thể xem xét tất cả giá High
hoặc tất cả giá Low
, hoặc thậm chí phân bố khối lượng tick trong các thanh. Tất cả điều này cho phép bạn có được một ước lượng hợp lý hơn về các mức.
Hãy bắt đầu với tệp tiêu đề LibHoughTransform.mqh
. Vì một số hình ảnh trừu tượng cung cấp dữ liệu ban đầu để phân tích, hãy định nghĩa giao diện mẫu HoughImage
.
template<typename T>
interface HoughImage
{
virtual int getWidth() const;
virtual int getHeight() const;
virtual T get(int x, int y) const;
};
2
3
4
5
6
7
Tất cả những gì bạn cần biết về hình ảnh khi xử lý nó là kích thước của nó và nội dung của mỗi pixel, mà vì lý do tổng quát, được biểu diễn bởi kiểu tham số T. Rõ ràng rằng trong trường hợp đơn giản nhất, nó có thể là int
hoặc double
.
Việc gọi xử lý hình ảnh phân tích phức tạp hơn một chút. Trong thư viện, chúng ta cần mô tả lớp mà các đối tượng của nó sẽ được trả về từ một hàm nhà máy đặc biệt (dưới dạng con trỏ). Chính hàm này nên được xuất từ thư viện. Giả sử, nó như sau:
template<typename T>
class HoughTransformDraft
{
public:
virtual int transform(const HoughImage<T> &image, double &result[],
const int elements = 8) = 0;
};
HoughTransformDraft<?> *createHoughTransform() export { ... } // Vấn đề - mẫu!
2
3
4
5
6
7
8
9
Tuy nhiên, các kiểu mẫu và hàm mẫu không thể được xuất. Do đó, chúng ta sẽ tạo một lớp trung gian không phải mẫu HoughTransform
, trong đó chúng ta sẽ thêm một phương thức mẫu cho tham số hình ảnh. Thật không may, các phương thức mẫu không thể là ảo, và do đó chúng ta sẽ phân phối các lệnh gọi thủ công bên trong phương thức (sử dụng dynamic_cast
), chuyển hướng xử lý sang một lớp dẫn xuất với phương thức ảo.
class HoughTransform
{
public:
template<typename T>
int transform(const HoughImage<T> &image, double &result[],
const int elements = 8)
{
HoughTransformConcrete<T> *ptr = dynamic_cast<HoughTransformConcrete<T> *>(&this);
if(ptr) return ptr.extract(image, result, elements);
return 0;
}
};
template<typename T>
class HoughTransformConcrete: public HoughTransform
{
public:
virtual int extract(const HoughImage<T> &image, double &result[],
const int elements = 8) = 0;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Việc triển khai nội bộ của lớp HoughTransformConcrete
sẽ được ghi vào tệp thư viện MQL5/Libraries/MQL5Book/LibHoughTransform.mq5
.
#property library
#include <MQL5Book/LibHoughTransform.mqh>
template<typename T>
class LinearHoughTransform: public HoughTransformConcrete<T>
{
protected:
int size;
public:
LinearHoughTransform(const int quants): size(quants) { }
...
};
2
3
4
5
6
7
8
9
10
11
12
13
14
Vì chúng ta sẽ tính toán lại các điểm hình ảnh vào không gian mới, tọa độ cực, một kích thước nhất định nên được phân bổ cho nhiệm vụ. Ở đây chúng ta đang nói về một biến đổi Hough rời rạc vì chúng ta coi hình ảnh ban đầu là một tập hợp rời rạc các điểm (pixel), và chúng ta sẽ tích lũy các giá trị của góc với các đường vuông góc trong các ô (quanta). Để đơn giản, chúng ta sẽ tập trung vào biến thể với không gian hình vuông, nơi số lượng phép đọc cả về góc và khoảng cách đến tâm là bằng nhau. Tham số này được truyền vào hàm tạo của lớp.
template<typename T>
class LinearHoughTransform: public HoughTransformConcrete<T>
{
protected:
int size;
Plain2DArray<T> data;
Plain2DArray<double> trigonometric;
void init()
{
data.allocate(size, size);
trigonometric.allocate(2, size);
double t, d = M_PI / size;
int i;
for(i = 0, t = 0; i < size; i++, t += d)
{
trigonometric.set(0, i, MathCos(t));
trigonometric.set(1, i, MathSin(t));
}
}
public:
LinearHoughTransform(const int quants): size(quants)
{
init();
}
...
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Để tính toán số liệu thống kê "dấu chân" do các pixel "được điền" để lại trong không gian kích thước biến đổi với kích thước size
x size
, chúng ta mô tả mảng data
. Lớp mẫu trợ giúp Plain2DArray
(với tham số kiểu T) cho phép mô phỏng một mảng hai chiều với kích thước bất kỳ. Cùng lớp đó nhưng với tham số kiểu double
được áp dụng cho bảng trigonometric
chứa các giá trị được tính trước của sin và cosin của các góc. Chúng ta sẽ cần bảng này để ánh xạ nhanh các pixel sang không gian mới.
Phương thức để phát hiện các tham số của các đường thẳng nổi bật nhất được gọi là extract
. Nó nhận một hình ảnh làm đầu vào và phải điền mảng đầu ra result
với các cặp tham số của các đường thẳng được tìm thấy. Trong phương trình sau:
y = a * x + b
tham số a
(độ nghiêng, "theta") sẽ được ghi vào các số chẵn của mảng result
, và tham số b
(độ thụt, "ro") sẽ được ghi vào các số lẻ của mảng. Ví dụ, đường thẳng đáng chú ý nhất đầu tiên sau khi hoàn thành phương thức được mô tả bởi biểu thức:
y = result[0] * x + result[1];
Đối với đường thẳng thứ hai, các chỉ số sẽ tăng lên 2 và 3, tương ứng, và cứ tiếp tục như vậy, cho đến số lượng đường tối đa được yêu cầu (lines
). Kích thước mảng result
bằng hai lần số lượng đường.
template<typename T>
class LinearHoughTransform: public HoughTransformConcrete<T>
{
...
virtual int extract(const HoughImage<T> &image, double &result[],
const int lines = 8) override
{
ArrayResize(result, lines * 2);
ArrayInitialize(result, 0);
data.zero();
const int w = image.getWidth();
const int h = image.getHeight();
const double d = M_PI / size; // 180 / 36 = 5 độ, ví dụ
const double rstep = MathSqrt(w * w + h * h) / size;
...
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Các vòng lặp lồng nhau qua các pixel hình ảnh được tổ chức trong khối tìm kiếm đường thẳng. Đối với mỗi điểm "được điền" (khác không), một vòng lặp qua các độ nghiêng được thực hiện, và các cặp tọa độ cực tương ứng được đánh dấu trong không gian biến đổi. Trong trường hợp này, chúng ta chỉ cần gọi phương thức để tăng nội dung của ô bằng giá trị do pixel trả về: data.inc((int)r, i, v)
, nhưng tùy thuộc vào ứng dụng và kiểu T, nó có thể yêu cầu xử lý phức tạp hơn.
double r, t;
int i;
for(int x = 0; x < w; x++)
{
for(int y = 0; y < h; y++)
{
T v = image.get(x, y);
if(v == (T)0) continue;
for(i = 0, t = 0; i < size; i++, t += d) // t < Math.PI
{
r = (x * trigonometric.get(0, i) + y * trigonometric.get(1, i));
r = MathRound(r / rstep); // phạm vi [-range, +range]
r += size; // [0, +2size]
r /= 2;
if((int)r < 0) r = 0;
if((int)r >= size) r = size - 1;
if(i < 0) i = 0;
if(i >= size) i = size - 1;
data.inc((int)r, i, v);
}
}
}
...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Trong phần thứ hai của phương thức, việc tìm kiếm các giá trị tối đa trong không gian mới được thực hiện và mảng đầu ra result
được điền.
for(i = 0; i < lines; i++)
{
int x, y;
if(!findMax(x, y))
{
return i;
}
double a = 0, b = 0;
if(MathSin(y * d) != 0)
{
a = -1.0 * MathCos(y * d) / MathSin(y * d);
b = (x * 2 - size) * rstep / MathSin(y * d);
}
if(fabs(a) < DBL_EPSILON && fabs(b) < DBL_EPSILON)
{
i--;
continue;
}
result[i * 2 + 0] = a;
result[i * 2 + 1] = b;
}
return i;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Phương thức trợ giúp findMax
(xem mã nguồn) ghi tọa độ của giá trị tối đa trong không gian mới vào các biến x
và y
, đồng thời ghi đè lên vùng lân cận của vị trí này để không tìm thấy nó lặp lại.
Lớp LinearHoughTransform
đã sẵn sàng, và chúng ta có thể viết một hàm nhà máy có thể xuất để tạo ra các đối tượng.
HoughTransform *createHoughTransform(const int quants,
const ENUM_DATATYPE type = TYPE_INT) export
{
switch(type)
{
case TYPE_INT:
return new LinearHoughTransform<int>(quants);
case TYPE_DOUBLE:
return new LinearHoughTransform<double>(quants);
...
}
return NULL;
}
2
3
4
5
6
7
8
9
10
11
12
13
Vì các mẫu không được phép xuất, chúng ta sử dụng liệt kê ENUM_DATATYPE
trong tham số thứ hai để thay đổi kiểu dữ liệu trong quá trình chuyển đổi và trong biểu diễn hình ảnh gốc.
Để kiểm tra việc xuất/nhập các cấu trúc, chúng ta cũng đã mô tả một cấu trúc chứa siêu thông tin về phép biến đổi trong một phiên bản nhất định của thư viện và xuất một hàm trả về cấu trúc như vậy.
struct HoughInfo
{
const int dimension; // số lượng tham số trong công thức mô hình
const string about; // mô tả bằng lời
HoughInfo(const int n, const string s): dimension(n), about(s) { }
HoughInfo(const HoughInfo &other): dimension(other.dimension), about(other.about) { }
};
HoughInfo getHoughInfo() export
{
return HoughInfo(2, "Line: y = a * x + b; a = p[0]; b = p[1];");
}
2
3
4
5
6
7
8
9
10
11
12
Các biến thể khác nhau của phép biến đổi Hough có thể phát hiện không chỉ các đường thẳng mà còn các cấu trúc khác tương ứng với một công thức phân tích đã cho (ví dụ, đường tròn). Những biến thể như vậy sẽ tiết lộ số lượng tham số khác nhau và mang ý nghĩa khác nhau. Việc có một hàm tự ghi chú có thể giúp việc tích hợp thư viện dễ dàng hơn (đặc biệt khi có nhiều thư viện; lưu ý rằng tệp tiêu đề của chúng ta chỉ chứa thông tin chung liên quan đến bất kỳ thư viện nào triển khai giao diện biến đổi Hough này, không chỉ dành riêng cho đường thẳng).
Tất nhiên, ví dụ này về việc xuất một lớp với một phương thức công khai duy nhất có phần tùy ý vì có thể xuất trực tiếp hàm biến đổi. Tuy nhiên, trong thực tế, các lớp thường chứa nhiều chức năng hơn. Đặc biệt, dễ dàng thêm vào lớp của chúng ta việc điều chỉnh độ nhạy của thuật toán, lưu trữ các mẫu ví dụ từ các đường để phát hiện tín hiệu được kiểm tra trên lịch sử, v.v.
Hãy sử dụng thư viện trong một chỉ báo tính toán các đường hỗ trợ và kháng cự bằng giá High
và Low
trên một số lượng thanh nhất định. Nhờ biến đổi Hough và giao diện lập trình, thư viện cho phép hiển thị một số đường quan trọng nhất như vậy.
Mã nguồn của chỉ báo nằm trong tệp MQL5/Indicators/MQL5Book/p7/LibHoughChannel.mq5
. Nó cũng bao gồm tệp tiêu đề LibHoughTransform.mqh
, nơi chúng ta đã thêm chỉ thị nhập.
#import "MQL5Book/LibHoughTransform.ex5"
HoughTransform *createHoughTransform(const int quants,
const ENUM_DATATYPE type = TYPE_INT);
HoughInfo getHoughInfo();
#import
2
3
4
5
Trong hình ảnh được phân tích, chúng ta biểu thị bằng các pixel vị trí của các loại giá cụ thể (OHLC) trong báo giá. Để triển khai hình ảnh, chúng ta cần mô tả lớp HoughQuotes
được dẫn xuất từ HoughImage<int>
.
Chúng ta sẽ cung cấp việc "vẽ" pixel theo nhiều cách: bên trong thân nến, bên trong toàn bộ phạm vi của nến, cũng như trực tiếp tại các mức cao và thấp. Tất cả điều này được chính thức hóa trong liệt kê PRICE_LINE
. Hiện tại, chỉ báo sẽ chỉ sử dụng HighHigh
và LowLow
, nhưng điều này có thể được đưa ra trong cài đặt.
class HoughQuotes: public HoughImage<int>
{
public:
enum PRICE_LINE
{
HighLow = 0, // Phạm vi thanh |High..Low|
OpenClose = 1, // Thân thanh |Open..Close|
LowLow = 2, // Các mức thấp của thanh
HighHigh = 3, // Các mức cao của thanh
};
...
2
3
4
5
6
7
8
9
10
11
Trong các tham số hàm tạo và biến nội bộ, chúng ta chỉ định phạm vi thanh để phân tích. Số lượng thanh size
xác định kích thước ngang của hình ảnh. Để đơn giản, chúng ta sẽ sử dụng cùng số lượng phép đọc theo chiều dọc. Do đó, bước phân cấp giá (step
) bằng phạm vi giá thực tế (pp
) cho các thanh size
chia cho size
. Đối với biến base
, chúng ta tính toán giới hạn dưới của giá được xem xét trong các thanh đã chỉ định. Biến này sẽ cần thiết để liên kết việc xây dựng các đường dựa trên các tham số tìm thấy của biến đổi Hough.
protected:
int size;
int offset;
int step;
double base;
PRICE_LINE type;
public:
HoughQuotes(int startbar, int barcount, PRICE_LINE price)
{
offset = startbar;
size = barcount;
type = price;
int hh = iHighest(NULL, 0, MODE_HIGH, size, startbar);
int ll = iLowest(NULL, 0, MODE_LOW, size, startbar);
int pp = (int)((iHigh(NULL, 0, hh) - iLow(NULL, 0, ll)) / _Point);
step = pp / size;
base = iLow(NULL, 0, ll);
}
...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Hãy nhớ rằng giao diện HoughImage
yêu cầu triển khai 3 phương thức: getWidth
, getHeight
và get
. Hai phương thức đầu tiên thì dễ dàng.
virtual int getWidth() const override
{
return size;
}
virtual int getHeight() const override
{
return size;
}
2
3
4
5
6
7
8
9
Phương thức get
để lấy "pixel" dựa trên báo giá trả về 1 nếu điểm được chỉ định nằm trong phạm vi thanh hoặc ô, theo phương pháp tính toán được chọn từ PRICE_LINE
. Ngược lại, trả về 0. Phương thức này có thể được cải thiện đáng kể bằng cách đánh giá các fractal, các cực trị tăng liên tục, hoặc giá "tròn" với trọng số cao hơn (pixel dày).
virtual int get(int x, int y) const override
{
if(offset + x >= iBars(NULL, 0)) return 0;
const double price = convert(y);
if(type == HighLow)
{
if(price >= iLow(NULL, 0, offset + x) && price <= iHigh(NULL, 0, offset + x))
{
return 1;
}
}
else if(type == OpenClose)
{
if(price >= fmin(iOpen(NULL, 0, offset + x), iClose(NULL, 0, offset + x))
&& price <= fmax(iOpen(NULL, 0, offset + x), iClose(NULL, 0, offset + x)))
{
return 1;
}
}
else if(type == LowLow)
{
if(iLow(NULL, 0, offset + x) >= price - step * _Point / 2
&& iLow(NULL, 0, offset + x) <= price + step * _Point / 2)
{
return 1;
}
}
else if(type == HighHigh)
{
if(iHigh(NULL, 0, offset + x) >= price - step * _Point / 2
&& iHigh(NULL, 0, offset + x) <= price + step * _Point / 2)
{
return 1;
}
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Phương thức trợ giúp convert
cung cấp việc tính toán lại từ tọa độ pixel y sang giá trị giá.
double convert(const double y) const
{
return base + y * step * _Point;
}
};
2
3
4
5
Bây giờ mọi thứ đã sẵn sàng để viết phần kỹ thuật của chỉ báo. Trước hết, hãy khai báo ba biến đầu vào để chọn đoạn cần phân tích và số lượng đường. Tất cả các đường sẽ được xác định bằng một tiền tố chung.
input int BarOffset = 0;
input int BarCount = 21;
input int MaxLines = 3;
const string Prefix = "HoughChannel-";
2
3
4
5
Đối tượng cung cấp dịch vụ biến đổi sẽ được mô tả là toàn cục: đây là nơi hàm nhà máy createHoughTransform
được gọi từ thư viện.
HoughTransform *ht = createHoughTransform(BarCount);
Trong hàm OnInit
, chúng ta chỉ ghi lại mô tả của thư viện bằng cách sử dụng hàm nhập thứ hai getHoughInfo
.
int OnInit()
{
HoughInfo info = getHoughInfo();
Print(info.dimension, " per ", info.about);
return INIT_SUCCEEDED;
}
2
3
4
5
6
Chúng ta sẽ thực hiện tính toán trong OnCalculate
một lần, khi mở thanh.
int OnCalculate(const int rates_total,
const int prev_calculated,
const int begin,
const double &price[])
{
static datetime now = 0;
if(now != iTime(NULL, 0, 0))
{
... // xem khối tiếp theo
now = iTime(NULL, 0, 0);
}
return rates_total;
}
2
3
4
5
6
7
8
9
10
11
12
13
Việc tính toán biến đổi được chạy hai lần trên một cặp hình ảnh (highs
và lows
) được hình thành bởi các loại giá khác nhau. Trong trường hợp này, công việc được thực hiện tuần tự bởi cùng một đối tượng ht
. Nếu việc phát hiện các đường thẳng thành công, chúng ta hiển thị chúng trên biểu đồ bằng hàm DrawLine
. Vì các đường được liệt kê trong mảng kết quả theo thứ tự giảm dần về tầm quan trọng, các đường được gán trọng số giảm dần.
HoughQuotes highs(BarOffset, BarCount, HoughQuotes::HighHigh);
HoughQuotes lows(BarOffset, BarCount, HoughQuotes::LowLow);
static double result[];
int n;
n = ht.transform(highs, result, fmin(MaxLines, 5));
if(n)
{
for(int i = 0; i < n; ++i)
{
DrawLine(highs, Prefix + "Highs-" + (string)i,
result[i * 2 + 0], result[i * 2 + 1], clrBlue, 5 - i);
}
}
n = ht.transform(lows, result, fmin(MaxLines, 5));
if(n)
{
for(int i = 0; i < n; ++i)
{
DrawLine(lows, Prefix + "Lows-" + (string)i,
result[i * 2 + 0], result[i * 2 + 1], clrRed, 5 - i);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Hàm DrawLine
dựa trên các đối tượng đồ họa xu hướng (OBJ_TREND, xem mã nguồn).
Khi hủy khởi tạo chỉ báo, chúng ta xóa các đường và đối tượng phân tích.
void OnDeinit(const int)
{
AutoPtr<HoughTransform> destructor(ht);
ObjectsDeleteAll(0, Prefix);
}
2
3
4
5
Trước khi thử nghiệm một phát triển mới, đừng quên biên dịch cả thư viện và chỉ báo.
Chạy chỉ báo với cài đặt mặc định sẽ cho kết quả như thế này.
Chỉ báo với các đường chính cho giá High/Low dựa trên thư viện biến đổi Hough
Trong trường hợp của chúng ta, thử nghiệm đã thành công. Nhưng nếu bạn cần gỡ lỗi thư viện thì sao? Không có công cụ tích hợp nào cho việc này, vì vậy có thể sử dụng mẹo sau. Mã nguồn thử nghiệm của thư viện được biên dịch có điều kiện thành phiên bản gỡ lỗi của sản phẩm, và sản phẩm được thử nghiệm với thư viện đã xây dựng. Hãy xem xét ví dụ về chỉ báo của chúng ta.
Hãy cung cấp macro LIB_HOUGH_IMPL_DEBUG
để kích hoạt việc tích hợp mã nguồn thư viện trực tiếp vào chỉ báo. Macro này nên được đặt trước khi bao gồm tệp tiêu đề.
#define LIB_HOUGH_IMPL_DEBUG
#include <MQL5Book/LibHoughTransform.mqh>
2
Trong chính tệp tiêu đề, chúng ta sẽ phủ lên khối nhập từ bản sao độc lập nhị phân của thư viện bằng các chỉ thị biên dịch trước có điều kiện. Khi macro được kích hoạt, một nhánh khác sẽ chạy, với câu lệnh #include
.
#ifdef LIB_HOUGH_IMPL_DEBUG
#include "../../Libraries/MQL5Book/LibHoughTransform.mq5"
#else
#import "MQL5Book/LibHoughTransform.ex5"
HoughTransform *createHoughTransform(const int quants,
const ENUM_DATATYPE type = TYPE_INT);
HoughInfo getHoughInfo();
#import
#endif
2
3
4
5
6
7
8
9
Trong tệp mã nguồn thư viện LibHoughTransform.mq5
, bên trong hàm getHoughInfo
, chúng ta thêm đầu ra vào nhật ký thông tin về phương pháp biên dịch, tùy thuộc vào việc macro được kích hoạt hay tắt.
HoughInfo getHoughInfo() export
{
#ifdef LIB_HOUGH_IMPL_DEBUG
Print("inline library (debug)");
#else
Print("standalone library (production)");
#endif
return HoughInfo(2, "Line: y = a * x + b; a = p[0]; b = p[1];");
}
2
3
4
5
6
7
8
9
Nếu trong mã chỉ báo, trong tệp LibHoughChannel.mq5
, bạn bỏ ghi chú chỉ thị #define LIB_HOUGH_IMPL_DEBUG
, bạn có thể kiểm tra từng bước phân tích hình ảnh.