Lọc Ảnh Bằng Phép Tương Quan Và Tích Chập

Cập nhật ngày: 28/03/2024 - Đã có 565 lượt xem bài viết này!
Lọc Ảnh Bằng Phép Tương Quan Và Tích Chập
Xử lý ảnh và thị giác máy tính là một trong những nhánh của khoa học máy tính có vô số ứng dụng cho đời sống con người như nhận diện khuôn mặt, đọc biển số xe, cảm biến chuyển động, … Do đó khi đã đi vào ngành này, kiến thức về phép lọc ảnh bằng phép tính tương quan, tích chập là không thể thiếu. Ngày nay, khi nói đến xử lý ảnh hay thị giác máy tính, người ta lập tức nghĩ đến các thư viện như OpenCV, thậm chí trong một số trường đại học, người ta chỉ chú tâm đến cách giải quyết vấn đề chứ không

Lọc Ảnh Bằng Phép Tương Quan Và Tích Chập

Nguyên lý của việc làm mờ ảnh

Về bản, chất thực hiện làm mờ ảnh chính là tạo ra ảnh mới sao cho giá trị mức xám của mỗi pixel ở ảnh mới đúng bằng giá trị trung bình của điểm tương ứng và 8 điểm lân cận trên ảnh ban đầu. Nói cách khác, với mỗi điểm trên hình ban đầu, bạn tính giá trị trung bình của nó (tại hàng i cột j) với 8 điểm xung quanh rồi viết lại giá trị mức xám ở vị trí tương ứng (cũng tại hàng i cột j) lên ảnh mới, sau đó tương tự với các điểm tiếp theo.

 

Vậy có nghĩa là ta đang tính trung bình cộng của 9 pixel (pixel tại điểm đó và 8 pixel lân cận), vậy phép tính đó cũng giống như nhân từng giá trị mức sáng của các pixel lân cận với fraction  sau đó cộng lại với nhau. Vậy, nếu có một ma trận 3 x 3 với tất cả các con số trong ma trận đều là fraction , ta nhân từng phần tử của ma trận này với mức sáng của pixel tương ứng và cộng lại, ta sẽ có kết quả giống nhau (xem hình vẽ bên dưới).

Áp dụng tương tự cho mọi pixel trên ảnh ban đầu và lấy từng kết quả cho từng pixel của ảnh mới, ta sẽ được ảnh mới chính là ảnh mờ của ảnh ban đầu.

Đến đây, có thể bạn đã hiểu được ý nghĩa của công cụ filter của photoshop, các con số 1 chính là phần tử của ma trận 3x3 (bạn cũng có thể tạo ma trận 5x5 thậm chí lớn hơn nếu thích) và số 9 chính là chia tất cả cho 9 (để tạo ra các phần tử bằng fraction).

Phép tương quan và tích chập

Phép tính trên gọi là phép tương quan (correlation) và ma trận 3x3 đó gọi là kernel. Một phiên bản khác của nó là phép tích chập (convolution) với khác biệt là kernel được xoay 180 độ (xem hình dưới), phép tích chập này rất thông dụng và được sử dụng rộng rãi trong xử lý ảnh.

Quay kernel 180 độ

Một số vấn đề khi tính tương quan, tích chập

  • Khi nhân các phần tử tương ứng với nhau (giữa pixel, các điểm lân cận – các thành phần trong kernel), đối với các phần tử ở cạnh thì sẽ có một số pixel bị khuyết, lúc này, có nhiều cách giải quyết như bỏ qua, chèn thêm một (một số) hàng, cột mang giá trị 0 hoặc bằng giá trị gần nhất.

  • Các kernel thường có kích thước lẻ (3, 5, 7, …) để thuận lợi và có nhiều ý nghĩa hơn trong tính toán.

  • Do khi hiện thực, ta cần dùng đến 4 vòng for nên tốc độ tính toán sẽ rất chậm nếu hình ảnh quá lớn. Khi đó, ta nên sử dụng GPU để tính toán hoặc sử dụng phép biến đổi fourier để đưa về miền tầng số (dành cho bạn đọc muốn tìm hiểu sâu hơn – tìm hiểu với từ khóa “Fourier Transform image processing”).

Tách cạnh từ hình ảnh bằng phép convolution

