|
| 1 | +#include <iostream> |
| 2 | +#include <vector> |
| 3 | + |
| 4 | +#include <opencv2/highgui/highgui.hpp> |
| 5 | +#include <opencv2/imgproc/imgproc.hpp> |
| 6 | + |
| 7 | +/* overlayImage: copies a transparent 4-channel image over a solid background image. |
| 8 | + * Original source: http://jepsonsblog.blogspot.com.br/2012/10/overlay-transparent-image-in-opencv.html |
| 9 | + * |
| 10 | + * background: must be 3-channel BGR. |
| 11 | + * foreground: must be 4-channel RGBA. |
| 12 | + * output: the destination Mat. |
| 13 | + * location: offset starting point. |
| 14 | + */ |
| 15 | +void overlayImage(const cv::Mat &background, const cv::Mat &foreground, cv::Mat &output, cv::Point2i location) |
| 16 | +{ |
| 17 | + background.copyTo(output); |
| 18 | + |
| 19 | + // start at the row indicated by location, or at row 0 if location.y is negative. |
| 20 | + for (int y = std::max(location.y , 0); y < background.rows; ++y) { |
| 21 | + int fY = y - location.y; // because of the translation |
| 22 | + |
| 23 | + // we are done of we have processed all rows of the foreground image. |
| 24 | + if (fY >= foreground.rows) |
| 25 | + break; |
| 26 | + |
| 27 | + // start at the column indicated by location, or at column 0 if location.x is negative. |
| 28 | + for (int x = std::max(location.x, 0); x < background.cols; ++x) { |
| 29 | + int fX = x - location.x; // because of the translation. |
| 30 | + |
| 31 | + // we are done with this row if the column is outside of the foreground image. |
| 32 | + if (fX >= foreground.cols) |
| 33 | + break; |
| 34 | + |
| 35 | + // determine the opacity of the foregrond pixel, using its fourth (alpha) channel. |
| 36 | + double opacity = ((double)foreground.data[fY * foreground.step + fX * foreground.channels() + 3]) / 255.; |
| 37 | + |
| 38 | + // and now combine the background and foreground pixel, using the opacity, but only if opacity > 0. |
| 39 | + for (int c = 0; opacity > 0 && c < output.channels(); ++c) { |
| 40 | + unsigned char foregroundPx = foreground.data[fY * foreground.step + fX * foreground.channels() + c]; |
| 41 | + unsigned char backgroundPx = background.data[y * background.step + x * background.channels() + c]; |
| 42 | + output.data[y*output.step + output.channels()*x + c] = backgroundPx * (1.-opacity) + foregroundPx * opacity; |
| 43 | + } |
| 44 | + } |
| 45 | + } |
| 46 | +} |
| 47 | + |
| 48 | +/* getComponent: helper function that returns the color of a specific component. |
| 49 | + * |
| 50 | + * bgr_pixel: the input BGR pixel of type cv::Vec3b. |
| 51 | + * bgr_component: the index of the channel. 0 for Blue, 1 for Green, 2 for Red. |
| 52 | + * This function returns the color of a pixel as unsigned char. |
| 53 | + */ |
| 54 | +uchar getComponent(cv::Vec3b bgr_pixel, int bgr_component) |
| 55 | +{ |
| 56 | + switch (bgr_component) |
| 57 | + { |
| 58 | + case 0: // Blue |
| 59 | + return bgr_pixel[0]; |
| 60 | + |
| 61 | + case 1: // Green |
| 62 | + return bgr_pixel[1]; |
| 63 | + |
| 64 | + case 2: // Red |
| 65 | + return bgr_pixel[2]; |
| 66 | + |
| 67 | + default: |
| 68 | + std::cout << "!!! getComponent: " << bgr_component << " is not a valid component" << std::endl; |
| 69 | + break; |
| 70 | + } |
| 71 | + |
| 72 | + return 0; |
| 73 | +} |
| 74 | + |
| 75 | +/* displacementMapFilter: uses the pixel values of a map to displace the pixels of the target image. |
| 76 | + * |
| 77 | + * map: the input map image (BGR). |
| 78 | + * target: the input foreground image (BGRA). |
| 79 | + * componentX: describes which color channel to use in the map image to displace the x result. Blue:0 Green:1 Red:2. |
| 80 | + * componentY: describes which color channel to use in the map image to displace the y result. Blue:0 Green:1 Red:2. |
| 81 | + * scaleX: the strength of the effect in the x plane. |
| 82 | + * scaleY: the strength of the effect in the y plane. |
| 83 | + * output: the destination Mat (BGRA). |
| 84 | + */ |
| 85 | +void displacementMapFilter(const cv::Mat& map, const cv::Mat& target, int componentX, int componentY, int scaleX, int scaleY, cv::Mat& output) |
| 86 | +{ |
| 87 | + if (componentX < 0 || componentX > 2 || componentY < 0 || componentY > 2) |
| 88 | + { |
| 89 | + std::cout << "!!! displacementMapFilter: componentX and componentY values must be in range [0,2]" << std::endl; |
| 90 | + return; |
| 91 | + } |
| 92 | + |
| 93 | + if (target.size().width != map.size().width || target.size().height != map.size().height || target.type() != CV_8UC4) |
| 94 | + { |
| 95 | + std::cout << "!!! displacementMapFilter: map and target need to have the same dimensions, and target type must be CV_8UC4" << std::endl; |
| 96 | + return; |
| 97 | + } |
| 98 | + |
| 99 | + output.create(target.rows, target.cols, target.type()); |
| 100 | + |
| 101 | + for (int x = 0; x < output.rows; x++) |
| 102 | + for (int y = 0; y < output.cols; y++) |
| 103 | + { |
| 104 | + /* Formula: |
| 105 | + * dstPixel[x, y] = srcPixel[x + ((componentX(x, y) - 128) * scaleX) / 256, |
| 106 | + * y + ((componentY(x, y) - 128) * scaleY) / 256)] |
| 107 | + */ |
| 108 | + |
| 109 | + int dx = x + (getComponent(map.at<cv::Vec3b>(x, y), componentX) - 128) * scaleX / 256; |
| 110 | + if (dx < 0) dx = 0; |
| 111 | + if (dx >= output.rows) dx = output.rows; |
| 112 | + |
| 113 | + int dy = y + (getComponent(map.at<cv::Vec3b>(x, y), componentY) - 128) * scaleY / 256; |
| 114 | + if (dy < 0) dy = 0; |
| 115 | + if (dy >= output.cols) dy = output.cols; |
| 116 | + |
| 117 | + output.at<cv::Vec4b>(x, y) = target.at<cv::Vec4b>(dx, dy); |
| 118 | + } |
| 119 | +} |
| 120 | + |
| 121 | + |
| 122 | +int main(int argc, char* argv[]) |
| 123 | +{ |
| 124 | + // Load input map (colored, 3-channel, BGR) |
| 125 | + cv::Mat map = cv::imread("map.jpg"); |
| 126 | + if (map.empty()) |
| 127 | + { |
| 128 | + std::cout << "!!! Failed imread() #1" << std::endl; |
| 129 | + return -1; |
| 130 | + } |
| 131 | + std::cout << "map size: " << map.cols << "x" << map.rows << " channels:" << map.channels() << " type:" << map.type() << std::endl; |
| 132 | + |
| 133 | + // Load input target (colored, 4-channel, BGRA) |
| 134 | + cv::Mat target = cv::imread("target.png", -1); |
| 135 | + if (target.empty()) |
| 136 | + { |
| 137 | + std::cout << "!!! Failed imread() #2" << std::endl; |
| 138 | + return -1; |
| 139 | + } |
| 140 | + std::cout << "target size: " << target.cols << "x" << target.rows << "channels: " << target.channels() << " type: " << target.type() << std::endl; |
| 141 | + |
| 142 | + if (target.channels() != 4) |
| 143 | + { |
| 144 | + std::cout << "!!! A PNG image with transparent layer is required" << std::endl; |
| 145 | + return -1; |
| 146 | + } |
| 147 | + |
| 148 | + if (target.size().width > map.size().width || target.size().height > map.size().height) |
| 149 | + { |
| 150 | + std::cout << "!!! Target needs to have smaller dimensions than map" << std::endl; |
| 151 | + return -1; |
| 152 | + } |
| 153 | + |
| 154 | + /* Display the map as movie clip */ |
| 155 | + |
| 156 | + int offset_x = 0; |
| 157 | + |
| 158 | + while (1) |
| 159 | + { |
| 160 | + // Crop the map (which is larger) to the size of the target image |
| 161 | + cv::Rect roi = cv::Rect(offset_x, 0, target.size().width, target.size().height); |
| 162 | + cv::Mat cropped_map = map(roi); |
| 163 | + |
| 164 | + // Execute the Displacement Map Filter |
| 165 | + cv::Mat output; |
| 166 | + displacementMapFilter(cropped_map, target, 2, 2, 20, 20, output); |
| 167 | + |
| 168 | + // Display the results on the screen |
| 169 | + cv::Mat frame; |
| 170 | + overlayImage(cropped_map, output, frame, cv::Point(0,0)); |
| 171 | + cv::imshow("OpenCV - Displacement Map Filter", frame); |
| 172 | + |
| 173 | + // Detect if ESC was pressed and quit. Frames are displayed every 33ms. |
| 174 | + char key = cv::waitKey(33); |
| 175 | + if (key == 27) |
| 176 | + break; |
| 177 | + |
| 178 | + // You can increase the value of offset_x to play the animation faster |
| 179 | + offset_x += 3; |
| 180 | + if ((map.size().width - target.size().width) <= offset_x) |
| 181 | + break; |
| 182 | + } |
| 183 | + |
| 184 | + return 0; |
| 185 | +} |
0 commit comments