Я хочу преобразовать ATL::CImage
в cv::Mat
для обработки изображений в opencv (C++).
Не могли бы вы помочь преобразовать этот объект?
Я получил CImage
на снимке экрана Windows (с помощью MFC).
Затем я хочу обработать изображение в объекте OpenCV Mat.
Я не знал, как преобразовать.
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
.
Самый простой (но довольно глупый) подход — сохранить его в файл (похоже, для этого есть функция-член), а затем загрузить его обратно с помощью imread
. Также кажется, что вы можете сохранить в поток в памяти, чтобы затем передать полученный буфер в imdecode
и избежать использования файловой системы. Наилучшим подходом было бы получить пиксельный буфер с известным макетом, понятным Mat
(в идеале по строкам, сверху вниз, поэтому вам не нужно его переворачивать).. тогда вам просто нужно создать заголовок Mat
, чтобы он соответствовал данные. Только когда-либо приходилось переходить от Mat
к объекту GDI, поэтому нужно будет исследовать.
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);
}
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
, вы потенциально допускаете утечку буфера (или, по крайней мере, напрашиваетесь на неприятности — на самом деле не хотите копаться в реализации, чтобы найти любые возможные ловушки из-за возни с его открытым внутренним состоянием). Просто используйте соответствующий конструктор, чтобы правильно создать заголовок, обертывающий этот буфер. Исправьте это, и я пересмотрю свой голос.
Также может быть полезно упомянуть, что Mat
хорош только до тех пор, пока image
находится в области действия.
@DanMašek Вам не нужно копаться в исходном коде. Вы можете прочитать документацию для data
, которая «разоблачена», и вы можете проверить наличие утечек. Утечек в этом случае не происходит, потому что Mat
отслеживает собственное распределение. Ваше предложение, безусловно, эффективнее и безопаснее, но вопрос в том, что Mat mat;
это зависит от того, кто его перепишет...
Да, я вижу, что объект-член UMatData
будет обрабатывать первоначально выделенную память. Но вы определенно нарушаете внутреннее состояние прямо сейчас, поскольку datastart
, dataend
и datalimit
по-прежнему указывают на внутреннюю выделенную память, а data
указывает на совершенно другой блок. Так что такие вещи, как locateROI
и, возможно, adjustROI
, вероятно, в лучшем случае не будут очень счастливыми. Не говоря уже о том, что вы выделяете бесполезный кусок памяти. Я действительно не вижу причин делать это, особенно указав более чистый подход. Извините, но я бы никогда не пропустил такой код через обзор.
«до того, кто попросит переписать его» - основываясь на моих наблюдениях за SO, я ожидаю, что многие новички воспримут этот код как правильный, не задумываясь. Следовательно, мне кажется немного безответственным писать такой код в ответе.
@DanMašek, да, дополнительное выделение неэффективно (честно говоря, это лучше, чем сохранять изображение на диск и читать его снова). Я обновил ответ, чтобы избежать дополнительного выделения и исправить возможные проблемы с указателем.
«лучше, чем экономить» — абсолютно точно, это был просто подход с минимальными усилиями, который пришел на ум с минимальными исследованиями. Ваш подход (кроме того, на что я жаловался) был бы тем, что я бы искал (а чистое решение WinAPI даже лучше). Спасибо за обновления.
#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);
Можно было просто исключить посредника и уже одной функцией получить скриншот в виде мата. Это может помочь: stackoverflow.com/questions/34466993/opencv-рабочий стол-захват или stackoverflow.com/questions/14148758/…