Как преобразовать ATL::CImage в cv::Mat?

Я хочу преобразовать ATL::CImage в cv::Mat для обработки изображений в opencv (C++). Не могли бы вы помочь преобразовать этот объект?

Я получил CImage на снимке экрана Windows (с помощью MFC). Затем я хочу обработать изображение в объекте OpenCV Mat.

Я не знал, как преобразовать.

  • Проект С++ (VC 2017)
  • МФЦ
  • OpenCV 3.4.6

CImage image;
int cx;
int cy;
CWnd* pWndDesktop = CWnd::GetDesktopWindow();
CWindowDC srcDC(pWndDesktop);

Rect rcDesktopWindow;
::GetWindowRect(pWndDesktop->m_hWnd, %rcDesktopWindow);

cx = (rcDesktopWindow.right - rcDesktopWindow.left);
cy = (rcDesktopWindow.bottom - rcDesktopWindow.top);

image.create(cx, cy, srcDC.GetDeviceCaps(BITPIXEL));

CDC* pDC = CDC::FromHandle(image.GetDC());
pDC->BitBlt(0, 0, cx, cy, &srcDC, 0, 0, SRCCOPY);

image.ReleaseDC();

cv::Mat mat;

// I want set image to mat!
mat = image???

Не могу преобразовать ATL::Image в cv::Mat.

Можно было просто исключить посредника и уже одной функцией получить скриншот в виде мата. Это может помочь: stackoverflow.com/questions/34466993/opencv-рабочий стол-захват или stackoverflow.com/questions/14148758/…

Retired Ninja 15.05.2019 02:43

Самый простой (но довольно глупый) подход — сохранить его в файл (похоже, для этого есть функция-член), а затем загрузить его обратно с помощью imread. Также кажется, что вы можете сохранить в поток в памяти, чтобы затем передать полученный буфер в imdecode и избежать использования файловой системы. Наилучшим подходом было бы получить пиксельный буфер с известным макетом, понятным Mat (в идеале по строкам, сверху вниз, поэтому вам не нужно его переворачивать).. тогда вам просто нужно создать заголовок Mat, чтобы он соответствовал данные. Только когда-либо приходилось переходить от Mat к объекту GDI, поэтому нужно будет исследовать.

Dan Mašek 15.05.2019 04:11
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
5
2
1 145
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

CImage создает растровое изображение снизу вверх, если высота положительна. Вы должны передать отрицательную высоту, чтобы создать растровое изображение сверху вниз для mat

Используйте CImage::GetBits для получения битов следующим образом:

HDC hdc = GetDC(0);
RECT rc;
GetClientRect(GetDesktopWindow(), &rc);
int cx = rc.right;
int cy = rc.bottom;

CImage image;
image.Create(cx, -cy, 32);

BitBlt(image.GetDC(), 0, 0, cx, cy, hdc, 0, 0, SRCCOPY);
image.ReleaseDC();
ReleaseDC(0, hdc);

cv::Mat mat;
mat.create(cy, cx, CV_8UC4);
memcpy(mat.data, image.GetBits(), cy * cx * 4);

//or borrow pixel data from CImage 
cv::Mat mat(cy, cx, CV_8UC4, image.GetBits()); 

Или принудительно выполните глубокую копию следующим образом:

cv::Mat mat;
mat = cv::Mat(cy, cx, CV_8UC4, image.GetBits()).clone();

Обратите внимание: CImage самостоятельно распределяет пиксельные данные. И Mat должен сделать свое собственное распределение, или он должен заимствовать у CImage, что может быть сложно.

Если вы просто делаете снимок экрана, вы можете сделать это с помощью простого Windows API, а затем написать прямо в cv::Mat. Таким образом, происходит одно выделение (немного быстрее), и mat не зависит от других объектов. Пример:

void foo()
{
    HDC hdc = ::GetDC(0);

    RECT rc;
    ::GetClientRect(::GetDesktopWindow(), &rc);
    int cx = rc.right;
    int cy = rc.bottom;
    cv::Mat mat;
    mat.create(cy, cx, CV_8UC4);

    HBITMAP hbitmap = CreateCompatibleBitmap(hdc, cx, cy);
    HDC memdc = CreateCompatibleDC(hdc);
    HBITMAP oldbmp = (HBITMAP)SelectObject(memdc, hbitmap);
    BitBlt(memdc, 0, 0, cx, cy, hdc, 0, 0, SRCCOPY);

    BITMAPINFOHEADER bi = { sizeof(bi), cx, -cy, 1, 32, BI_RGB };
    GetDIBits(hdc, hbitmap, 0, cy, mat.data, (BITMAPINFO*)&bi, DIB_RGB_COLORS);

    //GDI cleanup:
    SelectObject(memdc, oldbmp);
    DeleteDC(memdc);
    DeleteObject(hbitmap);
    ::ReleaseDC(0, hdc);
}