Bạn có thể nhận thấy hình ảnh bây giờ chỉ còn giữ lại pixel của các cạnh. Vì sao kernel này giúp lấy được cạnh? Bởi vì nó khảo sát sự thay đổi mức xám đột ngột. Ví dụ với 1 ảnh chụp hình cửa sổ nằm trên một bức tường, ở trên bức tường thì mức xám của các pixel gần giống nhau, nên pixel bên trái và bên phải của pixel đang xét sẽ có mức xám gần bằng nhau. Khi nhân từng phần tử tương ứng lại và cộng lại, pixel bên trái sẽ triệt tiêu pixel bên phải (do hệ số trong ma trận là trái dấu), nên con số tính ra sẽ rất nhỏ (chứng tỏ pixel đang xét không phải là cạnh). Ngược lại nếu như có sự thay đổi mức xám đột ngột, thì con số tính được sẽ lớn (do sự mất cân bằng giữa pixel bên phải và bên trái), từ đó ta nhận diện được cạnh.

Code hoàn chỉnh

 

/*
Owner: STDIO Training
Author: Phan Tan Phuc
Website: https://training.stdio.vn
Email: developer@stdio.vn
*/
 
#define _CRT_SECURE_NO_WARNINGS
 
#include 
#include 
 
typedef unsigned char byte;
 
int _width;
int _height;
int _bitdepth;
 
typedef struct
{
    char            m_type[2];
    unsigned char    m_fSize[4];
    unsigned char    m_reserved1[2];
    unsigned char    m_reserved2[2];
    unsigned char    m_data_offset[4];
}BitMapHeader;
 
typedef struct
{
    unsigned char m_size[4];
    unsigned char m_width[4];
    unsigned char m_height[4];
    unsigned char m_planes[2];
    unsigned char m_bitDepth[2];
    unsigned char m_compression_type[16];
    unsigned char m_color_used[4];
    unsigned char m_color_important[4];
}BitMapInformation;
 
void readImage(const char * _filePath, BitMapHeader & _header, BitMapInformation & _information, unsigned char * &_data)
{
    FILE* _imageFile;
    _imageFile = fopen(_filePath, "rb");
 
    if (_imageFile == nullptr)
    {
        return;
    }
 
 
    fread(&_header, sizeof(BitMapHeader), sizeof(char), _imageFile);
    fread(&_information, sizeof(BitMapInformation), sizeof(char), _imageFile);
 
    _width = *(int*)_information.m_width;
    _height = *(int*)_information.m_height;
    _bitdepth = *(int*)_information.m_bitDepth;
    int data_offset = *(int*)_header.m_data_offset;
 
    int _rowSize = (_bitdepth * _width + 31) / 32 * 4;
 
    if (_data != nullptr)
    {
        delete[] _data;
        _data = nullptr;
    }
    _data = new unsigned char[_rowSize * _height];
 
 
    fseek(_imageFile, data_offset, SEEK_SET);
    fread(_data, _rowSize * _height, sizeof(char), _imageFile);
 
    fclose(_imageFile);
}
 
void corr(byte* data, int width, int height, int byte_per_pixel, float* kernel, int kernel_size) {
 
    int padding = kernel_size / 2;
    int width_size = width * byte_per_pixel;
    int height_size = height * byte_per_pixel;
 
    int _rowSize = (byte_per_pixel * 8 * width + 31) / 32 * 4;
 
    int img_size = width * height * byte_per_pixel * sizeof(byte);
 
    byte* temp_img = new byte[_rowSize * height];
 
    for (int r = padding; r < height - padding; ++r) {
        for (int c = padding; c < width - padding; ++c) {
 
            float sum_c1 = 0.0f;
            float sum_c2 = 0.0f;
            float sum_c3 = 0.0f;
 
            for (int kr = -padding; kr <= padding; ++kr) {
                for (int kc = -padding; kc <= padding; ++kc) {
                    int r_corr = r + kr;
                    int c_corr = c + kc;
                    sum_c1 += data[_rowSize * r_corr + c_corr * byte_per_pixel + 0] * kernel[kernel_size * (kr + padding) + (kc + padding)];
                    sum_c2 += data[_rowSize * r_corr + c_corr * byte_per_pixel + 1] * kernel[kernel_size * (kr + padding) + (kc + padding)];
                    sum_c3 += data[_rowSize * r_corr + c_corr * byte_per_pixel + 2] * kernel[kernel_size * (kr + padding) + (kc + padding)];
                }
            }
 
            if (sum_c1 > 255) sum_c1 = 255;
            if (sum_c1 < 0) sum_c1 = 0;
            if (sum_c2 > 255) sum_c2 = 255;
            if (sum_c2 < 0) sum_c2 = 0;
            if (sum_c3 > 255) sum_c3 = 255;
            if (sum_c3 < 0) sum_c3 = 0;
 
            temp_img[_rowSize * r + c * byte_per_pixel + 0] = (byte)sum_c1;
            temp_img[_rowSize * r + c * byte_per_pixel + 1] = (byte)sum_c2;
            temp_img[_rowSize * r + c * byte_per_pixel + 2] = (byte)sum_c3;
        }
    }
 
    memcpy(data, temp_img, img_size);
    delete[] temp_img;
 
 
}
 
