יעילות בעיבוד תמונה (ג'אווה) - תכנות - HWzone פורומים
עבור לתוכן
  • צור חשבון

יעילות בעיבוד תמונה (ג'אווה)


Eviljelly

Recommended Posts

שלום,

אני משחק קצת עם לכידת ועיבוד תמונה, ויש לי בעיה שכנראה העיבוד שאני מבצע ממש איטי ומוריד לי את הקצב רענון למשהו כמו 2-3 פריימים לשניה.

כשאני רק לוכד תמונות מהמצלמה ומציג אותם (או מבצע עיבוד בגודל התמונה) אני מגיע לקצב סביר של 10 פריימים לשניה, אבל כשאני מוסיף את הקוד שכתבתי אני מאבד הרבה פריימים.

ללכידת אני משתמש ב JMF בעזרת מדריכים שמצאתי ולעיבוד תמונה אני משתמש ב BufferedImage.

בגדול כרגע אני לא עושה הרבה חוץ מלבנות תמונה חדשה (גם BufferedImage) ולהציג בה את התמונה ארבע פעמים אחת ליד השניה (כל פעם עם שינוי קטן שנותן צבע שונה לכל פיקסל), מבחינת לולאות יש רק לולאה כפולה (גובה ,רוחב) שעוברת על כל פיקסל ונותנת לו מיקום בתמונה הגדולה יותר.

אין לי כרגע את הקוד אבל אם יהיה צורך אני אעלה אותו

כרגע אני מנסה לממש עיצוב של Producer-Consumer (ותוך כדי להכניס את הכל לעבוד עם Threads) בתקווה שזה יאיץ קצת את הסיפור.

יש למישהו עצות כיצד ליעל את התהליך ? אולי להשתמש באובייקט אחר חסכוני יותר ? בחיפושים שמעתי על ספרייה בשם JAI או JIA למישהו יש ניסיון איתה ? האם היא מהירה באופן משמעותי ? יכול להיות שהפתרון היחיד הוא מעבד מהיר יותר ? אולי הבעיה היא תקורה של גאווה ואני צריך להחליף ל C\C++ ?

אני משתמש בלפטופ שלי ,יש לו מעבד ליבה כפולה T7200 וכרטיס מובנה.

קישור לתוכן
שתף באתרים אחרים

10 פריימים לשנייה לא נשמע לי סביר בכלל בשביל הצגת הווידאו ללא עיבוד.

אני בכלל לא מבין איך זה כ"כ נמוך. מצלמת webcam הכי פשוטה עם USB2 ברזולוציה של 320X240 (נקרא QVGA) מצלמת 30 פריימים לשניה, וכמות הנתונים היא בהחלט קטנה כל שכל מחשב אפילו בן 10 אמור להצליח לפחות להציג את זה על המסך.

אין לי מושג ב-JAVA, אבל לדעתי משהו ממש, ממש לא בסדר בסיטואציה שאתה מתאר. אולי אתה עושה משהו לא נכון כמו להקצות מערכים כל פריים או משהו אחר? בלי שום קשר לJAVA, הרושם שאני מקבל זה שאתה עושה משהו בדרך הלא נכונה עוד מראש.

אולי תפרסם קצת יותר פרטים ובתקווה מישהו יעזור.

קישור לתוכן
שתף באתרים אחרים

המצלמה שאני משתמש בה כרגע היא לא להיט, זאת מצלמה מובנית במסך 1.2 מגה פיקסל, שגם הדרייבר שבא איתה לא סוחב מעל 15 פריימים לשניה.

כשאני בבית אני משתמש במחשב שלי ואז מחבר מצלמת פשוטה אבל באיכות DV עם חיבור firewire ובעזרת אותו קוד הגעתי לבערך 25 פריימים לשניה (בלי עיבוד)

עוד לא יצא לי לבדוק את הקוד עם העיבוד על המחשב השני שלי.

זה החלק בקוד שאחראי על הלכידה - הוא לא עובד אם JMF לא מותקן ומוגדר כמו שצריך:



package VideoCapture;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.util.Iterator;
import java.util.Vector;

import javax.media.Buffer;
import javax.media.CaptureDeviceInfo;
import javax.media.CaptureDeviceManager;
import javax.media.Manager;
import javax.media.MediaLocator;
import javax.media.Player;
import javax.media.control.FrameGrabbingControl;
import javax.media.format.VideoFormat;
import javax.media.util.BufferToImage;


public class CaptureImage {

private final String VIDEO_DEVICE_NAME = "vfw:Microsoft WDM Image Capture (Win32):0";
private boolean mFoundVideoDevice = false;
private CaptureDeviceInfo mCaptureDeviceInfo = null;
private FrameGrabbingControl mFrameGrabber;
private Player mPlayer = null;
private BufferedImage mCurrentPicture = null;
/**
* Constructor
* in here we initialize the capture device and define it
*/
public CaptureImage() {
// get the list of devices from the manager
Vector<?> devices = CaptureDeviceManager.getDeviceList(null);
// define a capture device
CaptureDeviceInfo captureDevice = null;
// go over the vector and look for a video device
for (Iterator<?> i = devices.iterator(); i.hasNext() {
captureDevice = (CaptureDeviceInfo)i.next();
// check if the device is a video device
if (captureDevice.getName().contentEquals(VIDEO_DEVICE_NAME)) {
mCaptureDeviceInfo = captureDevice;
mFoundVideoDevice = true;
break;
}
}
// setup the grabbing mechanisem:
if (mFoundVideoDevice) {
try {
MediaLocator mediaLocator = mCaptureDeviceInfo.getLocator();
mPlayer = Manager.createPlayer(mediaLocator);
mPlayer.start();

mFrameGrabber = (FrameGrabbingControl)mPlayer.getControl("javax.media.control.FrameGrabbingControl");
} catch (Exception ex) {
ex.printStackTrace();
}

}
}

/**
* This function actually captures frames from the video source
* in the first few times it throws lots of exceptions becuase the video source is not ready
* it returns a buffered Image or null if device is not ready.
*
* @return
*/
public BufferedImage getImageFromCaptureSource() {
try {
if (mFrameGrabber == null)
mFrameGrabber = (FrameGrabbingControl)mPlayer.getControl("javax.media.control.FrameGrabbingControl");
else {
Buffer buf = mFrameGrabber.grabFrame();
// Convert frame to an buffered image so it can be processed and saved
Image img = (new BufferToImage((VideoFormat) buf.getFormat())
.createImage(buf));
mCurrentPicture = Pictures.toBufferedImage(img);

System.out.println("ok");
return mCurrentPicture;
}
} catch (Throwable e) {
//e.printStackTrace();
}
return null;
}
}


את הקוד הזה עטפתי בטיימר וכל 100 מילי שניות אני מקבל ממנו תמונה.

קישור לתוכן
שתף באתרים אחרים

אני לא יודע מספיק על הנושא או על JAVA אז זה כל מה שאני יכול לפרט:

1) רק מוודא שברור לך שאם אתה קורא תמונה כל 100 מילישניות, אז לכל היותר תקבל 10 פריימים לשניה.

2) בתלות במה הוא עושה, הקוד שמאתחל את img יכול להיות מאוד איטי:

* האם הוא מקצה את הזכרון לפריים מחדש כל פריים? עדיף להקצות את ה-bitmap מראש ורק להעתיק לתוכו אם צריך.

* כנ"ל לגבי createImage. האם יכול להיות שיש פה הרבה הקצאות והעתקות מיותרות?

* האם משהו מבצע המרת פורמט (לדוגמא מ-planar ל- רגיל, או מ-16 ל-24 ביט, וכו')? אם כן, אולי יתכן שקוד ההמרה איטי?

לגבי קוד עיבוד התמונה - זה תלוי במה שהוא עושה, איך הוא כתוב, והאלגוריתם.

לדוגמא, לרוב עדיף לעבור על התמונה שורה שורה ולא עמודה עמודה, על מנת לא לשחוט את ה-cache.

אולי האלגוריתם לא יעיל? אולי אתה משהו בצורה לא יעילה כמו קריאת למתודה לכל פיקסל? אולי אתה עושה משהו שדווקא לא מהווה בעיה בד"כ, אבל דווקא ב-JAVA נוטה להיות איטי?

קשה לענות בלי לדעת יותר.

קישור לתוכן
שתף באתרים אחרים

כמובן, כאשר בדקתי את הקוד עם המצלמה היותר טובה שיחקתי עם הערכים ומתחת ל 40 מילישניות בערך לא ראיתי שיפור , בגלל זה ובגלל שכאשר אני פשוט לוכד ומציג את התמונות אין לי ירידה בקצב אני מניח שאין בעיה בקוד שלוכד תמונה אלא בעיבוד שלה ...

אני חושב שמיקדתי את הבעיה לפונקציה הבאה :



public void CreateMask(Color iColor, int iOffset) {
BufferedImage result;
int pixelRGB = 0 , pixelLocation = 0 , white = Color.WHITE.getRGB(), black = Color.BLACK.getRGB();
int pictureHeight = mBufferedImage.getHeight() , pictureWidth = mBufferedImage.getWidth();
boolean inRedRange = true , inGreenRange = true, inBlueRange = true , inColorRange = true;
boolean notSimilarSize = false;
Color pixelColor = null;

mDominantColor = iColor;

// allocating tempImage only once:
if (mTempImage == null) {
mTempImage = new BufferedImage(pictureWidth, pictureHeight, BufferedImage.TYPE_INT_ARGB);
mTempPixelsArray = new int[pictureWidth * pictureHeight];
}

notSimilarSize = (mTempImage.getHeight() != pictureHeight) ||
(mTempImage.getWidth() != pictureWidth);
// this was due to a bug in the capture, at start irregular image sizes are received.
if (notSimilarSize) {
mTempImage = new BufferedImage(pictureWidth, pictureHeight, BufferedImage.TYPE_INT_ARGB);
mTempPixelsArray = new int[pictureWidth * pictureHeight];
}

result = mTempImage;
// getting all the pixels into the mTempPixelsArray
mBufferedImage.getRGB(0, 0, pictureWidth, pictureHeight,
mTempPixelsArray, 0, pictureWidth);

// Going over the image by pixel
for (int j=0 ; j < pictureHeight ; j++) {
for (int i=0; i< pictureWidth; i++) {
// setting pixle location :
pixelLocation = j* pictureWidth + i;
// getting color.
pixelRGB = mTempPixelsArray[pixelLocation];
// converting to Color object to get RGB components.
pixelColor = Color.decode(String.valueOf(pixelRGB));
// checking each base color for range :
inRedRange = (pixelColor.getRed() > (iColor.getRed() - iOffset)) && (pixelColor.getRed() < (iColor.getRed() + iOffset));
inGreenRange = (pixelColor.getGreen() > (iColor.getGreen() - iOffset)) && (pixelColor.getGreen() < (iColor.getGreen() + iOffset));
inBlueRange = (pixelColor.getBlue() > (iColor.getBlue() - iOffset)) && (pixelColor.getBlue() < (iColor.getBlue() + iOffset));
inColorRange = inRedRange && inGreenRange && inBlueRange;
// checking if color is within range.
if (inColorRange) {
// color is in range setting it to white
mTempPixelsArray[pixelLocation] = white;
} else {
// pixel color is out of range setting it to black
mTempPixelsArray[pixelLocation] = black;
}
// TODO remove in production DEBUG add squere with active color
if (i < 25 && j < 25) {
mTempPixelsArray[pixelLocation] = iColor.getRGB();
}
}
}
// setting result's pixels from the mTempPixelsArray
result.setRGB(0, 0, pictureWidth, pictureHeight, mTempPixelsArray,
0, pictureWidth);

mBufferedImage = result;
}

זאת פונקציה שמקבלת צבע וטווח ובודקת האם יש בתמונה פיקסלים באותו צבע (ובטווח) ומסמנת אותם בלבן , את כל השאר היא מסמנת בשחור.

עברתי לדיזיין של producer & consumer וכשאני לא משתמש בפונקציה הכל טוב והתור שבו אני מאחסן את התמונות בד"כ ריק.

אבל כאשר אני משתמש בה תוך בערך חצי דקה התור מתמלא, ולכן אני מסיק שיש שם איזהשהוא חלק שממש מאט אותה.

הצלחתי לקבל שיפור משמעותי על ידי מעבר מקריאה של פיקסל בודד כל פעם לקריאה של כל הפיקסלים בתמונה למערך פעם אחת ועדכון שלו בסוף אבל כרגע התור מתמלא בקצב כפול בערך מהקצב שבו הצרכן מרוקן אותו ..

אני חושב לנסות להשתמש בהיוריסטיקות לעבור על התמונה , נגיד כל שורה שניה מתוך הנחה שאני לא מחפש פיקסל בודד בתמונה אלא עצם יחסית גדול...

אשמח לשמוע מאנשים שיש להם רקע בעיבוד , האם בד"כ עוברים על כל הפיקסלים ?

אגב, תיקון טעות קטן יש ללפטופ שלי מעבד T2080 ולא כמו שציינתי קודם ..

קישור לתוכן
שתף באתרים אחרים

אם אני מנחש נכון מה זה עושה, יש לך בעיית ביצועים חמורה בשורה הזו אשר מתבצעת לכל פיקסל.


pixelColor = Color.decode(String.valueOf(pixelRGB));

ספר לי בבקשה מה היא עושה?

קישור לתוכן
שתף באתרים אחרים

זאת אכן השורה שאני הכי פחות אוהב בקוד ,

יש לי ערך בתור int אבל הסתבכתי בדרך העבודה איתו, ולאובייקט מסוג color יש יכולת להחזיר כל פעם רק את אחד מהצבעים.

אז אני הופך את ה int הזה לאובייקט מסוג color , שלא ממש ברור לי למה יש לו בנאי סטטי שמקבל מחרוזת ולא מספר , אבל זה מה יש ועם זה הסתדרתי..

אני אנסה לשחק עם זה נראה מה יצא ..

תוספת :

אוקי , שיחקתי עם זה קצת ואכן זאת השורה הבעייתית החלפתי אותה במשחק עם ביטים והכל הסתדר, תודה רבה :)

קישור לתוכן
שתף באתרים אחרים

1) יש לWINDOWS פונקציות API יעודיות לעיבוד תמונה (ה- GDI). אני ממליץ לך לוודא שJAVA משתמשת בו. אם אתה מתכנן פרוייקט רציתי, הייתי ממליץ לך ליצור גם POOL של שיכול להכיל , ולשלוף ממנו כל פעם תמונה אחרת לעבודה עליה.

