# Macro nhận diện hình ảnh BlueStacks và vòng lặp LDPlayer

> Dựng macro nhận diện hình ảnh cho BlueStacks: cắt ảnh mẫu trong Asset Lab, so khớp trên BlueStacks hoặc LDPlayer, và lặp cho đến khi một trạng thái xuất hiện.

Source: https://automationmacro.com/vi/docs/guides/image-recognition-macros (Hướng dẫn, updated 2026-09-05)

Một macro nhận diện hình ảnh trên BlueStacks hoặc LDPlayer nhìn màn hình trước khi hành động. Macro nhận diện hình ảnh chạm vào nút vì nút đang ở đó, đóng popup vì popup vừa hiện, và dừng khi thông báo "hết năng lượng" xuất hiện. Hướng dẫn này dựng một macro như vậy trong Macro Automation Studio (MAS) trên BlueStacks hoặc LDPlayer, từ mẩu cắt ảnh mẫu đầu tiên đến vòng lặp chờ một trạng thái. Trang dành cho bất kỳ ai đã vượt quá khả năng của trình ghi tích hợp trong giả lập.

## Vì sao trình ghi không rẽ nhánh được

Trình ghi macro trong BlueStacks hoặc LDPlayer lưu cú chạm và thời gian rồi phát lại. Nó không bao giờ nhìn màn hình. Nó không biết popup đã che nút, màn hình tải hôm nay lâu hơn, hay bộ đếm đã về 0. Mọi macro đã ghi đều là một đường thẳng.

Macro MAS là một đoạn mã Python. Mỗi lời gọi `mas.find_object` chụp một ảnh màn hình và tìm ảnh mẫu trong đó bằng so khớp mẫu OpenCV. Khi kết quả khớp tốt nhất đạt ngưỡng (`threshold=0.8` theo mặc định), lời gọi trả về điểm giữa của nó. Nếu không, nó trả về `None`. Chính câu `if` đơn lẻ đó cho phép macro rẽ nhánh, chờ, thử lại và dừng.

## Trước khi bắt đầu

- MAS đã cài và đăng nhập với bản dùng thử hoặc gói. Bản dùng thử bắt đầu từ [bản tải miễn phí](/download); xem [Cài đặt MAS](/docs/install).
- BlueStacks hoặc LDPlayer đang chạy với ADB đã bật và đã thêm làm thiết bị. Xem hướng dẫn [BlueStacks](/docs/bluestacks-setup-guide) hoặc [LDPlayer](/docs/ldplayer-setup-guide).
- Giả lập dùng một độ phân giải cố định mà bạn sẽ giữ. Các hướng dẫn dùng 540x960 ở chế độ dọc và 240 DPI làm chuẩn cơ sở.
- Một dự án Code-Based đang mở trong Code Editor. Xem [Bắt đầu](/docs/getting-started).

## Chụp ảnh mẫu trong Asset Lab

1. Đưa ứng dụng tới màn hình có nút bạn muốn tìm.
2. Trong Code Editor, mở bảng **Tài nguyên** và nhấn **Mở Asset Helper**. Asset Lab mở với màn hình đang chạy của thiết bị.
3. Cắt một hình chữ nhật sát quanh nút và lưu. Mẩu cắt vào Thư viện ảnh của bạn và nhận một ID dạng số.
4. Lặp lại cho mọi trạng thái macro phải nhận ra: nút đóng popup, nút xác nhận, thông báo "xong".
5. Trong bảng **Tài nguyên**, nhấn **Sao chép ID** trên từng ảnh và khai báo chúng ở đầu mã của bạn.

```python
import mas

images = mas.images({
    "play_button": 201,
    "close_popup": 202,
    "out_of_energy": 203,
})
```

`mas.images` cho mỗi ID một tên dễ đọc và cho trình đóng gói biết ảnh nào cần gói khi bạn đăng. Ảnh mẫu có thể là jpg, png, gif hoặc webp, tối đa 10 MB mỗi tệp. Xem [Asset Lab](/docs/asset-lab).

> [!TIP]
> Cắt phần không bao giờ thay đổi. Một nút có con số trên đó chỉ khớp đúng con số ấy; hãy cắt biểu tượng cạnh con số thay vào đó.

## Chọn đúng lời gọi