Edit:
Changed mat.data = (unsigned char*)image.GetBits(); to
memcpy(mat.data, image.GetBits(), cy * cx * 4);

Изменено ReleaseDC(0, hdc) на ::ReleaseDC(0, hdc), чтобы избежать конфликта с CWnd::ReleaseDC(dc)

Сбрасывая член data, вы потенциально допускаете утечку буфера (или, по крайней мере, напрашиваетесь на неприятности — на самом деле не хотите копаться в реализации, чтобы найти любые возможные ловушки из-за возни с его открытым внутренним состоянием). Просто используйте соответствующий конструктор, чтобы правильно создать заголовок, обертывающий этот буфер. Исправьте это, и я пересмотрю свой голос.

Dan Mašek 15.05.2019 04:58

Также может быть полезно упомянуть, что Mat хорош только до тех пор, пока image находится в области действия.

Dan Mašek 15.05.2019 05:06

@DanMašek Вам не нужно копаться в исходном коде. Вы можете прочитать документацию для data, которая «разоблачена», и вы можете проверить наличие утечек. Утечек в этом случае не происходит, потому что Mat отслеживает собственное распределение. Ваше предложение, безусловно, эффективнее и безопаснее, но вопрос в том, что Mat mat; это зависит от того, кто его перепишет...

Barmak Shemirani 15.05.2019 09:04

Да, я вижу, что объект-член UMatData будет обрабатывать первоначально выделенную память. Но вы определенно нарушаете внутреннее состояние прямо сейчас, поскольку datastart, dataend и datalimit по-прежнему указывают на внутреннюю выделенную память, а data указывает на совершенно другой блок. Так что такие вещи, как locateROI и, возможно, adjustROI, вероятно, в лучшем случае не будут очень счастливыми. Не говоря уже о том, что вы выделяете бесполезный кусок памяти. Я действительно не вижу причин делать это, особенно указав более чистый подход. Извините, но я бы никогда не пропустил такой код через обзор.

Dan Mašek 16.05.2019 23:33

«до того, кто попросит переписать его» - основываясь на моих наблюдениях за SO, я ожидаю, что многие новички воспримут этот код как правильный, не задумываясь. Следовательно, мне кажется немного безответственным писать такой код в ответе.

Dan Mašek 16.05.2019 23:35

@DanMašek, да, дополнительное выделение неэффективно (честно говоря, это лучше, чем сохранять изображение на диск и читать его снова). Я обновил ответ, чтобы избежать дополнительного выделения и исправить возможные проблемы с указателем.

Barmak Shemirani 17.05.2019 06:11

«лучше, чем экономить» — абсолютно точно, это был просто подход с минимальными усилиями, который пришел на ум с минимальными исследованиями. Ваш подход (кроме того, на что я жаловался) был бы тем, что я бы искал (а чистое решение WinAPI даже лучше). Спасибо за обновления.

Dan Mašek 17.05.2019 07:05
#include <opencv2\opencv.hpp>
#include <opencv2/imgproc/types_c.h>
#include <atlimage.h>

using namespace cv;

Mat CImage2Mat(CImage cimg)
{
    BITMAP bmp;
    ::GetObject(cimg.Detach(), sizeof(BITMAP), &bmp);

    int nChannels = bmp.bmBitsPixel == 1 ? 1 : bmp.bmBitsPixel / 8;
    int depth = bmp.bmBitsPixel == 1 ? IPL_DEPTH_1U : IPL_DEPTH_8U;

    IplImage* img = cvCreateImageHeader(cvSize(bmp.bmWidth, bmp.bmHeight), depth, nChannels);

    img->imageData = (char*)malloc(bmp.bmHeight * bmp.bmWidth * nChannels * sizeof(char));
    memcpy(img->imageData, (char*)(bmp.bmBits), bmp.bmHeight * bmp.bmWidth * nChannels);
    cvFlip(img, img, 0);
    return cvarrToMat(static_cast<IplImage*>(img));
}

Применение:

CImage cimg;
cimg.Load(L"resim.png");

Mat img = CImage2Mat(cimg);

Другие вопросы по теме