2) מעבר פיקסל פיקסל לא יעיל. עבור כל פיקסל JAVA מוודאת שהאינדקסים שהבאת טובים ולא חורגים מגבולות התמונה. שימוש בקוד LOW LEVELים יעשה רק טוב. אני יודע שבC# יש אפשרות לעבוד בקוד שהוא unsafe, וכך לגשת לזיכרון ישירות. תבדוק אם יש אופציה כזו גם בJAVA.

3) את הבדיקה הזו:


pixelColor.getRed() > (iColor.getRed() - iOffset)) && (pixelColor.getRed() < (iColor.getRed() + iOffset

אפשר לבצע ע"י פחות פעולות חיבור. תחשב את iColor-iOffset בהתחלה ואז תשווה אליו כל צבע שאתה מקבל. יש כאן 6 השוואות ובלי פעולות אריתמטיות לכל פיקסל.

קישור לתוכן
שתף באתרים אחרים

באופן כללי העצה נכונה - עיבוד תמונה רציני דורש אופטמיזציה.

אבל במקרה הזה לא בטוח שווה להשקיע.

כבר שנים ש-JITTER-ים טובים מקמפלים החוצה את בדיקת האינדקסים בלולאות שכתובות "נכון".

לדוגמא אם תנאי הסיום של הלולאה על האינקדס הוא שהוא לא גדול מאורך המערך (וכן עוד מספר תנאים יחסית פשוטים), אז C# כבר שנים לא מבצע בדיקת האינדקס.

כמובן צריך שהלולאה תתאים.

בכל מקרה ממה שאני רואה שהוא עושה, אין טעם לעבוד קשה מדי על אופטימיזציות או לטרוח לעבור ל-unmanaged/unsafe. מעבר ב-JAVA על כל פיקסל (כל עוד זה בצורת גישה למערך ולא קריאה לפונקציה) יהיה מספיק טוב בשביל לעשות threshold על תמונת VGA בבערך 30 פריימים לשניה.

(ובכל מקרה על בד"כ עדיף לעשות עוד כמה פעולות אריתמטיות מאשר לשרוף עוד רגיסטרים).

הדבר העיקרי שהייתי מתקן: במקום לעשות שתי לולאות על i ו-j ולהכפיל אותם כדי לקבל אינקדס - פשוט תעשה לולאה על האינדקס. כך תעבור על כל הפיקסלים באותו סדר, וגם תפשט את הקוד שיהיה קצר יותר. ואם במקרה הקומפיילר לא מאוד מוצלח, זה גם יכול לשפר ביצועים. אין downside! תצטרך להוציא את שתי השורות שמציירות ריבוע לצורך DEBUG, כמובן.

קישור לתוכן
שתף באתרים אחרים

ארכיון

דיון זה הועבר לארכיון ולא ניתן להוסיף בו תגובות חדשות.

×
  • צור חדש...