| Lời gọi | Dùng khi | Trả về |
|---|---|---|
| `find_object(image)` | Bạn muốn nhìn màn hình đúng một lần ngay bây giờ | `ObjectMatch` hoặc `None` |
| `find_object_retry(image, total_tries=3, time_sleep=2.0)` | Thứ cần tìm có thể mất một lúc mới hiện | `ObjectMatch` hoặc `None` sau lần thử cuối |
| `find_any_object([a, b, c])` | Nhiều ảnh mẫu đều chấp nhận được: các biến thể popup, hai chủ đề | Kết quả khớp đầu tiên, kèm `matched_template_id` |
| `find_objects(image, max_matches=10)` | Cùng một biểu tượng xuất hiện nhiều lần và bạn muốn tất cả | Danh sách sắp theo độ tin cậy |

`find_object_retry` là lời gọi chờ chuẩn của nhà: nó gọi `find_object` tối đa `total_tries` lần với khoảng nghỉ `time_sleep` cố định giữa các lần thất bại và không bao giờ ngủ sau lần cuối. Mọi tham số khác (`threshold`, `search_region`, `screenshot`) được chuyển tiếp tới `find_object`. `find_any_object_retry` làm tương tự trên một danh sách. Ưu tiên chúng hơn `wait_for_object`, vốn giữ lại cho các macro cũ.

```python
match = mas.find_object_retry(images.play_button, total_tries=5, time_sleep=1.5)
if match:
    mas.click(match.x, match.y, delay_ms=1000)
else:
    mas.log("Play button did not appear", level="warning")
```

Một `ObjectMatch` có `x`, `y`, `center` và `matched_template_id`. Nó không mang điểm số, nên hãy chỉnh ngưỡng trên lời gọi thay vì đọc độ tin cậy sau đó.

## Thu hẹp phạm vi tìm bằng search_region

So khớp mẫu trượt ảnh mẫu trên toàn bộ ảnh chụp màn hình. Một `Region` giới hạn việc trượt vào một hình chữ nhật, nhanh hơn và tránh các thứ trông giống ở nơi khác trên màn hình. Tọa độ là pixel tính từ góc trên bên trái.

```python
from mas import Region

TOP_BAR = Region(x1=0, y1=0, x2=540, y2=120)

energy_icon = mas.find_object(images.energy_icon, search_region=TOP_BAR)
```

Dùng vùng bất cứ khi nào bạn biết phần tử nằm ở đâu: thanh trên cùng cho bộ đếm, phía dưới cho nút hành động, giữa cho hộp thoại. Đọc vùng từ Asset Lab; bộ chọn điểm hiển thị tọa độ pixel trên màn hình đang chạy.

## Chỉnh ngưỡng

`threshold` chạy từ 0.0 đến 1.0 và mặc định là 0.8. Kết quả khớp phải đạt mức đó mới được tính.

- Hạ xuống 0.7 khi một ảnh mẫu đúng cứ trượt vì khử răng cưa, thay đổi tỷ lệ nhỏ, hoặc hiệu ứng phát sáng trên nút.
- Nâng lên 0.9 khi ảnh mẫu khớp nhầm chỗ, ví dụ hai biểu tượng giống nhau nằm cạnh nhau hoặc một nút cũng xuất hiện ở dạng xám mờ.
- Cắt lại trước khi xuống dưới 0.7. Ngưỡng thấp đến vậy chấp nhận gần như bất cứ thứ gì cùng màu.

```python
enabled = mas.find_object(images.claim_enabled, threshold=0.9)   # strict: disabled and enabled look alike
glowing = mas.find_object(images.reward_icon, threshold=0.7)     # lenient: the icon has a pulsing glow
```

## Xử lý popup bằng find_any_object

Popup là lý do macro đã ghi bị hỏng. Cho macro một hàm biết mọi popup và gọi nó ở đầu mỗi lượt lặp.

```python
POPUPS = [images.close_popup, images.confirm_button, images.later_button]


def clear_popups():
    popup = mas.find_any_object(POPUPS)
    if popup is None:
        return False
    mas.log(f"Closing popup {popup.matched_template_id}")
    mas.click(popup.x, popup.y, delay_ms=800)
    return True
```

`find_any_object` nhận một danh sách và trả về kết quả khớp đầu tiên. `search_strategy="best_match"` thay vào đó chọn điểm cao nhất trong cả danh sách, và `"priority_order"` thử các ảnh mẫu theo thứ tự bạn liệt kê. Trả về `True` cho phép vòng lặp `continue`, nên lượt tiếp theo nhìn vào một màn hình sạch.

## LDPlayer macro loop chờ một trạng thái

Chờ là một vòng lặp có hạn chót. `find_object_retry` bao các lần chờ ngắn; với màn hình tải có thể mất một phút, hãy tự viết vòng lặp để bạn ghi tiến độ và bỏ cuộc gọn gàng.