void saveImage(const char * _filePath, BitMapHeader & _header, BitMapInformation & _information, const unsigned char * _data)
{
    int _width = *(int*)_information.m_width;
    int _height = *(int*)_information.m_height;
    int _bitdepth = *(int*)_information.m_bitDepth;
    int _offset = *(int*)_header.m_data_offset;
 
    FILE * _imageFile;
    _imageFile = fopen(_filePath, "wb");
 
    fwrite(&_header, sizeof(BitMapHeader), sizeof(char), _imageFile);
    fwrite(&_information, sizeof(BitMapInformation), 1, _imageFile);
 
    int _size = (_bitdepth * _width + 31) / 32 * 4;
 
    fseek(_imageFile, _offset, SEEK_SET);
    fwrite(_data, _size * _height, sizeof(char), _imageFile);
    fclose(_imageFile);
}
 
int main()
{
    // initilize
    byte * _dataImage = nullptr;
 
    BitMapHeader        _header;
    BitMapInformation    _infomation;
 
    // Thay STDIO_Image bang duong dan den anh Bitmap phu hop
 
    readImage("STDIO_Image.bmp", _header, _infomation, _dataImage);
 
    float laplacekernel[] =
        { -1, -1, -1,
          -1, 8, -1,
          -1, -1, -1 };
 
    corr(_dataImage, _width, _height, _bitdepth / 8, laplacekernel, 3);
 
    saveImage("STDIO_ImageNew.bmp", _header, _infomation, _dataImage);
 
    delete[] _dataImage;
 
    return 0;
}

 

Bạn đang muốn tìm kiếm 1 công việc với mức thu nhập cao.
✅ Hoặc là bạn đang muốn chuyển đổi công việc mà chưa biết theo học ngành nghề gì cho tốt.
✅ Giới thiệu với bạn Chương trình đào tạo nhân sự dài hạn trong 12 tháng với những điều đặc biệt mà chỉ có tại IMIC và đây cũng chính là sự lựa chọn phù hợp nhất dành cho bạn:
👉 Thứ nhất: Học viên được đào tạo bài bản kỹ năng, kiến thức chuyên môn lý thuyết, thực hành, thực chiến nhiều dự án và chia sẻ những kinh nghiệm thực tế từ Chuyên gia có nhiều năm kinh nghiệm dự án cũng như tâm huyết truyền nghề.
👉 Thứ hai: Được ký hợp đồng cam kết chất lượng đào tạo cũng như mức lương sau tốt nghiệp và đi làm tại các đối tác tuyển dụng của IMIC. Trả lại học phí nếu không đúng những gì đã ký kết.
👉 Thứ ba: Cam kết hỗ trợ giới thiệu công việc sang đối tác tuyển dụng trong vòng 10 năm liên tục.
👉 Thứ tư: Được hỗ trợ tài chính với mức lãi suất 0 đồng qua ngân hàng VIB Bank.
👉  Có 4 Chương trình đào tạo nhân sự dài hạn dành cho bạn lựa chọn theo học. Gồm có:
1)  Data Scientist full-stack
2)  Embedded System & IoT development full-stack
3)  Game development full-stack
4)  Web development full-stack 
✅ Cảm ơn bạn đã dành thời gian lắng nghe những chia sẻ của mình. Và tuyệt vời hơn nữa nếu IMIC được góp phần vào sự thành công của bạn. 
✅ Hãy liên hệ ngay với Phòng tư vấn tuyển sinh để được hỗ trợ về thủ tục nhập học.
✅ Chúc bạn luôn có nhiều sức khỏe và thành công!

Tham khảo các khóa đào tạo nhân sự qua danh mục