```python
import time

def wait_for(image, timeout_s=60, every_s=2.0):
    deadline = time.monotonic() + timeout_s
    while time.monotonic() < deadline:
        match = mas.find_object(image)
        if match:
            return match
        time.sleep(every_s)
    return None
```

`time.monotonic()` đếm giây và không bao giờ nhảy khi đồng hồ đổi, nên nó là bộ đếm giờ đúng cho một hạn chót. Mọi lần chờ phải kết thúc: trả về `None` và để bên gọi quyết định có dừng lần chạy hay không.

## Macro nhận diện hình ảnh hoàn chỉnh trên BlueStacks

Mã này mở một màn hình, dọn popup, chạm vào nút khi nút còn bật, và dừng khi ảnh mẫu "hết năng lượng" xuất hiện hoặc hết ngân sách thời gian. Thay ID ảnh bằng ID trong Thư viện ảnh của bạn.

```python
import random
import sys
import time

import mas
from mas import Region

images = mas.images({
    "play_button": 201,
    "close_popup": 202,
    "confirm_button": 203,
    "out_of_energy": 204,
    "home_screen": 205,
})

BOTTOM = Region(x1=0, y1=700, x2=540, y2=960)
TIME_BUDGET_S = 15 * 60


def clear_popups():
    popup = mas.find_any_object([images.close_popup, images.confirm_button])
    if popup is None:
        return False
    mas.click(popup.x, popup.y, delay_ms=800)
    return True


def main():
    home = mas.find_object_retry(images.home_screen, total_tries=10, time_sleep=3.0)
    if home is None:
        mas.log("Home screen never appeared", level="error")
        sys.exit(2)

    started = time.monotonic()
    taps = 0
    while time.monotonic() - started < TIME_BUDGET_S:
        if clear_popups():
            continue
        if mas.find_object(images.out_of_energy):
            mas.log("Out of energy, done")
            break
        button = mas.find_object_retry(images.play_button, total_tries=3, time_sleep=2.0, search_region=BOTTOM)
        if button is None:
            mas.log("Play button not found, looking again", level="warning")
            continue
        mas.click(button.x, button.y, delay_ms=1000)
        taps += 1
        time.sleep(random.uniform(0.8, 2.0))

    mas.log(f"Finished after {taps} taps")


if __name__ == "__main__":
    main()
```

Chạy bằng **Chạy** (<kbd>F5</kbd>) trong Code Editor và theo dõi bảng điều khiển. Khi một ảnh mẫu trượt, sửa mẩu cắt trước và ngưỡng sau.

## Khắc phục sự cố

### Ảnh mẫu không bao giờ khớp

Mẩu cắt được chụp ở độ phân giải hoặc DPI khác với thiết bị đang chạy, mẩu cắt có nền đã thay đổi, hoặc phần tử có hoạt ảnh. Đối chiếu cài đặt màn hình của giả lập với lúc bạn chụp, cắt lại sát hơn, và thử `threshold=0.7`. Nếu phần tử có nhiều kiểu hiển thị, chụp từng kiểu và dùng `find_any_object`. `mas.ImageNotFoundError` nghĩa là ID hoàn toàn không có trong Thư viện ảnh của bạn; sao chép lại từ bảng **Tài nguyên**.

### Khớp nhầm thứ khác

Ảnh mẫu quá chung chung: một mũi tên trơn, một ô vuông một màu, một từ xuất hiện hai lần. Cắt thứ gì đó độc nhất bên cạnh nó, nâng ngưỡng lên 0.9, hoặc truyền `search_region` để việc tìm ở yên chỗ phần tử nằm. `find_objects` cho bạn thấy mọi chỗ ảnh mẫu đạt trên ngưỡng, giúp dễ phát hiện thứ trông giống.

### Chạy được trên một instance, trượt trên instance khác

Instance giả lập thứ hai chạy ở độ phân giải hoặc DPI khác. So khớp mẫu dựa trên pixel, nên ảnh mẫu chụp ở 540x960 không khớp ở 720x1280 hay ở DPI khác. Đặt mọi instance về cùng cài đặt màn hình và khởi động lại. Trên thiết bị cloud, tạo thiết bị với cùng preset màn hình bạn đã chụp. Nếu các instance buộc phải khác nhau, chụp một bộ ảnh mẫu cho mỗi độ phân giải và chọn bằng `mas.get_screen_size()`.

Không có công cụ tự động hóa nào an toàn 100%, vì vậy hãy tự động hóa một cách có trách nhiệm và theo quyết định của riêng bạn.
